diff --git a/.gitignore b/.gitignore index 68bc86872..c15bc2af5 100644 --- a/.gitignore +++ b/.gitignore @@ -262,3 +262,4 @@ SixLabors.Shapes.Coverage.xml /coverage.*.xml *.lcov +BenchmarkDotNet.Artifacts/ diff --git a/.gitmodules b/.gitmodules index 94d28dd52..5773a171a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "shared-infrastructure"] path = shared-infrastructure url = https://github.com/SixLabors/SharedInfrastructure +[submodule "tests/harfbuzz"] + path = tests/harfbuzz + url = https://github.com/harfbuzz/harfbuzz.git diff --git a/src/SixLabors.Fonts/ArraySlice{T}.cs b/src/SixLabors.Fonts/ArraySlice{T}.cs index 27200610f..40d0fad0b 100644 --- a/src/SixLabors.Fonts/ArraySlice{T}.cs +++ b/src/SixLabors.Fonts/ArraySlice{T}.cs @@ -126,7 +126,7 @@ public void CopyTo(ArraySlice destination) /// Thrown when the specified or end index is not in range (<0 or >Length). /// public ArraySlice Slice(int start, int length) - => new(this.data, start, length); + => new(this.data, this.Start + start, length); /// public IEnumerator GetEnumerator() => new Enumerator(this); diff --git a/src/SixLabors.Fonts/BidiReordering.cs b/src/SixLabors.Fonts/BidiReordering.cs new file mode 100644 index 000000000..6a1ba9083 --- /dev/null +++ b/src/SixLabors.Fonts/BidiReordering.cs @@ -0,0 +1,324 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; + +namespace SixLabors.Fonts; + +/// +/// Reorders positioned glyph records from logical order to visual order for one +/// resolved line. +/// +/// +/// Implements rule L2 of the +/// Unicode Bidirectional Algorithm. +/// +internal static class BidiReordering +{ + /// + /// Supplies level lookup and range reversal for a glyph storage representation. + /// Static operations let the JIT specialize the shared loop without delegates, + /// closures, or interface instances on the shaping hot path. + /// + /// The reordered storage. + private interface IBidiReorderingOperations + { + /// + /// Gets the resolved embedding level of one glyph. + /// + /// The reordered storage. + /// The glyph index. + /// The resolved embedding level. + public static abstract int GetLevel(TState state, int index); + + /// + /// Reverses one half-open range while keeping each glyph record intact. + /// + /// The reordered storage. + /// The first glyph index. + /// The index after the final glyph. + public static abstract void Reverse(TState state, int start, int end); + } + + /// + /// Reorders layout run fragments for one line after its final source boundary + /// is known. + /// + /// The logically ordered layout entries. + /// The orientation used to finalize each directional run. + public static void Reorder(List glyphs, LayoutMode layoutMode) + { + // Browsers slice each already-visual shaped run by source range before + // line items are reordered. Composition keeps + // source-codepoint containers logical for line breaking, so first arrange + // those complete containers in the same visual order as their shaped run. + // The positioned glyphs inside each container already retain projected + // visual order and are never reversed here. + int fragmentStart = 0; + while (fragmentStart < glyphs.Count) + { + int fragmentEnd = FindFragmentEnd(glyphs, fragmentStart, glyphs.Count); + if ((glyphs[fragmentStart].BidiRun.Level & 1) != 0) + { + if (layoutMode.IsVertical()) + { + // The public ShapeRun contract is HarfBuzz-verified for upright + // bottom-to-top runs: graphemes reverse, while positioned glyph + // order inside each grapheme remains unchanged. + glyphs.Reverse(fragmentStart, fragmentEnd - fragmentStart); + int graphemeStart = fragmentStart; + while (graphemeStart < fragmentEnd) + { + int graphemeIndex = glyphs[graphemeStart].GraphemeIndex; + int graphemeEnd = graphemeStart + 1; + while (graphemeEnd < fragmentEnd + && glyphs[graphemeEnd].GraphemeIndex == graphemeIndex) + { + graphemeEnd++; + } + + glyphs.Reverse(graphemeStart, graphemeEnd - graphemeStart); + graphemeStart = graphemeEnd; + } + } + else + { + // Horizontal and mixed-vertical backward runs store HarfBuzz's + // complete backward glyph stream: shaped source positions appear + // in reverse source order while the glyphs inside one source + // position already carry the stream's visual order and offsets. + // Every entry is one complete source position holding its slice + // of that stream, so reversing entry order alone reproduces the + // stored stream exactly and no glyph inside an entry ever moves. + // This is the browser's fragment discipline: sliced fragments + // stay visual and only whole units reorder. + glyphs.Reverse(fragmentStart, fragmentEnd - fragmentStart); + } + } + + fragmentStart = fragmentEnd; + } + + int maximumLevel = 0; + int minimumOddLevel = int.MaxValue; + for (fragmentStart = 0; fragmentStart < glyphs.Count;) + { + int level = glyphs[fragmentStart].BidiRun.Level; + maximumLevel = Math.Max(maximumLevel, level); + if ((level & 1) != 0) + { + minimumOddLevel = Math.Min(minimumOddLevel, level); + } + + fragmentStart = FindFragmentEnd(glyphs, fragmentStart, glyphs.Count); + } + + if (minimumOddLevel == int.MaxValue) + { + return; + } + + // Browsers perform this step only after line breaking and reorder whole + // runs rather than characters. Apply UAX #9 L2 to the + // complete fragments, preserving the visual glyph order within each one. + for (int level = maximumLevel; level >= minimumOddLevel; level--) + { + int sequenceStart = 0; + while (sequenceStart < glyphs.Count) + { + int fragmentEnd = FindFragmentEnd(glyphs, sequenceStart, glyphs.Count); + while (sequenceStart < glyphs.Count + && glyphs[sequenceStart].BidiRun.Level < level) + { + sequenceStart = fragmentEnd; + fragmentEnd = sequenceStart < glyphs.Count + ? FindFragmentEnd(glyphs, sequenceStart, glyphs.Count) + : sequenceStart; + } + + if (sequenceStart == glyphs.Count) + { + break; + } + + int sequenceEnd = sequenceStart; + int fragmentCount = 0; + while (sequenceEnd < glyphs.Count + && glyphs[sequenceEnd].BidiRun.Level >= level) + { + sequenceEnd = FindFragmentEnd(glyphs, sequenceEnd, glyphs.Count); + fragmentCount++; + } + + if (fragmentCount > 1) + { + // Reversing the complete storage range reverses both fragment + // order and each fragment's contents. Reverse each now-contiguous + // fragment once more to retain its already-visual glyph order. + // This moves values in place and needs no per-run owner, copied + // glyph array, permutation map, or temporary collection. + glyphs.Reverse(sequenceStart, sequenceEnd - sequenceStart); + int restoredStart = sequenceStart; + while (restoredStart < sequenceEnd) + { + int restoredEnd = FindFragmentEnd(glyphs, restoredStart, sequenceEnd); + glyphs.Reverse(restoredStart, restoredEnd - restoredStart); + restoredStart = restoredEnd; + } + } + + sequenceStart = sequenceEnd; + } + } + } + + /// + /// Reorders positioned shaping records for one line. + /// + /// The logically ordered shaping records. + /// The resolved bidirectional runs covering the source text. + /// The source codepoint to bidirectional-run mapping. + public static void Reorder(ShapingBuffer glyphs, BidiRun[] bidiRuns, int[] bidiMap) + => Reorder(new(glyphs, bidiRuns, bidiMap), 0, glyphs.Count); + + /// + /// Reorders a half-open range of positioned shaping records for one line. + /// + /// The logically ordered shaping records. + /// The resolved bidirectional runs covering the source text. + /// The source codepoint to bidirectional-run mapping. + /// The first glyph record in the line. + /// The glyph record immediately after the line. + public static void Reorder(ShapingBuffer glyphs, BidiRun[] bidiRuns, int[] bidiMap, int start, int end) + => Reorder(new(glyphs, bidiRuns, bidiMap), start, end); + + /// + /// Applies rule L2 of the Unicode Bidirectional Algorithm to one line. + /// + /// The reordered storage. + /// The specialized operations for that storage. + /// The reordered storage and any level-mapping state it needs. + /// The first glyph record in the line. + /// The glyph record immediately after the line. + private static void Reorder(TState state, int start, int end) + where TOperations : struct, IBidiReorderingOperations + { + int maximumLevel = 0; + int minimumOddLevel = int.MaxValue; + for (int i = start; i < end; i++) + { + int level = TOperations.GetLevel(state, i); + maximumLevel = Math.Max(maximumLevel, level); + if ((level & 1) != 0) + { + minimumOddLevel = Math.Min(minimumOddLevel, level); + } + } + + if (minimumOddLevel == int.MaxValue) + { + return; + } + + // UAX #9 rule L2 reverses each maximal contiguous sequence whose level is + // at least the current level, walking down from the highest resolved level + // to the lowest odd one. Operating directly on the destination storage + // keeps every glyph's identity, positioning, and source index together and + // requires no temporary permutation or per-run allocation. + for (int level = maximumLevel; level >= minimumOddLevel; level--) + { + int sequenceStart = start; + while (sequenceStart < end) + { + while (sequenceStart < end && TOperations.GetLevel(state, sequenceStart) < level) + { + sequenceStart++; + } + + int sequenceEnd = sequenceStart; + while (sequenceEnd < end && TOperations.GetLevel(state, sequenceEnd) >= level) + { + sequenceEnd++; + } + + if (sequenceEnd - sequenceStart > 1) + { + TOperations.Reverse(state, sequenceStart, sequenceEnd); + } + + sequenceStart = sequenceEnd + 1; + } + } + } + + /// + /// Finds the exclusive end of the directional run fragment beginning at the + /// supplied layout index. + /// + /// The line's layout storage. + /// The first entry in the fragment. + /// The exclusive search limit. + /// The first entry after the fragment. + private static int FindFragmentEnd(List glyphs, int start, int end) + { + BidiRun bidiRun = glyphs[start].BidiRun; + int index = start + 1; + while (index < end && glyphs[index].BidiRun.Equals(bidiRun)) + { + index++; + } + + return index; + } + + /// + /// Holds a shaping buffer together with the source mapping needed to recover + /// each glyph's resolved embedding level. + /// + private readonly struct ShapingGlyphState + { + /// + /// Initializes a new instance of the struct. + /// + /// The positioned shaping records. + /// The resolved bidirectional runs. + /// The source codepoint to bidirectional-run mapping. + public ShapingGlyphState(ShapingBuffer glyphs, BidiRun[] bidiRuns, int[] bidiMap) + { + this.Glyphs = glyphs; + this.BidiRuns = bidiRuns; + this.BidiMap = bidiMap; + } + + /// + /// Gets the positioned shaping records. + /// + public ShapingBuffer Glyphs { get; } + + /// + /// Gets the resolved bidirectional runs. + /// + public BidiRun[] BidiRuns { get; } + + /// + /// Gets the source codepoint to bidirectional-run mapping. + /// + public int[] BidiMap { get; } + } + + /// + /// Adapts positioned shaping records to the shared reordering loop. + /// + private readonly struct ShapingGlyphOperations : IBidiReorderingOperations + { + /// + public static int GetLevel(ShapingGlyphState state, int index) + { + int codePointIndex = state.Glyphs[index].CodePointIndex; + return state.BidiRuns[state.BidiMap[codePointIndex]].Level; + } + + /// + public static void Reverse(ShapingGlyphState state, int start, int end) => state.Glyphs.ReverseRange(start, end); + } +} diff --git a/src/SixLabors.Fonts/Exceptions/FontsThrowHelper.cs b/src/SixLabors.Fonts/Exceptions/FontsThrowHelper.cs index 958bdf58b..f9ff9a3f3 100644 --- a/src/SixLabors.Fonts/Exceptions/FontsThrowHelper.cs +++ b/src/SixLabors.Fonts/Exceptions/FontsThrowHelper.cs @@ -24,4 +24,16 @@ public static T ThrowGlyphMissingException(CodePoint codePoint) [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowDefaultInstance() => throw new FontException("Cannot use the default value type instance to create a font."); + + /// + /// Throws an recording that shaping + /// state was queried outside a segment window, where no shape plan is current. + /// Kept out of the accessor so it stays inlinable. + /// + /// The declared result type of the failed accessor. + /// Never returns; the type satisfies the caller's flow analysis. + [MethodImpl(MethodImplOptions.NoInlining)] + public static T ThrowNoCurrentShapePlan() + => throw new InvalidOperationException( + "No shape plan is current; the operation is only valid while a segment is being shaped."); } diff --git a/src/SixLabors.Fonts/FileFontMetrics.cs b/src/SixLabors.Fonts/FileFontMetrics.cs index b0f9a1993..e292915d1 100644 --- a/src/SixLabors.Fonts/FileFontMetrics.cs +++ b/src/SixLabors.Fonts/FileFontMetrics.cs @@ -79,6 +79,9 @@ private FileFontMetrics(FontDescription description, string path, long offset) /// public override VerticalMetrics VerticalMetrics => this.fontMetrics.Value.VerticalMetrics; + /// + internal override bool HasUnicodeVariationSequences => this.fontMetrics.Value.HasUnicodeVariationSequences; + /// public override short SubscriptXSize => this.fontMetrics.Value.SubscriptXSize; @@ -202,21 +205,25 @@ public override ReadOnlyMemory GetAvailableCodePoints() internal override bool TryGetGSubTable([NotNullWhen(true)] out GSubTable? gSubTable) => this.fontMetrics.Value.TryGetGSubTable(out gSubTable); + /// + internal override bool TryGetGPosTable([NotNullWhen(true)] out GPosTable? gPosTable) + => this.fontMetrics.Value.TryGetGPosTable(out gPosTable); + /// internal override bool TryGetBaselineCoordinate(Tag baselineTag, bool isVerticalLayout, out short coordinate) => this.fontMetrics.Value.TryGetBaselineCoordinate(baselineTag, isVerticalLayout, out coordinate); /// - internal override void ApplySubstitution(GlyphSubstitutionCollection collection) - => this.fontMetrics.Value.ApplySubstitution(collection); + internal override void ApplySubstitution(ShapingBuffer buffer) + => this.fontMetrics.Value.ApplySubstitution(buffer); /// internal override bool TryGetKerningOffset(ushort currentId, ushort nextId, out Vector2 vector) => this.fontMetrics.Value.TryGetKerningOffset(currentId, nextId, out vector); /// - internal override void UpdatePositions(GlyphPositioningCollection collection) - => this.fontMetrics.Value.UpdatePositions(collection); + internal override void UpdatePositions(ShapingBuffer buffer) + => this.fontMetrics.Value.UpdatePositions(buffer); /// internal override float GetGDefVariationDelta(uint packedVariationIndex) @@ -227,9 +234,9 @@ internal override ReadOnlySpan GetNormalizedCoordinates() => this.fontMetrics.Value.GetNormalizedCoordinates(); /// - /// Reads a font collection from the specified filesystem path. + /// Reads a font buffer from the specified filesystem path. /// - /// The filesystem path to the font collection. + /// The filesystem path to the font buffer. /// A read-only memory region containing the font metrics. public static ReadOnlyMemory LoadFontCollection(string path) { diff --git a/src/SixLabors.Fonts/Font.cs b/src/SixLabors.Fonts/Font.cs index 1c197f499..86da85fd0 100644 --- a/src/SixLabors.Fonts/Font.cs +++ b/src/SixLabors.Fonts/Font.cs @@ -373,7 +373,7 @@ public bool TryGetGlyph( TextRun textRun = new() { Start = 0, End = 1, Font = this, TextAttributes = textAttributes, TextDecorations = textDecorations }; FontGlyphMetrics metrics = fontMetrics.GetGlyphMetrics(codePoint, glyphId, textAttributes, textDecorations, layoutMode, support); - glyph = new(metrics.CloneForRendering(textRun), this.Size); + glyph = new(metrics, this.Size, textRun, Vector2.Zero, new Vector2(metrics.AdvanceWidth, metrics.AdvanceHeight)); return true; } diff --git a/src/SixLabors.Fonts/FontGlyphMetrics.cs b/src/SixLabors.Fonts/FontGlyphMetrics.cs index 688938039..45d2f4cd3 100644 --- a/src/SixLabors.Fonts/FontGlyphMetrics.cs +++ b/src/SixLabors.Fonts/FontGlyphMetrics.cs @@ -44,6 +44,24 @@ public abstract class FontGlyphMetrics /// private const float SyntheticBoldEmScale = 1F / 31F; + /// + /// Initializes a new instance of the class, deriving the + /// scale factor and offset from the text attributes: subscript and superscript glyphs + /// receive the font's configured scaling and baseline displacement. All glyphs loaded + /// from font data use this form. + /// + /// The font metrics this glyph belongs to. + /// The glyph identifier. + /// The Unicode code point for this glyph. + /// The glyph bounds in font design units. + /// The advance width in font design units. + /// The advance height in font design units. + /// The left side bearing in font design units. + /// The top side bearing in font design units. + /// The units per em for the font. + /// The text attributes applied to the glyph. + /// The text decorations applied to the glyph. + /// The glyph type. internal FontGlyphMetrics( StreamFontMetrics font, ushort glyphId, @@ -98,6 +116,26 @@ internal FontGlyphMetrics( this.Offset = offset; } + /// + /// Initializes a new instance of the class taking the + /// scale factor and offset verbatim. Used by synthetic metrics such as placeholders, + /// whose dimensions are authored against the plain font scale factor and must not + /// receive attribute-derived scaling. + /// + /// The font metrics this glyph belongs to. + /// The glyph identifier. + /// The Unicode code point for this glyph. + /// The glyph bounds in font design units. + /// The advance width in font design units. + /// The advance height in font design units. + /// The left side bearing in font design units. + /// The top side bearing in font design units. + /// The units per em for the font. + /// The offset in font design units. + /// The scale factor. Dividing a scaled pixels-per-em value by it yields device pixels per font unit. + /// The text attributes applied to the glyph. + /// The text decorations applied to the glyph. + /// The glyph type. internal FontGlyphMetrics( StreamFontMetrics font, ushort glyphId, @@ -110,14 +148,14 @@ internal FontGlyphMetrics( ushort unitsPerEM, Vector2 offset, Vector2 scaleFactor, - TextRun textRun, + TextAttributes textAttributes, + TextDecorations textDecorations, GlyphType glyphType) { - // This is used during cloning. Ensure anything that could be changed is copied. this.FontMetrics = font; this.GlyphId = glyphId; this.CodePoint = codePoint; - this.Bounds = new Bounds(bounds.Min, bounds.Max); + this.Bounds = bounds; this.Width = bounds.Max.X - bounds.Min.X; this.Height = bounds.Max.Y - bounds.Min.Y; this.UnitsPerEm = unitsPerEM; @@ -127,12 +165,11 @@ internal FontGlyphMetrics( this.RightSideBearing = (short)(this.AdvanceWidth - this.LeftSideBearing - this.Width); this.TopSideBearing = topSideBearing; this.BottomSideBearing = (short)(this.AdvanceHeight - this.TopSideBearing - this.Height); - this.TextAttributes = textRun.TextAttributes; - this.TextDecorations = textRun.TextDecorations; + this.TextAttributes = textAttributes; + this.TextDecorations = textDecorations; this.GlyphType = glyphType; this.ScaleFactor = scaleFactor; this.Offset = offset; - this.TextRun = textRun; } /// @@ -148,12 +185,12 @@ internal FontGlyphMetrics( /// /// Gets the advance width for horizontal layout, expressed in font units. /// - public ushort AdvanceWidth { get; private set; } + public ushort AdvanceWidth { get; } /// /// Gets the advance height for vertical layout, expressed in font units. /// - public ushort AdvanceHeight { get; private set; } + public ushort AdvanceHeight { get; } /// /// Gets the left side bearing for horizontal layout, expressed in font units. @@ -211,14 +248,9 @@ internal FontGlyphMetrics( public Vector2 ScaleFactor { get; } /// - /// Gets or sets the offset in font design units. + /// Gets the offset in font design units. /// - internal Vector2 Offset { get; set; } - - /// - /// Gets the text run that the glyph belongs to. - /// - internal TextRun TextRun { get; } = null!; + internal Vector2 Offset { get; } /// /// Gets the text attributes applied to the glyph. @@ -230,60 +262,20 @@ internal FontGlyphMetrics( /// public TextDecorations TextDecorations { get; } - /// - /// Performs a semi-deep clone (FontMetrics are not cloned) for rendering - /// This allows caching the original in the font metrics. - /// - /// The current text run this glyph belongs to. - /// The new . - internal abstract FontGlyphMetrics CloneForRendering(TextRun textRun); - - /// - /// Apply an offset to the glyph. - /// - /// The x-offset. - /// The y-offset. - internal void ApplyOffset(short x, short y) - => this.Offset = Vector2.Transform(this.Offset, Matrix3x2.CreateTranslation(x, y)); - - /// - /// Applies an advance to the glyph. - /// - /// The x-advance. - /// The y-advance. - internal void ApplyAdvance(short x, short y) - { - this.AdvanceWidth = (ushort)(this.AdvanceWidth + x); - - // AdvanceHeight values grow downward but font-space grows upward, hence negation - this.AdvanceHeight = (ushort)(this.AdvanceHeight - y); - } - - /// - /// Sets a new advance width. - /// - /// The x-advance. - internal void SetAdvanceWidth(ushort x) => this.AdvanceWidth = x; - - /// - /// Sets a new advance height. - /// - /// The y-advance. - internal void SetAdvanceHeight(ushort y) => this.AdvanceHeight = y; - /// /// Gets the horizontal shear factor used to synthesize an oblique (faux italic) slant for /// this glyph. A non-zero value is returned only when the associated text run requests an /// italic style that the resolved font face does not itself provide, mirroring the CSS /// font-synthesis: style behavior used by web browsers. /// + /// The text run providing the styling information for this glyph. /// /// The shear factor to apply in the glyph's Y-up coordinate space, or 0 when no /// synthesis is required. /// - internal float GetObliqueSkew() + internal float GetObliqueSkew(TextRun? textRun) { - Font? font = this.TextRun?.ResolvedFont; + Font? font = textRun?.ResolvedFont; if (font is null) { return 0F; @@ -311,8 +303,9 @@ internal static Matrix3x2 CreateObliqueMatrix(float skew) /// Gets a value indicating whether a heavier weight must be synthesized for this glyph. /// Variable fonts apply their registered weight axis instead. /// + /// The text run providing the styling information for this glyph. /// true if bold synthesis is required; otherwise false. - internal bool ShouldSynthesizeBold() + internal bool ShouldSynthesizeBold(TextRun? textRun) { // Chromium deliberately leaves color emoji at their authored weight. Applying outline // dilation to a painted glyph would also distort each independently colored layer. @@ -321,7 +314,6 @@ internal bool ShouldSynthesizeBold() return false; } - TextRun? textRun = this.TextRun; Font? font = textRun?.ResolvedFont; if (font is null || (textRun!.UsesVariableWeight && ReferenceEquals(font.FontMetrics, this.FontMetrics))) { @@ -353,9 +345,10 @@ internal bool ShouldSynthesizeBold() /// halves this value internally before moving points along their lateral bisectors. /// /// The scaled point size, mapped to pixels by the caller. + /// The text run providing the styling information for this glyph. /// The emboldening strength in pixels. - internal float GetSyntheticBoldStrength(float scaledPointSize) - => this.ShouldSynthesizeBold() ? SyntheticBoldEmScale * this.UnitsPerEm * (scaledPointSize / this.ScaleFactor.X) : 0F; + internal float GetSyntheticBoldStrength(float scaledPointSize, TextRun? textRun) + => this.ShouldSynthesizeBold(textRun) ? SyntheticBoldEmScale * this.UnitsPerEm * (scaledPointSize / this.ScaleFactor.X) : 0F; /// /// Calculates the glyph bounding box in device-space (Y-down) coordinates, @@ -371,42 +364,52 @@ internal float GetSyntheticBoldStrength(float scaledPointSize) /// The glyph layout mode (horizontal, vertical, or vertical rotated). /// The render-space origin in pixels. /// The scaled point size, mapped to pixels by the caller. + /// The text run providing the styling information for this glyph. + /// The positioned placement offset in font design units, composed with the glyph's base offset. + /// The positioned advance in font design units used to substitute bounds for glyphs without an outline. /// /// A representing the glyph bounds in device space. /// - internal FontRectangle GetBoundingBox(GlyphLayoutMode mode, Vector2 origin, float scaledPointSize) + internal FontRectangle GetBoundingBox(GlyphLayoutMode mode, Vector2 origin, float scaledPointSize, TextRun? textRun, Vector2 positionOffset, Vector2 positionedAdvance) { Vector2 scale = new(scaledPointSize / this.ScaleFactor.X, scaledPointSize / this.ScaleFactor.Y); Bounds b = this.GetDesignBounds(); - // 1) Substitute fallback bounds if the glyph has no outline. + // 1) Substitute fallback bounds if the glyph has no outline. The box uses the + // positioned advance rather than the font's nominal advance because it stands + // for the space the glyph occupies in the laid-out line: positioning can + // rewrite the advance, and an invisible default ignorable is deliberately + // zeroed, so its nominal advance would claim ink no renderer produces. + // Callers without positioned state pass the nominal metrics advances, which + // are identical whenever nothing repositioned the glyph. if (b.Equals(Bounds.Empty)) { if (mode == GlyphLayoutMode.Vertical) { - // For vertical layout, set Y-up min = -AdvanceHeight to 0 so Y-down is 0..+AdvanceHeight. - b = new Bounds(0f, -this.AdvanceHeight, 0f, 0f); + // For vertical layout, set Y-up min = -advance to 0 so Y-down is 0..+advance. + b = new Bounds(0f, -positionedAdvance.Y, 0f, 0f); } else { - // For horizontal layout, just use advance width. - b = new Bounds(0f, 0f, this.AdvanceWidth, 0f); + // For horizontal layout, just use the positioned advance width. + b = new Bounds(0f, 0f, positionedAdvance.X, 0f); } } - // 2) Apply synthetic weight and the shared outline transform. - Vector2 offsetUp = this.Offset; + // 2) Apply synthetic weight and the shared outline transform. The positioned + // offset composes with any base metric offset in the same Y-up space. + Vector2 offsetUp = this.Offset + positionOffset; // FreeType halves the supplied strength before moving points. Inflating both sides by that // half-strength preserves the nominal increase in width and height for measurement while // the renderer applies the direction-sensitive point dilation. - if (this.ShouldSynthesizeBold()) + if (this.ShouldSynthesizeBold(textRun)) { float inflate = SyntheticBoldEmScale * this.UnitsPerEm * .5F; b = new Bounds(b.Min - new Vector2(inflate), b.Max + new Vector2(inflate)); } - Matrix3x2 outlineTransform = this.GetOutlineTransform(mode); + Matrix3x2 outlineTransform = this.GetOutlineTransform(mode, textRun); b = Bounds.Transform(in b, outlineTransform); offsetUp = Vector2.Transform(offsetUp, outlineTransform); @@ -457,6 +460,8 @@ internal FontRectangle GetBoundingBox(GlyphLayoutMode mode, Vector2 origin, floa /// /// The glyph layout mode to render using. /// The text run providing the styling information for this glyph. + /// The positioned placement offset in font design units, composed with the glyph's base offset. + /// The positioned advance in font design units used to substitute bounds for glyphs without an outline. /// The point size used to render this glyph. /// The pixel density used to render this glyph. /// The hinting mode used to render this glyph. @@ -471,6 +476,8 @@ internal virtual void RenderTo( Vector2 layoutAdvance, GlyphLayoutMode mode, TextRun textRun, + Vector2 positionOffset, + Vector2 positionedAdvance, float pointSize, float dpi, HintingMode hintingMode, @@ -491,7 +498,7 @@ internal virtual void RenderTo( float scaledPPEM = this.GetScaledSize(pointSize, dpi); Matrix3x2 rotation = GetRotationMatrix(mode); - FontRectangle box = this.GetBoundingBox(mode, glyphOrigin, scaledPPEM); + FontRectangle box = this.GetBoundingBox(mode, glyphOrigin, scaledPPEM, textRun, positionOffset, positionedAdvance); GlyphRendererParameters parameters = new(this, textRun, pointSize, dpi, mode, graphemeIndex); if (!renderer.BeginGlyph(in box, in parameters)) @@ -555,7 +562,7 @@ internal virtual void RenderTo( { if (!whitespace) { - this.RenderOutlineTo(outlineTarget, glyphOrigin, mode, scaledPPEM, hintingMode); + this.RenderOutlineTo(outlineTarget, glyphOrigin, mode, textRun, positionOffset, positionedAdvance, scaledPPEM, hintingMode); } renderer.EndGlyph(); @@ -574,17 +581,23 @@ internal virtual void RenderTo( /// /// Renders only the glyph outline geometry to the specified renderer, without glyph or /// decoration bookkeeping, using the same transforms as - /// . + /// . /// /// The surface renderer. /// The origin used to render the glyph outline, in device pixels. /// The glyph layout mode to render using. + /// The text run providing the styling information for this glyph. + /// The positioned placement offset in font design units, composed with the glyph's base offset. + /// The positioned advance in font design units used to substitute bounds for glyphs without an outline. /// The scaled pixels-per-em value used to scale the outline. /// The hinting mode used to render the glyph. internal virtual void RenderOutlineTo( IGlyphRenderer renderer, Vector2 glyphOrigin, GlyphLayoutMode mode, + TextRun? textRun, + Vector2 positionOffset, + Vector2 positionedAdvance, float scaledPPEM, HintingMode hintingMode) { @@ -882,13 +895,14 @@ private static void EmitDecoration( } /// - /// Gets a value indicating whether the specified code point should be skipped when rendering. + /// Gets a value indicating whether the specified code point should be skipped when + /// rendering: line terminators participate in layout but have no glyph to draw. /// /// The code point. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] protected internal static bool ShouldSkipGlyphRendering(CodePoint codePoint) - => UnicodeUtility.ShouldNotBeRendered(codePoint); + => CodePoint.IsNewLine(codePoint); /// /// Returns the size to render/measure the glyph based on the given size and resolution in px units. @@ -930,13 +944,14 @@ internal static Matrix3x2 GetRotationMatrix(GlyphLayoutMode mode) /// shear with the rotation required by the layout mode. /// /// The glyph layout mode. + /// The text run providing the styling information for this glyph. /// The outline transform. - internal Matrix3x2 GetOutlineTransform(GlyphLayoutMode mode) + internal Matrix3x2 GetOutlineTransform(GlyphLayoutMode mode, TextRun? textRun) { // All metrics keep outlines in the glyph's Y-up coordinate system through this transform // and convert to device-space Y-down coordinates afterwards. Decorations use // GetRotationMatrix directly because browser underlines are not themselves italicized. - Matrix3x2 transform = CreateObliqueMatrix(this.GetObliqueSkew()); + Matrix3x2 transform = CreateObliqueMatrix(this.GetObliqueSkew(textRun)); transform *= GetRotationMatrix(mode); return transform; } diff --git a/src/SixLabors.Fonts/FontMetrics.cs b/src/SixLabors.Fonts/FontMetrics.cs index 999d400c5..fc678964c 100644 --- a/src/SixLabors.Fonts/FontMetrics.cs +++ b/src/SixLabors.Fonts/FontMetrics.cs @@ -44,6 +44,13 @@ internal FontMetrics() /// public abstract VerticalMetrics VerticalMetrics { get; } + /// + /// Gets a value indicating whether the font declares Unicode variation sequences. + /// When it does not, glyph lookup never consumes the following codepoint, so + /// shaping can skip decoding it. The conservative default reports support. + /// + internal virtual bool HasUnicodeVariationSequences => true; + /// /// Gets the recommended horizontal size in font design units for subscripts for this font. /// @@ -299,6 +306,13 @@ internal abstract FontGlyphMetrics GetGlyphMetrics( /// true, if the glyph class could be retrieved. internal abstract bool TryGetGSubTable([NotNullWhen(true)] out GSubTable? gSubTable); + /// + /// Tries to get the glyph positioning table. + /// + /// The glyph positioning table. + /// when the font contains the table; otherwise, . + internal abstract bool TryGetGPosTable([NotNullWhen(true)] out GPosTable? gPosTable); + /// /// Tries to get the coordinate of the named baseline from the font's baseline table for /// the given layout direction, read from the default script record of the matching axis. @@ -316,10 +330,10 @@ internal abstract FontGlyphMetrics GetGlyphMetrics( internal abstract bool TryGetBaselineCoordinate(Tag baselineTag, bool isVerticalLayout, out short coordinate); /// - /// Applies any available substitutions to the collection of glyphs. + /// Applies any available substitutions to the buffer of glyphs. /// - /// The glyph substitution collection. - internal abstract void ApplySubstitution(GlyphSubstitutionCollection collection); + /// The glyph substitution buffer. + internal abstract void ApplySubstitution(ShapingBuffer buffer); /// /// Gets the amount, in font units, the glyph should be offset if it is followed by @@ -338,10 +352,10 @@ internal abstract FontGlyphMetrics GetGlyphMetrics( internal abstract bool TryGetKerningOffset(ushort currentId, ushort nextId, out Vector2 vector); /// - /// Applies any available positioning updates to the collection of glyphs. + /// Applies any available positioning updates to the buffer of glyphs. /// - /// The glyph positioning collection. - internal abstract void UpdatePositions(GlyphPositioningCollection collection); + /// The glyph positioning buffer. + internal abstract void UpdatePositions(ShapingBuffer buffer); /// /// Computes a GPOS/GSUB variation delta for the given packed VariationIndex. diff --git a/src/SixLabors.Fonts/Glyph.cs b/src/SixLabors.Fonts/Glyph.cs index 88a4f8b25..b89c3abaa 100644 --- a/src/SixLabors.Fonts/Glyph.cs +++ b/src/SixLabors.Fonts/Glyph.cs @@ -12,11 +12,17 @@ namespace SixLabors.Fonts; public readonly struct Glyph { private readonly float pointSize; + private readonly TextRun textRun; + private readonly Vector2 positionOffset; + private readonly Vector2 positionedAdvance; - internal Glyph(FontGlyphMetrics glyphMetrics, float pointSize) + internal Glyph(FontGlyphMetrics glyphMetrics, float pointSize, TextRun textRun, Vector2 positionOffset, Vector2 positionedAdvance) { this.GlyphMetrics = glyphMetrics; this.pointSize = pointSize; + this.textRun = textRun; + this.positionOffset = positionOffset; + this.positionedAdvance = positionedAdvance; } /// @@ -32,7 +38,7 @@ internal Glyph(FontGlyphMetrics glyphMetrics, float pointSize) /// The DPI to measure the glyph at. /// The rendered glyph bounds. public FontRectangle BoundingBox(GlyphLayoutMode mode, Vector2 glyphOrigin, float dpi) - => this.GlyphMetrics.GetBoundingBox(mode, glyphOrigin, this.pointSize * dpi); + => this.GlyphMetrics.GetBoundingBox(mode, glyphOrigin, this.pointSize * dpi, this.textRun, this.positionOffset, this.positionedAdvance); /// /// Renders the glyph to the render surface. @@ -57,7 +63,7 @@ internal void RenderTo( GlyphLayoutMode mode, TextOptions options) { - TextRun textRun = this.GlyphMetrics.TextRun; + TextRun textRun = this.textRun; float pointSize = textRun.Font?.Size ?? options.Font.Size; this.GlyphMetrics.RenderTo( @@ -68,6 +74,8 @@ internal void RenderTo( layoutAdvance, mode, textRun, + this.positionOffset, + this.positionedAdvance, pointSize, options.Dpi, options.HintingMode, diff --git a/src/SixLabors.Fonts/GlyphLayoutData.cs b/src/SixLabors.Fonts/GlyphLayoutData.cs index 8d7840945..5237d7ace 100644 --- a/src/SixLabors.Fonts/GlyphLayoutData.cs +++ b/src/SixLabors.Fonts/GlyphLayoutData.cs @@ -15,12 +15,12 @@ namespace SixLabors.Fonts; [DebuggerDisplay("{DebuggerDisplay,nq}")] internal struct GlyphLayoutData { - internal const int NoHyphenationMarker = -1; + public const int NoHyphenationMarker = -1; /// /// Initializes a new instance of the struct. /// - /// The shaped glyph metrics for this codepoint. + /// The entry's contiguous slice of the line's shaped glyph storage. /// The font used to shape and render this entry. /// The point size at which the glyph is rendered. /// The scaled advance of this entry. @@ -39,7 +39,7 @@ internal struct GlyphLayoutData /// The UTF-16 character index in the source string. /// The marker index to use if this entry becomes a selected soft-hyphen break. public GlyphLayoutData( - IReadOnlyList metrics, + ReadOnlyMemory metrics, Font font, float pointSize, float scaledAdvance, @@ -78,67 +78,113 @@ public GlyphLayoutData( this.HyphenationMarkerIndex = hyphenationMarkerIndex; } - /// Gets the source codepoint for this entry. - public readonly CodePoint CodePoint => this.Metrics[0].CodePoint; + /// + /// Gets the source codepoint for this entry. + /// + public readonly CodePoint CodePoint => this.Metrics.Span[0].Metrics.CodePoint; - /// Gets the shaped glyph metrics produced for this codepoint (one codepoint may map to several glyphs). - public IReadOnlyList Metrics { get; } + /// + /// Gets the entry's positioned glyphs as a contiguous slice of the line's shaped + /// glyph storage. The slice preserves the stored per-run visual glyph order the + /// shaper produced, so line composition and reordering move entries as whole + /// units and never rearrange the glyphs inside one. Generated entries such as + /// placeholders and markers reference their own single-glyph storage. + /// + public ReadOnlyMemory Metrics { get; } - /// Gets the font used to shape and render this entry. + /// + /// Gets the font used to shape and render this entry. + /// public Font Font { get; } - /// Gets the point size at which this entry is rendered. + /// + /// Gets the point size at which this entry is rendered. + /// public float PointSize { get; } - /// Gets or sets the scaled advance of this entry (mutated by justification). + /// + /// Gets or sets the scaled advance of this entry (mutated by justification). + /// public float ScaledAdvance { get; set; } - /// Gets the scaled line height contributed by this entry, before line-spacing is applied. + /// + /// Gets the scaled line height contributed by this entry, before line-spacing is applied. + /// public float ScaledLineHeight { get; } - /// Gets the scaled typographic ascender. + /// + /// Gets the scaled typographic ascender. + /// public float ScaledAscender { get; } - /// Gets the scaled typographic descender. + /// + /// Gets the scaled typographic descender. + /// public float ScaledDescender { get; } - /// Gets the symmetric ascender/descender delta applied during line-box construction. + /// + /// Gets the symmetric ascender/descender delta applied during line-box construction. + /// public float ScaledDelta { get; } - /// Gets the smallest (most negative) scaled Y across . + /// + /// Gets the smallest (most negative) scaled Y across . + /// public float ScaledMinY { get; } - /// Gets the resolved bidi run this entry belongs to. + /// + /// Gets the resolved bidi run this entry belongs to. + /// public BidiRun BidiRun { get; } - /// Gets the text direction derived from . + /// + /// Gets the text direction derived from . + /// public readonly TextDirection TextDirection => (TextDirection)this.BidiRun.Direction; - /// Gets the zero-based grapheme index in the original text. + /// + /// Gets the zero-based grapheme index in the original text. + /// public int GraphemeIndex { get; } - /// Gets or sets a value indicating whether this is the last entry in its grapheme cluster. + /// + /// Gets or sets a value indicating whether this is the last entry in its grapheme cluster. + /// public bool IsLastInGrapheme { get; set; } - /// Gets the index of this codepoint within its grapheme cluster (0-based). + /// + /// Gets the index of this codepoint within its grapheme cluster (0-based). + /// public int GraphemeCodePointIndex { get; } - /// Gets the codepoint index in the source text. + /// + /// Gets the codepoint index in the source text. + /// public int CodePointIndex { get; } - /// Gets a value indicating whether the entry participates in a transformed vertical layout. + /// + /// Gets a value indicating whether the entry participates in a transformed vertical layout. + /// public bool IsTransformed { get; } - /// Gets a value indicating whether the entry was produced by Unicode decomposition. + /// + /// Gets a value indicating whether the entry was produced by Unicode decomposition. + /// public bool IsDecomposed { get; } - /// Gets the zero-based UTF-16 code unit index in the original text. + /// + /// Gets the zero-based UTF-16 code unit index in the original text. + /// public int StringIndex { get; } - /// Gets the marker index to use if this entry becomes a selected soft-hyphen break. + /// + /// Gets the marker index to use if this entry becomes a selected soft-hyphen break. + /// public int HyphenationMarkerIndex { get; } - /// Gets a value indicating whether the codepoint is a line-break character. + /// + /// Gets a value indicating whether the codepoint is a line-break character. + /// public readonly bool IsNewLine => CodePoint.IsNewLine(this.CodePoint); private readonly string DebuggerDisplay => FormattableString diff --git a/src/SixLabors.Fonts/GlyphMetrics.cs b/src/SixLabors.Fonts/GlyphMetrics.cs index 3aa70259d..99952800e 100644 --- a/src/SixLabors.Fonts/GlyphMetrics.cs +++ b/src/SixLabors.Fonts/GlyphMetrics.cs @@ -14,6 +14,7 @@ public readonly struct GlyphMetrics /// Initializes a new instance of the struct. /// /// The Unicode code point represented by the glyph entry. + /// The glyph identifier within the entry's font. /// The positioned logical advance rectangle for the glyph entry in pixel units. /// The rendered rectangle for the glyph entry in pixel units. /// The union of the positioned logical advance rectangle and rendered rectangle in pixel units. @@ -22,6 +23,7 @@ public readonly struct GlyphMetrics /// The UTF-16 index in the original text where the glyph entry begins. internal GlyphMetrics( CodePoint codePoint, + ushort glyphId, in FontRectangle advance, in FontRectangle bounds, in FontRectangle renderableBounds, @@ -30,6 +32,7 @@ internal GlyphMetrics( int stringIndex) { this.CodePoint = codePoint; + this.GlyphId = glyphId; this.Advance = advance; this.Bounds = bounds; this.RenderableBounds = renderableBounds; @@ -43,6 +46,11 @@ internal GlyphMetrics( /// public CodePoint CodePoint { get; } + /// + /// Gets the glyph identifier within . + /// + public ushort GlyphId { get; } + /// /// Gets the positioned logical advance rectangle for the glyph entry in pixel units. /// @@ -75,5 +83,5 @@ internal GlyphMetrics( /// public override string ToString() - => $"CodePoint: {this.CodePoint}, Advance: {this.Advance}, Bounds: {this.Bounds}, RenderableBounds: {this.RenderableBounds}."; + => $"CodePoint: {this.CodePoint}, GlyphId: {this.GlyphId}, Advance: {this.Advance}, Bounds: {this.Bounds}, RenderableBounds: {this.RenderableBounds}."; } diff --git a/src/SixLabors.Fonts/GlyphPositioningCollection.cs b/src/SixLabors.Fonts/GlyphPositioningCollection.cs deleted file mode 100644 index 1bb4b918d..000000000 --- a/src/SixLabors.Fonts/GlyphPositioningCollection.cs +++ /dev/null @@ -1,457 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using SixLabors.Fonts.Tables.AdvancedTypographic; -using SixLabors.Fonts.Unicode; - -namespace SixLabors.Fonts; - -/// -/// Represents a collection of glyph metrics that are mapped to input codepoints. -/// -internal sealed class GlyphPositioningCollection : IGlyphShapingCollection -{ - /// - /// Contains a map the index of a map within the collection, non-sequential codepoint offsets, and their glyph ids, point size, and mtrics. - /// - private readonly List glyphs = []; - - /// - /// Initializes a new instance of the class. - /// - /// The text options. - public GlyphPositioningCollection(TextOptions textOptions) => this.TextOptions = textOptions; - - /// - public int Count => this.glyphs.Count; - - /// - public TextOptions TextOptions { get; } - - /// - public GlyphShapingData this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.glyphs[index].Data; - } - - /// - public void AddShapingFeature(int index, TagEntry feature) - { - GlyphShapingData data = this.glyphs[index].Data; - data.Features.Add(feature); - if (feature.Enabled) - { - data.EnabledFeatureTags.Add(feature.Tag); - } - } - - /// - public void EnableShapingFeature(int index, Tag feature) - { - GlyphShapingData data = this.glyphs[index].Data; - List features = data.Features; - for (int i = 0; i < features.Count; i++) - { - TagEntry tagEntry = features[i]; - if (tagEntry.Tag == feature) - { - tagEntry.Enabled = true; - features[i] = tagEntry; - data.EnabledFeatureTags.Add(feature); - break; - } - } - } - - /// - public void DisableShapingFeature(int index, Tag feature) - { - GlyphShapingData data = this.glyphs[index].Data; - List features = data.Features; - for (int i = 0; i < features.Count; i++) - { - TagEntry tagEntry = features[i]; - if (tagEntry.Tag == feature) - { - tagEntry.Enabled = false; - features[i] = tagEntry; - data.EnabledFeatureTags.Remove(feature); - break; - } - } - } - - /// - /// Gets the glyph metrics at the given codepoint offset. - /// - /// The zero-based index within the input codepoint collection. - /// - /// The index within the glyph list to start searching from. Updated to the position of the match - /// so that subsequent calls with increasing offsets avoid rescanning from the beginning. - /// - /// The font size in PT units of the font containing this glyph. - /// Whether the glyph is the result of a substitution. - /// Whether the glyph is the result of a vertical substitution. - /// Whether the glyph is the result of a decomposition substitution. - /// - /// When this method returns, contains the glyph metrics associated with the specified offset, - /// if the value is found; otherwise, the default value for the type of the metrics parameter. - /// This parameter is passed uninitialized. - /// - /// The metrics. - public bool TryGetGlyphMetricsAtOffset( - int offset, - ref int startIndex, - out float pointSize, - out bool isSubstituted, - out bool isVerticalSubstitution, - out bool isDecomposed, - [NotNullWhen(true)] out IReadOnlyList? data) - { - List match = []; - pointSize = 0; - isSubstituted = false; - isVerticalSubstitution = false; - isDecomposed = false; - - Tag vert = KnownFeatureTags.VerticalAlternates; - Tag vrt2 = KnownFeatureTags.VerticalAlternatesAndRotation; - Tag vrtr = KnownFeatureTags.VerticalAlternatesForRotation; - - for (int i = startIndex; i < this.glyphs.Count; i++) - { - if (this.glyphs[i].Offset == offset) - { - if (match.Count == 0) - { - startIndex = i; - } - - GlyphPositioningData glyph = this.glyphs[i]; - if (!glyph.Data.IsPlaceholder) - { - isSubstituted = glyph.Data.IsSubstituted; - isDecomposed = glyph.Data.IsDecomposed; - - foreach (Tag feature in glyph.Data.AppliedFeatures) - { - isVerticalSubstitution |= feature == vert; - isVerticalSubstitution |= feature == vrt2; - isVerticalSubstitution |= feature == vrtr; - } - - pointSize = glyph.PointSize; - } - - match.Add(glyph); - } - else if (match.Count > 0) - { - // Offsets, though non-sequential, are sorted, so we can stop searching. - break; - } - } - - data = match; - return match.Count > 0; - } - - /// - /// Updates the collection of glyph ids to the metrics collection to overwrite any glyphs that have been previously - /// identified as fallbacks. - /// - /// The font face with metrics. - /// The glyph substitution collection. - /// if the metrics collection does not contain any fallbacks; otherwise . - public bool TryUpdate(Font font, GlyphSubstitutionCollection collection) - { - FontMetrics fontMetrics = font.FontMetrics; - LayoutMode layoutMode = this.TextOptions.LayoutMode; - ColorFontSupport colorFontSupport = this.TextOptions.ColorFontSupport; - bool hasFallBacks = false; - List orphans = []; - - Tag vert = KnownFeatureTags.VerticalAlternates; - Tag vrt2 = KnownFeatureTags.VerticalAlternatesAndRotation; - Tag vrtr = KnownFeatureTags.VerticalAlternatesForRotation; - - for (int i = 0; i < this.glyphs.Count; i++) - { - GlyphPositioningData current = this.glyphs[i]; - if (current.Metrics.GlyphType != GlyphType.Fallback) - { - // We've already got the correct glyph. - continue; - } - - int offset = current.Offset; - float pointSize = current.PointSize; - if (collection.TryGetGlyphShapingDataAtOffset(offset, out IReadOnlyList? data)) - { - int replacementCount = 0; - for (int j = 0; j < data.Count; j++) - { - GlyphShapingData shape = data[j]; - ushort id = shape.GlyphId; - CodePoint codePoint = shape.CodePoint; - - // Perform a semi-deep clone (FontMetrics is not cloned) so we can continue to - // cache the original in the font metrics and only update our collection. - TextAttributes textAttributes = shape.TextRun.TextAttributes; - TextDecorations textDecorations = shape.TextRun.TextDecorations; - - bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(codePoint, layoutMode); - foreach (Tag feature in shape.AppliedFeatures) - { - isVertical |= feature == vert; - isVertical |= feature == vrt2; - isVertical |= feature == vrtr; - } - - FontGlyphMetrics metrics = fontMetrics.GetGlyphMetrics(codePoint, id, textAttributes, textDecorations, layoutMode, colorFontSupport); - { - // If the glyphs are fallbacks we don't want them as - // we've already captured them on the first run. - if (metrics.GlyphType == GlyphType.Fallback && !CodePoint.IsControl(codePoint)) - { - hasFallBacks = true; - } - } - - if (metrics.GlyphType != GlyphType.Fallback) - { - if (replacementCount == 0) - { - // There should only be a single fallback glyph at this position from the previous collection. - this.glyphs.RemoveAt(i); - } - - // We only want a single dimensional advance for positioning. - GlyphShapingBounds bounds = isVertical - ? new(0, 0, 0, metrics.AdvanceHeight) - : new(0, 0, metrics.AdvanceWidth, 0); - - // Track the number of inserted glyphs at the offset so we can correctly increment our position. - this.glyphs.Insert(i += replacementCount, new(offset, new(shape, true) { Bounds = bounds }, font, pointSize, metrics.CloneForRendering(shape.TextRun))); - replacementCount++; - } - } - } - else - { - // If a font had glyphs but a follow up font also has them and can substitute. e.g ligatures - // then we end up with orphaned fallbacks. We need to remove them. - orphans.Add(i); - } - } - - // Remove any orphans. - for (int i = orphans.Count - 1; i >= 0; i--) - { - this.glyphs.RemoveAt(orphans[i]); - } - - return !hasFallBacks; - } - - /// - /// Adds the collection of glyph ids to the metrics collection. - /// identified as fallbacks. - /// - /// The font face with metrics. - /// The glyph substitution collection. - /// if the metrics collection does not contain any fallbacks; otherwise . - public bool TryAdd(Font font, GlyphSubstitutionCollection collection) - { - bool hasFallBacks = false; - FontMetrics fontMetrics = font.FontMetrics; - LayoutMode layoutMode = this.TextOptions.LayoutMode; - ColorFontSupport colorFontSupport = this.TextOptions.ColorFontSupport; - - Tag vert = KnownFeatureTags.VerticalAlternates; - Tag vrt2 = KnownFeatureTags.VerticalAlternatesAndRotation; - Tag vrtr = KnownFeatureTags.VerticalAlternatesForRotation; - - for (int i = 0; i < collection.Count; i++) - { - GlyphShapingData data = collection.GetGlyphShapingData(i, out int offset); - CodePoint codePoint = data.CodePoint; - ushort id = data.GlyphId; - - if (data.IsPlaceholder) - { - // Placeholders are synthetic glyphs: they need layout metrics but must not - // go through font glyph lookup, fallback resolution, or GPOS positioning. - StreamFontMetrics streamFontMetrics = fontMetrics is FileFontMetrics fileFontMetrics - ? fileFontMetrics.StreamFontMetrics - : (StreamFontMetrics)fontMetrics; - - FontGlyphMetrics placeholderMetrics = new PlaceholderGlyphMetrics( - streamFontMetrics, - data.TextRun.Placeholder.GetValueOrDefault(), - font.Size, - this.TextOptions.Dpi, - data.TextRun); - - GlyphShapingBounds placeholderBounds = layoutMode.IsVertical() - ? new(0, 0, 0, placeholderMetrics.AdvanceHeight) - : new(0, 0, placeholderMetrics.AdvanceWidth, 0); - - GlyphShapingData placeholderData = new(data, true) - { - Bounds = placeholderBounds, - IsPositioned = true - }; - - this.glyphs.Add(new(offset, placeholderData, font, font.Size, placeholderMetrics)); - continue; - } - - // Perform a semi-deep clone (FontMetrics is not cloned) so we can continue to - // cache the original in the font metrics and only update our collection. - TextAttributes textAttributes = data.TextRun.TextAttributes; - TextDecorations textDecorations = data.TextRun.TextDecorations; - - bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(codePoint, layoutMode); - foreach (Tag feature in data.AppliedFeatures) - { - isVertical |= feature == vert; - isVertical |= feature == vrt2; - isVertical |= feature == vrtr; - } - - FontGlyphMetrics metrics = fontMetrics.GetGlyphMetrics(codePoint, id, textAttributes, textDecorations, layoutMode, colorFontSupport); - - if (metrics.GlyphType == GlyphType.Fallback && !CodePoint.IsControl(codePoint)) - { - hasFallBacks = true; - } - - // We only want a single dimensional advance for positioning. - GlyphShapingBounds bounds = isVertical - ? new(0, 0, 0, metrics.AdvanceHeight) - : new(0, 0, metrics.AdvanceWidth, 0); - - this.glyphs.Add(new(offset, new(data, true) { Bounds = bounds }, font, font.Size, metrics.CloneForRendering(data.TextRun))); - } - - return !hasFallBacks; - } - - /// - /// Updates the position of the glyph at the specified index. - /// - /// The font metrics. - /// The zero-based index of the element. - public void UpdatePosition(FontMetrics fontMetrics, int index) - { - GlyphShapingData data = this[index]; - bool isDirtyXY = data.Bounds.IsDirtyXY; - bool isDirtyWH = data.Bounds.IsDirtyWH; - if (!isDirtyXY && !isDirtyWH) - { - // No change required but the glyph has been processed. - data.IsPositioned = true; - return; - } - - ushort glyphId = data.GlyphId; - FontGlyphMetrics m = this.glyphs[index].Metrics; - - if (m.GlyphId == glyphId && fontMetrics == m.FontMetrics) - { - if (isDirtyXY) - { - m.ApplyOffset((short)data.Bounds.X, (short)data.Bounds.Y); - data.IsPositioned = true; - } - - if (isDirtyWH) - { - m.SetAdvanceWidth((ushort)data.Bounds.Width); - m.SetAdvanceHeight((ushort)data.Bounds.Height); - data.IsPositioned = true; - } - } - } - - /// - /// Updates the advanced metrics of the glyphs at the given index and id, - /// adding dx and dy to the current advance. - /// - /// The font face with metrics. - /// The zero-based index of the element. - /// The id of the glyph to offset. - /// The delta x-advance. - /// The delta y-advance. - public void Advance(FontMetrics fontMetrics, int index, ushort glyphId, short dx, short dy) - { - LayoutMode layoutMode = this.TextOptions.LayoutMode; - Tag vert = KnownFeatureTags.VerticalAlternates; - Tag vrt2 = KnownFeatureTags.VerticalAlternatesAndRotation; - Tag vrtr = KnownFeatureTags.VerticalAlternatesForRotation; - - GlyphPositioningData glyph = this.glyphs[index]; - FontGlyphMetrics m = glyph.Metrics; - - if (m.GlyphId == glyphId && fontMetrics == m.FontMetrics) - { - bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(m.CodePoint, layoutMode); - - foreach (Tag feature in glyph.Data.AppliedFeatures) - { - isVertical |= feature == vert; - isVertical |= feature == vrt2; - isVertical |= feature == vrtr; - } - - m.ApplyAdvance(dx, isVertical ? dy : (short)0); - } - } - - /// - /// Returns a value indicating whether the element at the given index should be processed. - /// - /// The font face with metrics. - /// The zero-based index of the elements to position. - /// if the element should be processed; otherwise, . - public bool ShouldProcess(FontMetrics fontMetrics, int index) - { - GlyphPositioningData data = this.glyphs[index]; - if (data.Data.IsPositioned) - { - return false; - } - - return data.Metrics.FontMetrics == fontMetrics; - } - - [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class GlyphPositioningData - { - public GlyphPositioningData(int offset, GlyphShapingData data, Font font, float pointSize, FontGlyphMetrics metrics) - { - this.Offset = offset; - this.Data = data; - this.Font = font; - this.PointSize = pointSize; - this.Metrics = metrics; - } - - public int Offset { get; set; } - - public GlyphShapingData Data { get; set; } - - public Font Font { get; set; } - - public float PointSize { get; set; } - - public FontGlyphMetrics Metrics { get; set; } - - private string DebuggerDisplay => FormattableString.Invariant($"Offset: {this.Offset}, Data: {this.Data.ToDebuggerDisplay()}"); - } -} diff --git a/src/SixLabors.Fonts/GlyphRun.cs b/src/SixLabors.Fonts/GlyphRun.cs deleted file mode 100644 index 2adba8e5b..000000000 --- a/src/SixLabors.Fonts/GlyphRun.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.Fonts; - -/// -/// Represents positioned glyph ids that share one set of glyph rendering options. -/// -public sealed class GlyphRun -{ - /// - /// Initializes a new instance of the class. - /// - /// The glyph identifiers. - /// The glyph origins. - public GlyphRun(ReadOnlyMemory glyphIds, ReadOnlyMemory origins) - { - if (glyphIds.Length != origins.Length) - { - throw new ArgumentException("Glyph id and origin counts must match.", nameof(origins)); - } - - this.GlyphIds = glyphIds; - this.Origins = origins; - } - - /// - /// Gets the glyph identifiers. - /// - public ReadOnlyMemory GlyphIds { get; } - - /// - /// Gets the glyph origins. - /// - public ReadOnlyMemory Origins { get; } - - /// - /// Gets the number of glyphs in the run. - /// - public int Count => this.GlyphIds.Length; -} diff --git a/src/SixLabors.Fonts/GlyphShapingBounds.cs b/src/SixLabors.Fonts/GlyphShapingBounds.cs index 1416cdeea..d80352330 100644 --- a/src/SixLabors.Fonts/GlyphShapingBounds.cs +++ b/src/SixLabors.Fonts/GlyphShapingBounds.cs @@ -6,48 +6,29 @@ namespace SixLabors.Fonts; /// -/// Represents the shaped bounds of a glyph. -/// Uses a class over a struct for ease of use. +/// Represents the shaped bounds of a glyph. A mutable struct embedded in +/// and accessed by reference through +/// : positioning lookups accumulate deltas into +/// the fields in place, and re-seeding is plain value assignment with no allocation. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] -internal class GlyphShapingBounds +internal struct GlyphShapingBounds { - private int x; - private int y; private int width; private int height; public GlyphShapingBounds(int x, int y, int width, int height) { - this.x = x; - this.y = y; + this.X = x; + this.Y = y; this.width = width; this.height = height; - this.IsDirtyXY = false; this.IsDirtyWH = false; } - public int X - { - get => this.x; + public int X { get; set; } - set - { - this.x = value; - this.IsDirtyXY = true; - } - } - - public int Y - { - get => this.y; - - set - { - this.y = value; - this.IsDirtyXY = true; - } - } + public int Y { get; set; } public int Width { @@ -71,10 +52,13 @@ public int Height } } - public bool IsDirtyXY { get; private set; } - + /// + /// Gets a value indicating whether positioning has written either advance dimension. + /// Consumers use this to choose between the positioned advances and the metrics + /// advances. + /// public bool IsDirtyWH { get; private set; } private string DebuggerDisplay - => FormattableString.Invariant($"{this.X} : {this.Y} : {this.Width} : {this.Height} : {this.IsDirtyXY} : {this.IsDirtyWH}"); + => FormattableString.Invariant($"{this.X} : {this.Y} : {this.Width} : {this.Height} : {this.IsDirtyWH}"); } diff --git a/src/SixLabors.Fonts/GlyphShapingClass.cs b/src/SixLabors.Fonts/GlyphShapingClass.cs index 24fd55188..d844b8424 100644 --- a/src/SixLabors.Fonts/GlyphShapingClass.cs +++ b/src/SixLabors.Fonts/GlyphShapingClass.cs @@ -5,19 +5,61 @@ namespace SixLabors.Fonts; internal readonly struct GlyphShapingClass { + /// + /// The bit for base glyphs. + /// + public const ushort BaseProp = 1; + + /// + /// The bit for ligature glyphs. + /// + public const ushort LigatureProp = 2; + + /// + /// The bit for mark glyphs. + /// + public const ushort MarkProp = 4; + + /// + /// The shift positioning the mark attachment class in the high byte of + /// . Lookup flags address attachment classes through their own + /// high byte, so the eight-bit range covers every class a lookup can reference. + /// + public const int MarkAttachmentTypeShift = 8; + public GlyphShapingClass(bool isMark, bool isBase, bool isLigature, ushort markAttachmentType) - { - this.IsMark = isMark; - this.IsBase = isBase; - this.IsLigature = isLigature; - this.MarkAttachmentType = markAttachmentType; - } + => this.Props = (ushort)((isBase ? BaseProp : 0) + | (isLigature ? LigatureProp : 0) + | (isMark ? MarkProp : 0) + | (markAttachmentType << MarkAttachmentTypeShift)); + + /// + /// Initializes a new instance of the struct from + /// an already packed props word, the round-trip used by cached classifications. + /// + /// The packed class props word. + public GlyphShapingClass(ushort props) => this.Props = props; - public bool IsMark { get; } + /// + /// Gets the class packed into a single word: the low byte carries the glyph class + /// bits and the high byte the mark attachment class, so a skip decision is bitwise + /// arithmetic instead of a branch per class. The word is the struct's only storage; + /// the class properties are derived from it. + /// + public ushort Props { get; } - public bool IsBase { get; } + /// + /// Gets a value indicating whether the glyph is classified as a mark. + /// + public bool IsMark => (this.Props & MarkProp) != 0; - public bool IsLigature { get; } + /// + /// Gets a value indicating whether the glyph is classified as a base. + /// + public bool IsBase => (this.Props & BaseProp) != 0; - public ushort MarkAttachmentType { get; } + /// + /// Gets a value indicating whether the glyph is classified as a ligature. + /// + public bool IsLigature => (this.Props & LigatureProp) != 0; } diff --git a/src/SixLabors.Fonts/GlyphShapingData.cs b/src/SixLabors.Fonts/GlyphShapingData.cs index 910cc8954..aac4ace4f 100644 --- a/src/SixLabors.Fonts/GlyphShapingData.cs +++ b/src/SixLabors.Fonts/GlyphShapingData.cs @@ -4,80 +4,211 @@ using System.Diagnostics; using SixLabors.Fonts.Tables.AdvancedTypographic; using SixLabors.Fonts.Unicode; -using static SixLabors.Fonts.Unicode.Resources.IndicShapingData; namespace SixLabors.Fonts; /// -/// Contains supplementary data that allows the shaping of glyphs. +/// Contains supplementary data that allows the shaping of glyphs. Stored by value in +/// the shaping buffer's flat storage; call sites mutate through the buffer indexer so +/// writes land in place. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] -internal class GlyphShapingData +internal struct GlyphShapingData { + /// + /// The syllable classification assigned by the complex-script shapers, stored by + /// value so classification never allocates. A of + /// means no classification has been assigned. + /// + public SyllableInfo Syllable; + + /// + /// The bit recording . + /// + private const ushort LigatedFlag = 1 << 0; + + /// + /// The bit recording . + /// + private const ushort SubstitutedFlag = 1 << 1; + + /// + /// The bit recording . + /// + private const ushort DecomposedFlag = 1 << 2; + + /// + /// The bit recording . + /// + private const ushort PlaceholderFlag = 1 << 3; + + /// + /// The bit recording . + /// + private const ushort DefaultIgnorableFlag = 1 << 4; + + /// + /// The bit recording . + /// + private const ushort HiddenFlag = 1 << 5; + + /// + /// The bit recording . + /// + private const ushort ZwnjFlag = 1 << 7; + + /// + /// The bit recording . + /// + private const ushort ZwjFlag = 1 << 8; + + /// + /// The bit recording . + /// + private const ushort HiddenIgnorableFlag = 1 << 9; + + /// + /// The bit recording a mark-order override of 22. + /// + private const ushort MarkOrder22Flag = 1 << 10; + + /// + /// The bit recording a mark-order override of 26. + /// + private const ushort MarkOrder26Flag = 1 << 11; + + /// + /// The bit recording that this is a fixed stretch tile. + /// + private const ushort FixedStretchFlag = 1 << 12; + + /// + /// The bit recording that this is a repeating stretch tile. + /// + private const ushort RepeatingStretchFlag = 1 << 13; + + /// + /// The bit recording that tracking must preserve this + /// glyph's cursive shaping run. + /// + private const ushort CursiveScriptFlag = 1 << 14; + + /// + /// The bits reserved for the script-specific mark-order override. + /// + private const ushort MarkOrderFlags = MarkOrder22Flag | MarkOrder26Flag; + + /// + /// The bit recording that + /// holds the glyph id was computed for. A default + /// record therefore reports an invalid cache. + /// + private const ushort ShapingClassCacheValidFlag = 1 << 6; + + /// + /// The modulus folding ligature ids into the three-bit range 1..7. Zero remains + /// the not-a-ligature value, so allocation skips it when the serial wraps. + /// + private const int LigatureIdModulus = 7; + private ushort glyphId; /// - /// Initializes a new instance of the class. + /// The three-bit ligature id, or zero when the glyph is not a ligature. + /// + private byte ligatureId; + + /// + /// The ligature component index stored as component + 1, so the default record + /// encodes the -1 no-component sentinel as zero. + /// + private byte ligatureComponentPlusOne; + + /// + /// The number of matched components represented by a ligature. Zero stores the + /// common single-component value without requiring initialization. + /// + private byte ligatureComponentCount; + + /// + /// Packed boolean shaping state addressed through the named flag constants above. + /// Single bits keep the record narrow; the properties are the only readers and + /// writers. /// - /// The text run. - public GlyphShapingData(TextRun textRun) => this.TextRun = textRun; + private ushort flags; /// - /// Initializes a new instance of the class. + /// The glyph id the cached shaping class was computed for; meaningful only while + /// the cache-valid flag bit is set. + /// + private ushort shapingClassCacheId; + + /// + /// The text direction, stored as a byte to keep the record narrow. + /// + private byte direction; + + /// + /// Initializes a new instance of the struct. The + /// feature masks are seeded with the shared global bit: global features apply to + /// every glyph by definition, so their registration never walks glyph ranges and + /// every record is born carrying the bit their lookups gate on. + /// + /// The index of the text run this glyph belongs to. + public GlyphShapingData(ushort textRunIndex) + { + this.TextRunIndex = textRunIndex; + this.RegisteredFeatureMask = ShapePlanFeatures.GlobalFeatureMask; + this.FeatureMask = ShapePlanFeatures.GlobalFeatureMask; + } + + /// + /// Initializes a new instance of the struct. /// /// The data to copy properties from. /// Whether to clear features. - public GlyphShapingData(GlyphShapingData data, bool clearFeatures = false) + public GlyphShapingData(GlyphShapingData data, bool clearFeatures) { this.GlyphId = data.GlyphId; + this.CodePointIndex = data.CodePointIndex; + this.StringIndex = data.StringIndex; + this.GraphemeIndex = data.GraphemeIndex; this.CodePoint = data.CodePoint; this.CodePointCount = data.CodePointCount; this.Direction = data.Direction; - this.TextRun = data.TextRun; + this.TextRunIndex = data.TextRunIndex; this.LigatureId = data.LigatureId; this.IsLigated = data.IsLigated; this.LigatureComponent = data.LigatureComponent; - this.MarkAttachment = data.MarkAttachment; - this.CursiveAttachment = data.CursiveAttachment; + this.LigatureComponentCount = data.LigatureComponentCount; this.IsSubstituted = data.IsSubstituted; this.IsDecomposed = data.IsDecomposed; this.IsPlaceholder = data.IsPlaceholder; - this.BidiRun = data.BidiRun; - this.IsPositioned = data.IsPositioned; - this.IsKerned = data.IsKerned; - - if (data.UniversalShapingEngineInfo != null) - { - this.UniversalShapingEngineInfo = new( - data.UniversalShapingEngineInfo.Category, - data.UniversalShapingEngineInfo.SyllableType, - data.UniversalShapingEngineInfo.Syllable); - } - - if (data.IndicShapingEngineInfo != null) - { - this.IndicShapingEngineInfo = new( - data.IndicShapingEngineInfo.Category, - data.IndicShapingEngineInfo.Position, - data.IndicShapingEngineInfo.SyllableType, - data.IndicShapingEngineInfo.Syllable); - } + this.IsDefaultIgnorable = data.IsDefaultIgnorable; + this.IsHidden = data.IsHidden; + this.IsZwnj = data.IsZwnj; + this.IsZwj = data.IsZwj; + this.IsHiddenIgnorable = data.IsHiddenIgnorable; + this.MarkOrderOverride = data.MarkOrderOverride; + this.IsFixedStretch = data.IsFixedStretch; + this.IsRepeatingStretch = data.IsRepeatingStretch; + this.IsCursiveScript = data.IsCursiveScript; + + this.Syllable = data.Syllable; if (!clearFeatures) { - this.Features.AddRange(data.Features); - foreach (Tag tag in data.EnabledFeatureTags) - { - this.EnabledFeatureTags.Add(tag); - } + this.RegisteredFeatureMask = data.RegisteredFeatureMask; + this.FeatureMask = data.FeatureMask; } - - foreach (Tag feature in data.AppliedFeatures) + else { - this.AppliedFeatures.Add(feature); + this.RegisteredFeatureMask = ShapePlanFeatures.GlobalFeatureMask; + this.FeatureMask = ShapePlanFeatures.GlobalFeatureMask; } - this.Bounds = data.Bounds; + this.AppliedFeatureMask = data.AppliedFeatureMask; + this.CachedShapingClass = data.CachedShapingClass; this.ShapingClassCacheKey = data.ShapingClassCacheKey; } @@ -87,7 +218,7 @@ public GlyphShapingData(GlyphShapingData data, bool clearFeatures = false) /// public ushort GlyphId { - get => this.glyphId; + readonly get => this.glyphId; set { if (this.glyphId != value) @@ -101,13 +232,47 @@ public ushort GlyphId /// /// Gets or sets the cached glyph shaping class, avoiding repeated GDEF lookups. /// - internal GlyphShapingClass CachedShapingClass { get; set; } + public GlyphShapingClass CachedShapingClass { get; set; } /// /// Gets or sets the cache key for . /// A value of -1 indicates the cache is invalid. Valid entries store the glyph id. /// - internal int ShapingClassCacheKey { get; set; } = -1; + public int ShapingClassCacheKey + { + readonly get => (this.flags & ShapingClassCacheValidFlag) != 0 ? this.shapingClassCacheId : -1; + set + { + if (value < 0) + { + this.flags = (ushort)(this.flags & ~ShapingClassCacheValidFlag); + } + else + { + this.shapingClassCacheId = (ushort)value; + this.flags = (ushort)(this.flags | ShapingClassCacheValidFlag); + } + } + } + + /// + /// Gets or sets the zero-based index within the input codepoint collection of the + /// leading codepoint this glyph represents. + /// + public int CodePointIndex { get; set; } + + /// + /// Gets or sets the zero-based char index in the original text represented by + /// this glyph. + /// + public int StringIndex { get; set; } + + /// + /// Gets or sets the zero-based index of the grapheme this glyph belongs to. The + /// text is walked grapheme by grapheme as the buffer is filled, so the grouping is + /// recorded there rather than worked out again from the characters later. + /// + public int GraphemeIndex { get; set; } /// /// Gets or sets the leading codepoint. @@ -122,146 +287,241 @@ public ushort GlyphId /// /// Gets or sets the text direction. /// - public TextDirection Direction { get; set; } + public TextDirection Direction + { + readonly get => (TextDirection)this.direction; + set => this.direction = (byte)value; + } /// - /// Gets or sets the text run this glyph belongs to. + /// Gets or sets the index of the text run this glyph belongs to, resolved against + /// the buffer's run list. An index keeps the record free of object references, so + /// the garbage collector never scans the pooled glyph arrays. /// - public TextRun TextRun { get; set; } + public ushort TextRunIndex { get; set; } /// - /// Gets or sets the id of any ligature this glyph is a member of. + /// Gets or sets the id of any ligature this glyph is a member of. Zero means the + /// glyph is not a ligature member; assigned ids fold into the three-bit range + /// while remaining distinct from zero. /// - public int LigatureId { get; set; } + public int LigatureId + { + readonly get => this.ligatureId; + set => this.ligatureId = value == 0 ? (byte)0 : (byte)(((value - 1) % LigatureIdModulus) + 1); + } /// /// Gets or sets a value indicating whether the glyph is ligated. /// - public bool IsLigated { get; set; } + public bool IsLigated + { + readonly get => (this.flags & LigatedFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | LigatedFlag) : (ushort)(this.flags & ~LigatedFlag); + } /// - /// Gets or sets the ligature component index of the glyph. + /// Gets or sets the ligature component index of the glyph, or -1 when the glyph + /// is not a ligature component. Stored offset by one so the default record holds + /// the -1 sentinel; indices clamp to the storable range, far beyond any real + /// font's component count. /// - public int LigatureComponent { get; set; } = -1; + public int LigatureComponent + { + readonly get => this.ligatureComponentPlusOne - 1; + set => this.ligatureComponentPlusOne = (byte)(Math.Min(value, byte.MaxValue - 1) + 1); + } /// - /// Gets or sets the index of any mark attachment. + /// Gets or sets the number of matched components represented by this ligature. + /// This differs from because ignored marks may remain + /// between matched components. /// - public int MarkAttachment { get; set; } = -1; + public int LigatureComponentCount + { + readonly get => this.ligatureComponentCount == 0 ? 1 : this.ligatureComponentCount; + set => this.ligatureComponentCount = (byte)Math.Min(value, byte.MaxValue); + } /// - /// Gets or sets the index of any cursive attachment. + /// Gets or sets the mask of features a shaper has registered for this glyph, enabled + /// or not. Bits are assigned by the owning plan's + /// . Enabling a feature + /// only ever reveals a registered bit; a feature that was never registered for the + /// glyph cannot be enabled. /// - public int CursiveAttachment { get; set; } = -1; + public uint RegisteredFeatureMask { get; set; } /// - /// Gets or sets the collection of features. + /// Gets or sets the mask of features currently enabled for this glyph: the subset of + /// a lookup application gate tests with a single + /// bitwise AND. /// - public List Features { get; set; } = []; + public uint FeatureMask { get; set; } /// - /// Gets the set of feature tags that are currently enabled, maintained - /// in sync with for O(1) lookup. + /// Gets or sets the mask of features whose lookups actually changed this glyph. + /// Only the vertical trio is recorded, in the reserved bits every plan shares, + /// because vertical alternate detection is the sole consumer; the record + /// therefore survives the copy into the positioning buffer regardless of which + /// plan wrote it. /// - internal HashSet EnabledFeatureTags { get; } = []; + public uint AppliedFeatureMask { get; set; } /// - /// Gets or sets the collection of applied features. + /// Gets or sets a value indicating whether this glyph is the result of a substitution. /// - public HashSet AppliedFeatures { get; set; } = []; + public bool IsSubstituted + { + readonly get => (this.flags & SubstitutedFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | SubstitutedFlag) : (ushort)(this.flags & ~SubstitutedFlag); + } /// - /// Gets or sets the shaping bounds. + /// Gets or sets a value indicating whether this glyph is the result of a decomposition substitution /// - public GlyphShapingBounds Bounds { get; set; } = new(0, 0, 0, 0); + public bool IsDecomposed + { + readonly get => (this.flags & DecomposedFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | DecomposedFlag) : (ushort)(this.flags & ~DecomposedFlag); + } /// - /// Gets or sets a value indicating whether this glyph is the result of a substitution. + /// Gets or sets a value indicating whether this glyph is a fixed tile in a + /// stretch decomposition. /// - public bool IsSubstituted { get; set; } + public bool IsFixedStretch + { + readonly get => (this.flags & FixedStretchFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | FixedStretchFlag) : (ushort)(this.flags & ~FixedStretchFlag); + } /// - /// Gets or sets a value indicating whether this glyph is the result of a decomposition substitution + /// Gets or sets a value indicating whether this glyph is a repeating tile in a + /// stretch decomposition. + /// + public bool IsRepeatingStretch + { + readonly get => (this.flags & RepeatingStretchFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | RepeatingStretchFlag) : (ushort)(this.flags & ~RepeatingStretchFlag); + } + + /// + /// Gets or sets a value indicating whether tracking must preserve the glyph's + /// cursive shaping run. /// - public bool IsDecomposed { get; set; } + public bool IsCursiveScript + { + readonly get => (this.flags & CursiveScriptFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | CursiveScriptFlag) : (ushort)(this.flags & ~CursiveScriptFlag); + } /// /// Gets or sets a value indicating whether this glyph represents an inline placeholder. + /// A placeholder's bidi run lives on the buffer, keyed by codepoint index. /// - public bool IsPlaceholder { get; set; } + public bool IsPlaceholder + { + readonly get => (this.flags & PlaceholderFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | PlaceholderFlag) : (ushort)(this.flags & ~PlaceholderFlag); + } /// - /// Gets or sets the bidi run assigned to an inline placeholder. + /// Gets or sets a value indicating whether the glyph's codepoint is a default + /// ignorable that renders invisibly. Classified once as the record enters the + /// buffer; the carve-outs that render as regular spacing glyphs, such as the + /// Hangul fillers, never receive the bit. /// - public BidiRun BidiRun { get; set; } + public bool IsDefaultIgnorable + { + readonly get => (this.flags & DefaultIgnorableFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | DefaultIgnorableFlag) : (ushort)(this.flags & ~DefaultIgnorableFlag); + } /// - /// Gets or sets a value indicating whether this glyph has been positioned. + /// Gets or sets a value indicating whether the hide stage replaced this glyph + /// with the invisible glyph at zero advance. Consumers read this recorded + /// decision instead of re-deriving it from the codepoint. /// - public bool IsPositioned { get; set; } + public bool IsHidden + { + readonly get => (this.flags & HiddenFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | HiddenFlag) : (ushort)(this.flags & ~HiddenFlag); + } /// - /// Gets or sets a value indicating whether this glyph has been kerned. + /// Gets or sets a value indicating whether the codepoint is the zero width + /// non-joiner. Classified once as the record enters the buffer; sequence + /// matching consults the bit when deciding joiner transparency. /// - public bool IsKerned { get; set; } + public bool IsZwnj + { + readonly get => (this.flags & ZwnjFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | ZwnjFlag) : (ushort)(this.flags & ~ZwnjFlag); + } /// - /// Gets or sets the universal shaping information. + /// Gets or sets a value indicating whether the codepoint is the zero width + /// joiner. Classified once as the record enters the buffer; sequence matching + /// consults the bit when deciding joiner transparency. /// - public UniversalShapingEngineInfo? UniversalShapingEngineInfo { get; set; } + public bool IsZwj + { + readonly get => (this.flags & ZwjFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | ZwjFlag) : (ushort)(this.flags & ~ZwjFlag); + } /// - /// Gets or sets the Indic shaping information. + /// Gets or sets a value indicating whether the codepoint is a default + /// ignorable that must stay matchable during substitution while positioning + /// treats it as transparent: the Mongolian free variation selectors, the tag + /// characters, and the combining grapheme joiner. /// - public IndicShapingEngineInfo? IndicShapingEngineInfo { get; set; } - - private string DebuggerDisplay - => FormattableString - .Invariant($" {this.GlyphId} : {this.CodePoint.ToDebuggerDisplay()} : {CodePoint.GetScriptClass(this.CodePoint)} : {this.Direction} : {this.TextRun.TextAttributes} : {this.LigatureId} : {this.LigatureComponent} : {this.IsDecomposed}"); - - internal string ToDebuggerDisplay() => this.DebuggerDisplay; -} - -/// -/// Represents information required for universal shaping. -/// -internal class UniversalShapingEngineInfo -{ - public UniversalShapingEngineInfo(string category, string syllableType, int syllable) + public bool IsHiddenIgnorable { - this.Category = category; - this.SyllableType = syllableType; - this.Syllable = syllable; + readonly get => (this.flags & HiddenIgnorableFlag) != 0; + set => this.flags = value ? (ushort)(this.flags | HiddenIgnorableFlag) : (ushort)(this.flags & ~HiddenIgnorableFlag); } - public string Category { get; set; } - - public string SyllableType { get; set; } - - public int Syllable { get; set; } -} - -internal class IndicShapingEngineInfo -{ - public IndicShapingEngineInfo( - Categories category, - Positions position, - string syllableType, - int syllable) + /// + /// Gets or sets the script-specific mark-order override, or zero when the character's generated order applies. + /// + public int MarkOrderOverride { - this.Category = category; - this.Position = position; - this.SyllableType = syllableType; - this.Syllable = syllable; + readonly get => (this.flags & MarkOrder22Flag) != 0 ? 22 : (this.flags & MarkOrder26Flag) != 0 ? 26 : 0; + set + { + this.flags = (ushort)(this.flags & ~MarkOrderFlags); + this.flags = value == 22 + ? (ushort)(this.flags | MarkOrder22Flag) + : value == 26 + ? (ushort)(this.flags | MarkOrder26Flag) + : this.flags; + } } - public Categories Category { get; set; } - - public MyanmarCategories MyanmarCategory => (MyanmarCategories)this.Category; + /// + /// Gets the order used to compare this record with adjacent combining marks. + /// + public readonly int MarkOrderingClass + => this.MarkOrderOverride is int order and not 0 ? order : CodePoint.GetMarkOrderingClass(this.CodePoint); - public Positions Position { get; set; } + private readonly string DebuggerDisplay + => FormattableString + .Invariant($" {this.GlyphId} : {this.CodePoint.ToDebuggerDisplay()} : {CodePoint.GetScriptClass(this.CodePoint)} : {this.Direction} : run {this.TextRunIndex} : {this.LigatureId} : {this.LigatureComponent} : {this.IsDecomposed}"); - public string SyllableType { get; set; } + /// + /// Resets the registered and enabled feature masks to the seeded global bit + /// while preserving the applied mask, matching the semantics of copying with + /// cleared features. Positioning reuses the substituted glyph data and re-plans + /// its own varying features, but the applied record of what substitution did + /// must survive for consumers such as vertical alternate detection. + /// + public void ClearFeatures() + { + this.RegisteredFeatureMask = ShapePlanFeatures.GlobalFeatureMask; + this.FeatureMask = ShapePlanFeatures.GlobalFeatureMask; + } - public int Syllable { get; set; } + public readonly string ToDebuggerDisplay() => this.DebuggerDisplay; } diff --git a/src/SixLabors.Fonts/GlyphShapingPosition.cs b/src/SixLabors.Fonts/GlyphShapingPosition.cs new file mode 100644 index 000000000..179d3fa01 --- /dev/null +++ b/src/SixLabors.Fonts/GlyphShapingPosition.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts; + +/// +/// One glyph's positioning-phase state, stored in a stream parallel to the glyph +/// records: the shaping bounds that accumulate placement and advance adjustments, +/// the attachment links, and the positioned and kerned marks. Entries are seeded +/// alongside the metrics stream after substitution; structural buffer operations +/// before seeding need not preserve alignment, matching the metrics stream. +/// +internal struct GlyphShapingPosition +{ + /// + /// The bit recording . + /// + private const byte PositionedFlag = 1 << 0; + + /// + /// The bit recording . + /// + private const byte KernedFlag = 1 << 1; + + /// + /// The relative cursive attachment value that identifies an unattached glyph. A nonzero value is the signed distance to the parent glyph. + /// + public const int NoCursiveAttachment = 0; + +#pragma warning disable SA1401 // Fields exposed so positioning mutates embedded values in place. + /// + /// The shaping bounds. A field rather than a property so positioning lookups + /// mutate the embedded value in place and re-seeding is plain value assignment. + /// + public GlyphShapingBounds Bounds; + + /// + /// The index of any mark attachment, or -1 when unattached. + /// + public int MarkAttachment; + + /// + /// The signed offset of any cursive attachment, or when unattached. + /// + public int CursiveAttachment; +#pragma warning restore SA1401 + + /// + /// Packed boolean positioning state addressed through the named flag constants + /// above. + /// + private byte flags; + + /// + /// Initializes a new instance of the struct + /// in its seeded state: unattached, unpositioned, with the given bounds. + /// + /// The seeded shaping bounds. + public GlyphShapingPosition(GlyphShapingBounds bounds) + { + this.Bounds = bounds; + this.MarkAttachment = -1; + this.CursiveAttachment = NoCursiveAttachment; + this.flags = 0; + } + + /// + /// Gets or sets a value indicating whether this glyph has been positioned. + /// + public bool IsPositioned + { + readonly get => (this.flags & PositionedFlag) != 0; + set => this.flags = value ? (byte)(this.flags | PositionedFlag) : (byte)(this.flags & ~PositionedFlag); + } + + /// + /// Gets or sets a value indicating whether this glyph has been kerned. + /// + public bool IsKerned + { + readonly get => (this.flags & KernedFlag) != 0; + set => this.flags = value ? (byte)(this.flags | KernedFlag) : (byte)(this.flags & ~KernedFlag); + } +} diff --git a/src/SixLabors.Fonts/GlyphSubstitutionCollection.cs b/src/SixLabors.Fonts/GlyphSubstitutionCollection.cs deleted file mode 100644 index 23895fa2d..000000000 --- a/src/SixLabors.Fonts/GlyphSubstitutionCollection.cs +++ /dev/null @@ -1,462 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using SixLabors.Fonts.Tables.AdvancedTypographic; -using SixLabors.Fonts.Unicode; - -namespace SixLabors.Fonts; - -/// -/// Represents a collection of glyph indices that are mapped to input codepoints. -/// -internal sealed class GlyphSubstitutionCollection : IGlyphShapingCollection -{ - /// - /// Contains a map the index of a map within the collection, non-sequential codepoint offsets, and their glyph ids. - /// - private readonly List glyphs = []; - - /// - /// Initializes a new instance of the class. - /// - /// The text options. - public GlyphSubstitutionCollection(TextOptions textOptions) => this.TextOptions = textOptions; - - /// - /// Gets the number of glyphs ids contained in the collection. - /// This may be more or less than original input codepoint count (due to substitution process). - /// - public int Count => this.glyphs.Count; - - /// - public TextOptions TextOptions { get; } - - /// - /// Gets or sets the running id of any ligature glyphs contained withing this collection are a member of. - /// - public int LigatureId { get; set; } = 1; - - /// - public GlyphShapingData this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.glyphs[index].Data; - } - - /// - /// Gets the shaping data at the specified position. - /// - /// The zero-based index of the elements to get. - /// The zero-based index within the input codepoint collection. - /// The . - internal GlyphShapingData GetGlyphShapingData(int index, out int offset) - { - OffsetGlyphDataPair pair = this.glyphs[index]; - offset = pair.Offset; - return pair.Data; - } - - /// - public void AddShapingFeature(int index, TagEntry feature) - { - GlyphShapingData data = this.glyphs[index].Data; - data.Features.Add(feature); - if (feature.Enabled) - { - data.EnabledFeatureTags.Add(feature.Tag); - } - } - - /// - public void EnableShapingFeature(int index, Tag feature) - { - GlyphShapingData data = this.glyphs[index].Data; - List features = data.Features; - for (int i = 0; i < features.Count; i++) - { - TagEntry tagEntry = features[i]; - if (tagEntry.Tag == feature) - { - tagEntry.Enabled = true; - features[i] = tagEntry; - data.EnabledFeatureTags.Add(feature); - break; - } - } - } - - /// - public void DisableShapingFeature(int index, Tag feature) - { - GlyphShapingData data = this.glyphs[index].Data; - List features = data.Features; - for (int i = 0; i < features.Count; i++) - { - TagEntry tagEntry = features[i]; - if (tagEntry.Tag == feature) - { - tagEntry.Enabled = false; - features[i] = tagEntry; - data.EnabledFeatureTags.Remove(feature); - break; - } - } - } - - /// - /// Adds a clone of the glyph shaping data to the collection at the specified offset. - /// - /// The data. - /// The zero-based index within the input codepoint collection. - public void AddGlyph(GlyphShapingData data, int offset) - => this.glyphs.Add(new(offset, new(data, false))); - - /// - /// Adds the glyph id and the codepoint it represents to the collection. - /// - /// The id of the glyph to add. - /// The codepoint the glyph represents. - /// The resolved text direction for the codepoint. - /// The text run this glyph belongs to. - /// The zero-based index within the input codepoint collection. - public void AddGlyph(ushort glyphId, CodePoint codePoint, TextDirection direction, TextRun textRun, int offset) - => this.glyphs.Add(new(offset, new(textRun) - { - CodePoint = codePoint, - Direction = direction, - GlyphId = glyphId, - })); - - /// - /// Adds an atomic inline placeholder to the collection. - /// - /// The object replacement codepoint used for Unicode processing. - /// The resolved bidi run for the placeholder. - /// The text run this placeholder belongs to. - /// The zero-based index within the input codepoint collection. - public void AddPlaceholder(CodePoint codePoint, BidiRun bidiRun, TextRun textRun, int offset) - => this.glyphs.Add(new(offset, new(textRun) - { - CodePoint = codePoint, - Direction = (TextDirection)bidiRun.Direction, - GlyphId = 0, - IsPlaceholder = true, - BidiRun = bidiRun, - })); - - /// - /// Moves the specified glyph to the specified position. - /// - /// The index to move from. - /// The index to move to. - public void MoveGlyph(int fromIndex, int toIndex) - { - if (fromIndex == toIndex) - { - return; - } - - GlyphShapingData data = this[fromIndex]; - if (fromIndex > toIndex) - { - // Move item to the right - for (int i = fromIndex; i > toIndex; i--) - { - this.glyphs[i].Data = this.glyphs[i - 1].Data; - } - } - else - { - // Move item to the left - for (int i = fromIndex; i < toIndex; i++) - { - this.glyphs[i].Data = this.glyphs[i + 1].Data; - } - } - - this.glyphs[toIndex].Data = data; - } - - /// - /// Reverses the order of elements in the specified range of the collection. - /// - /// - /// The range is interpreted as half-open, from (inclusive) - /// to (exclusive). - /// - /// Both indices are clamped to the valid range [0, ]. - /// If the resulting range contains fewer than two elements, the method performs no action. - /// The method does not throw if either index is equal to ; in such - /// cases the range is considered valid but may be empty. - /// - /// - /// The zero-based index at which to start reversing (inclusive). This value should be - /// greater than or equal to 0. Values greater than are treated as - /// . - /// - /// - /// The zero-based index at which to stop reversing (exclusive). This value should be - /// greater than or equal to . Values greater than - /// are treated as . - /// - public void ReverseRange(int startIndex, int endIndex) - { - int s = Math.Min(startIndex, this.Count); - int e = Math.Min(endIndex, this.Count); - - if (e < s + 2) - { - return; - } - - this.glyphs.Reverse(s, e - s); - } - - /// - /// Performs a stable sort of the glyphs by the comparison delegate starting at the specified index. - /// Only the references are reordered; offsets remain in place. - /// - /// The start index. - /// The end index. - /// The comparison delegate. - public void Sort(int startIndex, int endIndex, Comparison comparer) - { - // Stable insertion sort using adjacent swaps of Data references. - // The sorted ranges are typically small (syllable clusters of 2-10 glyphs), - // so insertion sort is optimal and avoids allocations. Adjacent swaps - // replace the previous MoveGlyph approach which shifted all intermediate elements. - List glyphs = this.glyphs; - for (int i = startIndex + 1; i < endIndex; i++) - { - int j = i; - while (j > startIndex && comparer(glyphs[j - 1].Data, glyphs[j].Data) > 0) - { - // Swap Data references between adjacent slots. - (glyphs[j].Data, glyphs[j - 1].Data) = (glyphs[j - 1].Data, glyphs[j].Data); - j--; - } - } - } - - /// - /// Removes all elements from the collection. - /// - public void Clear() - { - this.glyphs.Clear(); - this.LigatureId = 1; - } - - /// - /// Gets the specified glyph ids matching the given codepoint offset. - /// - /// The zero-based index within the input codepoint collection. - /// - /// When this method returns, contains the shaping data associated with the specified offset, - /// if the value is found; otherwise, the default value for the type of the data parameter. - /// This parameter is passed uninitialized. - /// - /// - /// if the contains glyph ids - /// for the specified offset; otherwise, . - /// - public bool TryGetGlyphShapingDataAtOffset(int offset, [NotNullWhen(true)] out IReadOnlyList? data) - { - List match = []; - for (int i = 0; i < this.glyphs.Count; i++) - { - if (this.glyphs[i].Offset == offset) - { - match.Add(this.glyphs[i].Data); - } - else if (match.Count > 0) - { - // Offsets, though non-sequential, are sorted, so we can stop searching. - break; - } - } - - data = match; - return match.Count > 0; - } - - /// - /// Performs a 1:1 replacement of a glyph id at the given position. - /// - /// The zero-based index of the element to replace. - /// The replacement glyph id. - /// The feature to apply to the glyph at the specified index. - public void Replace(int index, ushort glyphId, Tag feature) - { - GlyphShapingData current = this.glyphs[index].Data; - current.GlyphId = glyphId; - current.LigatureId = 0; - current.LigatureComponent = -1; - current.MarkAttachment = -1; - current.CursiveAttachment = -1; - current.IsSubstituted = true; - current.AppliedFeatures.Add(feature); - } - - /// - /// Performs a 1:1 replacement of a glyph id at the given position while removing a series of glyph ids at the given positions within the sequence. - /// - /// The zero-based index of the element to replace. - /// The indices at which to remove elements. - /// The replacement glyph id. - /// The ligature id. - /// The feature to apply to the glyph at the specified index. - public void Replace(int index, ReadOnlySpan removalIndices, ushort glyphId, int ligatureId, Tag feature) - { - // Remove the glyphs at each index. - int codePointCount = 0; - CodePoint codePoint = default; - for (int i = removalIndices.Length - 1; i >= 0; i--) - { - int match = removalIndices[i]; - codePointCount += this.glyphs[match].Data.CodePointCount; - CodePoint currentCodePoint = this.glyphs[match].Data.CodePoint; - if (!UnicodeUtility.IsDefaultIgnorableCodePoint((uint)codePoint.Value) || UnicodeUtility.ShouldRenderWhiteSpaceOnly(codePoint)) - { - if (!CodePoint.IsZeroWidthJoiner(currentCodePoint) && !CodePoint.IsZeroWidthNonJoiner(currentCodePoint)) - { - codePoint = currentCodePoint; - } - } - - this.glyphs.RemoveAt(match); - } - - // Assign our new id at the index. - GlyphShapingData current = this.glyphs[index].Data; - if (codePoint != default) - { - current.CodePoint = codePoint; - } - - current.CodePointCount += codePointCount; - current.GlyphId = glyphId; - current.LigatureId = ligatureId; - current.IsLigated = true; - current.LigatureComponent = -1; - current.MarkAttachment = -1; - current.CursiveAttachment = -1; - current.IsSubstituted = true; - current.AppliedFeatures.Add(feature); - } - - /// - /// Performs a 1:1 replacement of a glyph id at the given position while removing a series of glyph ids. - /// - /// The zero-based index of the element to replace. - /// The number of glyphs to remove. - /// The replacement glyph id. - /// The feature to apply to the glyph at the specified index. - public void Replace(int index, int count, ushort glyphId, Tag feature) - { - // Remove the glyphs at each index. - int codePointCount = 0; - CodePoint codePoint = default; - for (int i = count; i > 0; i--) - { - int match = index + i; - codePointCount += this.glyphs[match].Data.CodePointCount; - CodePoint currentCodePoint = this.glyphs[match].Data.CodePoint; - if (!UnicodeUtility.IsDefaultIgnorableCodePoint((uint)codePoint.Value) || UnicodeUtility.ShouldRenderWhiteSpaceOnly(codePoint)) - { - if (!CodePoint.IsZeroWidthJoiner(currentCodePoint) && !CodePoint.IsZeroWidthNonJoiner(currentCodePoint)) - { - codePoint = currentCodePoint; - } - } - - this.glyphs.RemoveAt(match); - } - - // Assign our new id at the index. - GlyphShapingData current = this.glyphs[index].Data; - if (codePoint != default) - { - current.CodePoint = codePoint; - } - - current.CodePointCount += codePointCount; - current.GlyphId = glyphId; - current.LigatureId = 0; - current.LigatureComponent = -1; - current.MarkAttachment = -1; - current.CursiveAttachment = -1; - current.IsSubstituted = true; - current.AppliedFeatures.Add(feature); - } - - /// - /// Replaces a single glyph id with a collection of glyph ids. - /// - /// The zero-based index of the element to replace. - /// The collection of replacement glyph ids. - /// The feature to apply to the glyph at the specified index. - public void Replace(int index, ReadOnlySpan glyphIds, Tag feature) - { - if (glyphIds.Length > 0) - { - OffsetGlyphDataPair pair = this.glyphs[index]; - GlyphShapingData current = pair.Data; - current.GlyphId = glyphIds[0]; - current.LigatureComponent = 0; - current.MarkAttachment = -1; - current.CursiveAttachment = -1; - current.IsSubstituted = true; - current.IsDecomposed = true; - - // Add additional glyphs from the rest of the sequence. - if (glyphIds.Length > 1) - { - glyphIds = glyphIds[1..]; - for (int i = 0; i < glyphIds.Length; i++) - { - GlyphShapingData data = new(current, false) - { - GlyphId = glyphIds[i], - LigatureComponent = i + 1 - }; - - data.AppliedFeatures.Add(feature); - - this.glyphs.Insert(++index, new(pair.Offset, data)); - } - } - } - else - { - // Spec disallows removal of glyphs in this manner but it's common enough practice to allow it. - // https://github.com/MicrosoftDocs/typography-issues/issues/673 - this.glyphs.RemoveAt(index); - } - } - - public void Insert(int index, GlyphShapingData data) - { - OffsetGlyphDataPair pair = this.glyphs[index]; - this.glyphs.Insert(index, new(pair.Offset, data)); - } - - [DebuggerDisplay("{DebuggerDisplay,nq}")] - private class OffsetGlyphDataPair - { - public OffsetGlyphDataPair(int offset, GlyphShapingData data) - { - this.Offset = offset; - this.Data = data; - } - - public int Offset { get; set; } - - public GlyphShapingData Data { get; set; } - - private string DebuggerDisplay => FormattableString.Invariant($"Offset: {this.Offset}, Data: {this.Data.ToDebuggerDisplay()}"); - } -} diff --git a/src/SixLabors.Fonts/IGlyphShapingCollection.cs b/src/SixLabors.Fonts/IGlyphShapingCollection.cs deleted file mode 100644 index 9a56fd64e..000000000 --- a/src/SixLabors.Fonts/IGlyphShapingCollection.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.Fonts.Tables.AdvancedTypographic; - -namespace SixLabors.Fonts; - -/// -/// Defines the contract for glyph shaping collections. -/// -internal interface IGlyphShapingCollection -{ - /// - /// Gets the collection count. - /// - public int Count { get; } - - /// - /// Gets the text options used by this collection. - /// - public TextOptions TextOptions { get; } - - /// - /// Gets the glyph shaping data at the specified index. - /// - /// The zero-based index of the elements to get. - /// The . - public GlyphShapingData this[int index] { get; } - - /// - /// Adds the shaping feature to the collection which should be applied to the glyph at a specified index. - /// - /// The zero-based index of the element. - /// The feature to apply. - public void AddShapingFeature(int index, TagEntry feature); - - /// - /// Enables a previously added shaping feature. - /// - /// The zero-based index of the element. - /// The feature to enable. - public void EnableShapingFeature(int index, Tag feature); - - /// - /// Disables a previously added shaping feature. - /// - /// The zero-based index of the element. - /// The feature to disable. - public void DisableShapingFeature(int index, Tag feature); -} diff --git a/src/SixLabors.Fonts/LogicalTextLine.cs b/src/SixLabors.Fonts/LogicalTextLine.cs index 437e86e6f..fb78e28a4 100644 --- a/src/SixLabors.Fonts/LogicalTextLine.cs +++ b/src/SixLabors.Fonts/LogicalTextLine.cs @@ -1,12 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.Fonts.Unicode; - namespace SixLabors.Fonts; /// -/// Contains a composed logical text line and its width-independent line break opportunities. +/// Contains a composed logical text line and the retained source text its line +/// break opportunities are queried from. /// internal readonly struct LogicalTextLine { @@ -14,17 +13,17 @@ internal readonly struct LogicalTextLine /// Initializes a new instance of the struct. /// /// The composed logical text line. - /// The collected line break opportunities. + /// The retained source text used to query line break opportunities. /// The collected word-boundary segment runs. /// The visible hyphenation markers created for soft hyphen entries. public LogicalTextLine( TextLine textLine, - List lineBreaks, + char[] sourceText, List wordSegments, List hyphenationMarkers) { this.TextLine = textLine; - this.LineBreaks = lineBreaks; + this.SourceText = sourceText; this.WordSegments = wordSegments; this.HyphenationMarkers = hyphenationMarkers; } @@ -35,9 +34,12 @@ public LogicalTextLine( public TextLine TextLine { get; } /// - /// Gets the collected line break opportunities. + /// Gets the retained source text. Browsers keep the source text alive and query + /// break opportunities through a lazy cursor per layout pass; retaining the text + /// here lets every wrapping length do the same instead of materializing the + /// paragraph's break candidates. /// - public List LineBreaks { get; } + public char[] SourceText { get; } /// /// Gets the collected word-boundary segment runs. diff --git a/src/SixLabors.Fonts/MemoryFontMetrics.cs b/src/SixLabors.Fonts/MemoryFontMetrics.cs index dc83badc3..08183d1da 100644 --- a/src/SixLabors.Fonts/MemoryFontMetrics.cs +++ b/src/SixLabors.Fonts/MemoryFontMetrics.cs @@ -73,6 +73,9 @@ private MemoryFontMetrics(FontDescription description, byte[] data, long offset) /// public override VerticalMetrics VerticalMetrics => this.fontMetrics.Value.VerticalMetrics; + /// + internal override bool HasUnicodeVariationSequences => this.fontMetrics.Value.HasUnicodeVariationSequences; + /// public override short SubscriptXSize => this.fontMetrics.Value.SubscriptXSize; @@ -196,21 +199,25 @@ public override ReadOnlyMemory GetAvailableCodePoints() internal override bool TryGetGSubTable([NotNullWhen(true)] out GSubTable? gSubTable) => this.fontMetrics.Value.TryGetGSubTable(out gSubTable); + /// + internal override bool TryGetGPosTable([NotNullWhen(true)] out GPosTable? gPosTable) + => this.fontMetrics.Value.TryGetGPosTable(out gPosTable); + /// internal override bool TryGetBaselineCoordinate(Tag baselineTag, bool isVerticalLayout, out short coordinate) => this.fontMetrics.Value.TryGetBaselineCoordinate(baselineTag, isVerticalLayout, out coordinate); /// - internal override void ApplySubstitution(GlyphSubstitutionCollection collection) - => this.fontMetrics.Value.ApplySubstitution(collection); + internal override void ApplySubstitution(ShapingBuffer buffer) + => this.fontMetrics.Value.ApplySubstitution(buffer); /// internal override bool TryGetKerningOffset(ushort currentId, ushort nextId, out Vector2 vector) => this.fontMetrics.Value.TryGetKerningOffset(currentId, nextId, out vector); /// - internal override void UpdatePositions(GlyphPositioningCollection collection) - => this.fontMetrics.Value.UpdatePositions(collection); + internal override void UpdatePositions(ShapingBuffer buffer) + => this.fontMetrics.Value.UpdatePositions(buffer); /// internal override float GetGDefVariationDelta(uint packedVariationIndex) @@ -221,7 +228,7 @@ internal override ReadOnlySpan GetNormalizedCoordinates() => this.fontMetrics.Value.GetNormalizedCoordinates(); /// - /// Reads a collection of instances from the specified stream. + /// Reads a buffer of instances from the specified stream. /// /// The source stream. /// A read-only memory region containing the font metrics. diff --git a/src/SixLabors.Fonts/PlaceholderGlyphMetrics.cs b/src/SixLabors.Fonts/PlaceholderGlyphMetrics.cs index d2d68c166..f6dcf93f6 100644 --- a/src/SixLabors.Fonts/PlaceholderGlyphMetrics.cs +++ b/src/SixLabors.Fonts/PlaceholderGlyphMetrics.cs @@ -24,7 +24,7 @@ internal sealed class PlaceholderGlyphMetrics : FontGlyphMetrics /// The point size used for layout. /// The resolution used to convert placeholder pixels into layout units. /// The text run this placeholder belongs to. - internal PlaceholderGlyphMetrics( + public PlaceholderGlyphMetrics( StreamFontMetrics font, TextPlaceholder placeholder, float pointSize, @@ -42,7 +42,8 @@ internal PlaceholderGlyphMetrics( font.UnitsPerEm, Vector2.Zero, new Vector2(font.ScaleFactor), - textRun, + textRun.TextAttributes, + textRun.TextDecorations, GlyphType.Placeholder) { this.placeholder = placeholder; @@ -50,14 +51,22 @@ internal PlaceholderGlyphMetrics( this.dpi = dpi; } - /// - internal override FontGlyphMetrics CloneForRendering(TextRun textRun) - => new PlaceholderGlyphMetrics( - this.FontMetrics, - this.placeholder, - this.pointSize, - this.dpi, - textRun); + /// + /// Creates placeholder metrics for the given font and placeholder text run. + /// + /// The font whose metrics anchor the placeholder's line metrics. + /// The placeholder text run. + /// The resolution used to convert placeholder pixels into layout units. + /// The placeholder metrics. + public static PlaceholderGlyphMetrics Create(Font font, TextRun textRun, float dpi) + { + FontMetrics fontMetrics = font.FontMetrics; + StreamFontMetrics streamFontMetrics = fontMetrics is FileFontMetrics fileFontMetrics + ? fileFontMetrics.StreamFontMetrics + : (StreamFontMetrics)fontMetrics; + + return new(streamFontMetrics, textRun.Placeholder.GetValueOrDefault(), font.Size, dpi, textRun); + } /// internal override void RenderTo( @@ -68,6 +77,8 @@ internal override void RenderTo( Vector2 layoutAdvance, GlyphLayoutMode mode, TextRun textRun, + Vector2 positionOffset, + Vector2 positionedAdvance, float pointSize, float dpi, HintingMode hintingMode, diff --git a/src/SixLabors.Fonts/PositionedGlyphMetrics.cs b/src/SixLabors.Fonts/PositionedGlyphMetrics.cs new file mode 100644 index 000000000..78f9ba9f0 --- /dev/null +++ b/src/SixLabors.Fonts/PositionedGlyphMetrics.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.Fonts; + +/// +/// Pairs a glyph's metrics with its positioned state for layout: the post-positioning +/// advances and placement offset. Layout and rendering read positions from here so the +/// metrics instance itself can remain immutable and shared. +/// +internal readonly struct PositionedGlyphMetrics +{ + /// + /// Initializes a new instance of the struct. + /// + /// The glyph metrics. + /// The horizontal advance after positioning. + /// The vertical advance after positioning. + /// The placement offset after positioning. + /// The text run the glyph belongs to. + public PositionedGlyphMetrics(FontGlyphMetrics metrics, ushort advanceWidth, ushort advanceHeight, Vector2 offset, TextRun textRun) + { + this.Metrics = metrics; + this.AdvanceWidth = advanceWidth; + this.AdvanceHeight = advanceHeight; + this.Offset = offset; + this.TextRun = textRun; + } + + /// + /// Gets the glyph metrics. + /// + public FontGlyphMetrics Metrics { get; } + + /// + /// Gets the horizontal advance in font design units after positioning. + /// + public ushort AdvanceWidth { get; } + + /// + /// Gets the vertical advance in font design units after positioning. + /// + public ushort AdvanceHeight { get; } + + /// + /// Gets the placement offset in font design units after positioning. + /// + public Vector2 Offset { get; } + + /// + /// Gets the text run the glyph belongs to, carried here so rendering reads it from + /// the positioned state rather than from per-glyph metric clones. + /// + public TextRun TextRun { get; } +} diff --git a/src/SixLabors.Fonts/ReadOnlyArraySlice{T}.cs b/src/SixLabors.Fonts/ReadOnlyArraySlice{T}.cs index 6c0789213..8b5f09b6a 100644 --- a/src/SixLabors.Fonts/ReadOnlyArraySlice{T}.cs +++ b/src/SixLabors.Fonts/ReadOnlyArraySlice{T}.cs @@ -116,7 +116,7 @@ public void CopyTo(ArraySlice destination) /// Thrown when the specified or end index is not in range (<0 or >Length). /// public ReadOnlyArraySlice Slice(int start, int length) - => new(this.data, start, length); + => new(this.data, this.Start + start, length); /// public IEnumerator GetEnumerator() => new Enumerator(this); diff --git a/src/SixLabors.Fonts/Rendering/PaintedGlyphMetrics.cs b/src/SixLabors.Fonts/Rendering/PaintedGlyphMetrics.cs index 4d1d55b80..a08bb1461 100644 --- a/src/SixLabors.Fonts/Rendering/PaintedGlyphMetrics.cs +++ b/src/SixLabors.Fonts/Rendering/PaintedGlyphMetrics.cs @@ -58,56 +58,6 @@ internal PaintedGlyphMetrics( GlyphType.Painted) => this.source = source; - /// - /// Initializes a new instance of the class for rendering with overrides. - /// - internal PaintedGlyphMetrics( - StreamFontMetrics font, - ushort glyphId, - CodePoint codePoint, - IPaintedGlyphSource source, - Bounds bounds, - ushort advanceWidth, - ushort advanceHeight, - short leftSideBearing, - short topSideBearing, - ushort unitsPerEM, - Vector2 offset, - Vector2 scaleFactor, - TextRun textRun) - : base( - font, - glyphId, - codePoint, - bounds, - advanceWidth, - advanceHeight, - leftSideBearing, - topSideBearing, - unitsPerEM, - offset, - scaleFactor, - textRun, - GlyphType.Painted) - => this.source = source; - - /// - internal override FontGlyphMetrics CloneForRendering(TextRun textRun) - => new PaintedGlyphMetrics( - this.FontMetrics, - this.GlyphId, - this.CodePoint, - this.source, - this.Bounds, - this.AdvanceWidth, - this.AdvanceHeight, - this.LeftSideBearing, - this.TopSideBearing, - this.UnitsPerEm, - this.Offset, - this.ScaleFactor, - textRun); - /// internal override Bounds GetDesignBounds() { @@ -125,6 +75,9 @@ internal override void RenderOutlineTo( IGlyphRenderer renderer, Vector2 glyphOrigin, GlyphLayoutMode mode, + TextRun? textRun, + Vector2 positionOffset, + Vector2 positionedAdvance, float scaledPPEM, HintingMode hintingMode) { @@ -134,17 +87,17 @@ internal override void RenderOutlineTo( } Vector2 scale = new Vector2(scaledPPEM) / this.ScaleFactor; // uniform - Matrix3x2 outlineTransform = this.GetOutlineTransform(mode); + Matrix3x2 outlineTransform = this.GetOutlineTransform(mode, textRun); // Keep painted geometry in Y-up font space through the same scale, offset, oblique, and // rotation sequence used by TrueType and CFF, then perform the device-space Y inversion. Matrix3x2 layout = Matrix3x2.CreateScale(scale); - layout.Translation = this.Offset * scale; + layout.Translation = (this.Offset + positionOffset) * scale; layout *= outlineTransform; layout *= Matrix3x2.CreateScale(1F, -1F); layout.Translation += glyphOrigin; - FontRectangle box = this.GetBoundingBox(mode, glyphOrigin, scaledPPEM); + FontRectangle box = this.GetBoundingBox(mode, glyphOrigin, scaledPPEM, textRun, positionOffset, positionedAdvance); // Source-to-UPEM: viewBox mapping (uniform "meet"), optional y-flip, optional root transform. Matrix3x2 s2u = ComputeSourceToUpem(canvas, this.UnitsPerEm); diff --git a/src/SixLabors.Fonts/Rendering/TextRenderer.cs b/src/SixLabors.Fonts/Rendering/TextRenderer.cs index a8736f141..e091ba0f2 100644 --- a/src/SixLabors.Fonts/Rendering/TextRenderer.cs +++ b/src/SixLabors.Fonts/Rendering/TextRenderer.cs @@ -53,13 +53,23 @@ public static void RenderTo(IGlyphRenderer renderer, ushort glyphId, GlyphOption => new TextRenderer(renderer).Render(glyphId, options); /// - /// Renders glyph ids to the . + /// Renders positioned glyph ids to the . /// /// The target renderer. - /// The glyph run. + /// The glyph identifiers. + /// The absolute glyph origins in pixel units. /// The glyph options. - public static void RenderTo(IGlyphRenderer renderer, GlyphRun glyphRun, GlyphOptions options) - => new TextRenderer(renderer).Render(glyphRun, options); + public static void RenderTo(IGlyphRenderer renderer, ReadOnlySpan glyphIds, ReadOnlySpan points, GlyphOptions options) + => new TextRenderer(renderer).Render(glyphIds, points, options); + + /// + /// Renders shaped glyphs to the . + /// + /// The target renderer. + /// The buffer containing the shaped glyphs. + /// The glyph options supplying the shaping font, resolution, and baseline origin. + public static void RenderTo(IGlyphRenderer renderer, TextShapingBuffer buffer, GlyphOptions options) + => new TextRenderer(renderer).Render(buffer, options); /// /// Renders the text to the configured renderer. @@ -83,8 +93,7 @@ public void Render(ReadOnlySpan text, TextOptions options) return; } - ShapedText shaped = TextLayout.ShapeText(text, options); - LogicalTextLine logicalLine = TextLayout.ComposeLogicalLine(shaped, text, options); + LogicalTextLine logicalLine = TextLayout.ComposeLogicalLine(text, options); this.RenderText(logicalLine, options); } @@ -130,8 +139,8 @@ public void Render(ushort glyphId, GlyphOptions options) // divided by DPI), so the ink box computed against it compares directly with the // scaled region. Inflating by the scaled line height for the glyph's orientation // gives em-box anchored decorations the same tolerance culled text receives, and - // rejecting here also skips the per-glyph metrics clone below. - FontRectangle box = metrics.GetBoundingBox(glyphLayoutMode, origin, options.Font.Size); + // rejecting here also skips creating the per-glyph text run below. + FontRectangle box = metrics.GetBoundingBox(glyphLayoutMode, origin, options.Font.Size, null, Vector2.Zero, new Vector2(metrics.AdvanceWidth, metrics.AdvanceHeight)); IMetricsHeader metricsHeader = glyphLayoutMode == GlyphLayoutMode.Vertical ? fontMetrics.VerticalMetrics : fontMetrics.HorizontalMetrics; @@ -148,9 +157,8 @@ public void Render(ushort glyphId, GlyphOptions options) } TextRun textRun = options.CreateTextRun(); - FontGlyphMetrics renderMetrics = metrics.CloneForRendering(textRun); - renderMetrics.RenderTo( + metrics.RenderTo( this.renderer, options.GraphemeIndex, origin, @@ -158,6 +166,8 @@ public void Render(ushort glyphId, GlyphOptions options) NoLayoutAdvance, glyphLayoutMode, textRun, + Vector2.Zero, + new Vector2(metrics.AdvanceWidth, metrics.AdvanceHeight), options.Font.Size, options.Dpi, options.HintingMode, @@ -167,17 +177,16 @@ public void Render(ushort glyphId, GlyphOptions options) } /// - /// Renders glyph ids to the configured renderer. + /// Renders positioned glyph ids to the configured renderer. /// - /// The glyph run. + /// The glyph identifiers. + /// The absolute glyph origins in pixel units. /// The glyph options. - public void Render(GlyphRun glyphRun, GlyphOptions options) + public void Render(ReadOnlySpan glyphIds, ReadOnlySpan points, GlyphOptions options) { - Guard.NotNull(glyphRun, nameof(glyphRun)); Guard.NotNull(options, nameof(options)); + Guard.IsTrue(glyphIds.Length == points.Length, nameof(points), "Glyph id and point counts must match."); - ReadOnlySpan glyphIds = glyphRun.GlyphIds.Span; - ReadOnlySpan origins = glyphRun.Origins.Span; Vector2 originalOrigin = options.Origin; int originalGraphemeIndex = options.GraphemeIndex; @@ -185,7 +194,9 @@ public void Render(GlyphRun glyphRun, GlyphOptions options) { for (int i = 0; i < glyphIds.Length; i++) { - options.Origin = origins[i]; + // Positioned points already belong to the caller's output coordinate + // space; DPI continues to size the outline but does not move the point. + options.Origin = points[i]; options.GraphemeIndex = originalGraphemeIndex + i; this.Render(glyphIds[i], options); @@ -198,6 +209,95 @@ public void Render(GlyphRun glyphRun, GlyphOptions options) } } + /// + /// Renders shaped glyphs to the configured renderer. + /// + /// The buffer containing the shaped glyphs. + /// The glyph options supplying the shaping font, resolution, and baseline origin. + public void Render(TextShapingBuffer buffer, GlyphOptions options) + { + Guard.NotNull(buffer, nameof(buffer)); + Guard.NotNull(options, nameof(options)); + + ReadOnlySpan glyphs = buffer.Glyphs; + ReadOnlySpan lineEnds = buffer.LineEnds; + Vector2 baselineOrigin = options.Origin; + int originalGraphemeIndex = options.GraphemeIndex; + + // Shaped values are already scaled by Font.Size and remain in point units. + // Convert points to device pixels here exactly once. + float scale = options.Dpi / 72F; + + try + { + if (lineEnds.IsEmpty) + { + float penX = 0; + float penY = 0; + for (int i = 0; i < glyphs.Length; i++) + { + ShapedGlyph glyph = glyphs[i]; + + // Public shaping values are Font.Size-scaled in Y-up shaping + // space. Rendering converts them once to DPI-scaled Y-down origins. + options.Origin = new Vector2( + baselineOrigin.X + ((penX + glyph.Offset.X) * scale), + baselineOrigin.Y - ((penY + glyph.Offset.Y) * scale)); + + // Every glyph produced by one grapheme repeats its grapheme + // index, preserving cluster identity through renderer callbacks. + options.GraphemeIndex = originalGraphemeIndex + glyph.GraphemeIndex; + this.Render(glyph.GlyphId, options); + + penX += glyph.AdvanceWidth; + penY += glyph.AdvanceHeight; + } + } + else + { + FontMetrics fontMetrics = options.Font.FontMetrics; + + // Hard line breaks reset the shaping pen. Baseline progression is + // a rendering concern, so derive it here from the supplied font and DPI. + float lineAdvance = fontMetrics.HorizontalMetrics.LineHeight * options.Font.Size * options.Dpi / fontMetrics.ScaleFactor; + float baselineY = baselineOrigin.Y; + int glyphStart = 0; + for (int lineIndex = 0; lineIndex <= lineEnds.Length; lineIndex++) + { + int glyphEnd = lineIndex < lineEnds.Length ? lineEnds[lineIndex] : glyphs.Length; + float penX = 0; + float penY = 0; + for (int i = glyphStart; i < glyphEnd; i++) + { + ShapedGlyph glyph = glyphs[i]; + + // Public shaping values are Font.Size-scaled in Y-up shaping + // space. Rendering converts them once to DPI-scaled Y-down origins. + options.Origin = new Vector2( + baselineOrigin.X + ((penX + glyph.Offset.X) * scale), + baselineY - ((penY + glyph.Offset.Y) * scale)); + + options.GraphemeIndex = originalGraphemeIndex + glyph.GraphemeIndex; + this.Render(glyph.GlyphId, options); + + penX += glyph.AdvanceWidth; + penY += glyph.AdvanceHeight; + } + + glyphStart = glyphEnd; + baselineY += lineAdvance; + } + } + } + finally + { + // GlyphOptions is caller-owned and temporarily reused to avoid creating + // one options object per glyph. Restore every mutated value on all exits. + options.Origin = baselineOrigin; + options.GraphemeIndex = originalGraphemeIndex; + } + } + /// /// Line-breaks and renders prepared text without retaining any layout state. When /// is set, whole lines outside the region are diff --git a/src/SixLabors.Fonts/ShapedGlyph.cs b/src/SixLabors.Fonts/ShapedGlyph.cs new file mode 100644 index 000000000..cdc452530 --- /dev/null +++ b/src/SixLabors.Fonts/ShapedGlyph.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.Fonts; + +/// +/// Represents a single shaped glyph: the result of substitution and positioning, +/// before line breaking. +/// +/// +/// Advances and offsets are scaled to the size of the font the run was shaped +/// against and preserve the shaper's Y-up coordinate system. +/// +public readonly struct ShapedGlyph +{ + /// + /// Initializes a new instance of the struct. + /// + /// The glyph identifier within the font. + /// The index of the first input represented by the glyph. + /// The grapheme index represented by the glyph. + /// The scaled horizontal advance. + /// The scaled vertical advance. + /// The scaled placement offset. + internal ShapedGlyph(ushort glyphId, int stringIndex, int graphemeIndex, float advanceWidth, float advanceHeight, Vector2 offset) + { + this.GlyphId = glyphId; + this.StringIndex = stringIndex; + this.GraphemeIndex = graphemeIndex; + this.AdvanceWidth = advanceWidth; + this.AdvanceHeight = advanceHeight; + this.Offset = offset; + } + + /// + /// Gets the glyph identifier within the font the run was shaped against. Glyph + /// id 0 is the font's missing glyph, produced when the font cannot map the + /// character. + /// + public ushort GlyphId { get; } + + /// + /// Gets the zero-based index of the first input represented + /// by this glyph. + /// + /// + /// Glyphs produced from the same input chars share this value. + /// + public int StringIndex { get; } + + /// + /// Gets the zero-based grapheme index in the input text. + /// + public int GraphemeIndex { get; } + + /// + /// Gets the scaled horizontal advance after positioning features have been + /// applied. + /// + public float AdvanceWidth { get; } + + /// + /// Gets the scaled vertical advance after positioning features have been + /// applied. + /// + public float AdvanceHeight { get; } + + /// + /// Gets the scaled placement offset in Y-up shaping space. The offset positions + /// the glyph outline relative to its pen position and does not contribute to the + /// advance. + /// + public Vector2 Offset { get; } +} diff --git a/src/SixLabors.Fonts/ShapedGlyphInfo.cs b/src/SixLabors.Fonts/ShapedGlyphInfo.cs new file mode 100644 index 000000000..535eb8d07 --- /dev/null +++ b/src/SixLabors.Fonts/ShapedGlyphInfo.cs @@ -0,0 +1,131 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; + +namespace SixLabors.Fonts; + +/// +/// Flags describing how shaping produced a glyph. +/// +[Flags] +internal enum ShapedGlyphFlags : byte +{ + /// + /// No flags. + /// + None = 0, + + /// + /// The entry is an inline placeholder. + /// + Placeholder = 1, + + /// + /// The glyph is the result of a substitution. + /// + Substituted = 2, + + /// + /// The glyph is the result of a decomposition substitution. + /// + Decomposed = 4, + + /// + /// A vertical alternate feature changed the glyph. + /// + VerticalSubstituted = 8, + + /// + /// Tracking must preserve the glyph's cursive shaping run. + /// + CursiveScript = 16, +} + +/// +/// The identity half of a shaped glyph: which glyph, which source codepoints it +/// covers, and which run it belongs to. Positioning values live in the parallel +/// position array. +/// +internal readonly struct ShapedGlyphInfo +{ + /// + /// Initializes a new instance of the struct. + /// + /// The zero-based index within the input codepoint collection. + /// The leading codepoint the glyph represents. + /// The codepoint count represented by the glyph. + /// The glyph id. + /// The index of the owning entry in the shaped run table. + /// The shaping flags. + public ShapedGlyphInfo( + int codePointIndex, + CodePoint codePoint, + int codePointCount, + ushort glyphId, + ushort runIndex, + ShapedGlyphFlags flags) + { + this.CodePointIndex = codePointIndex; + this.CodePoint = codePoint; + this.CodePointCount = codePointCount; + this.GlyphId = glyphId; + this.RunIndex = runIndex; + this.Flags = flags; + } + + /// + /// Gets the zero-based index within the input codepoint collection. + /// + public int CodePointIndex { get; } + + /// + /// Gets the leading codepoint the glyph represents. + /// + public CodePoint CodePoint { get; } + + /// + /// Gets the codepoint count represented by the glyph. + /// + public int CodePointCount { get; } + + /// + /// Gets the glyph id. + /// + public ushort GlyphId { get; } + + /// + /// Gets the index of the owning entry in the shaped run table. + /// + public ushort RunIndex { get; } + + /// + /// Gets the shaping flags. + /// + public ShapedGlyphFlags Flags { get; } + + /// + /// Gets a value indicating whether the entry is an inline placeholder. + /// + public bool IsPlaceholder => (this.Flags & ShapedGlyphFlags.Placeholder) != 0; + + /// + /// Gets a value indicating whether the glyph is the result of a substitution. + /// + public bool IsSubstituted => (this.Flags & ShapedGlyphFlags.Substituted) != 0; + + /// + /// Gets a value indicating whether the glyph is the result of a decomposition substitution. + /// + public bool IsDecomposed => (this.Flags & ShapedGlyphFlags.Decomposed) != 0; + + /// + /// Gets a value indicating whether a vertical alternate feature changed the glyph. + /// + public bool IsVerticalSubstituted => (this.Flags & ShapedGlyphFlags.VerticalSubstituted) != 0; + + /// + /// Gets a value indicating whether tracking must preserve the glyph's cursive shaping run. + /// + public bool IsCursiveScript => (this.Flags & ShapedGlyphFlags.CursiveScript) != 0; +} diff --git a/src/SixLabors.Fonts/ShapedGlyphPosition.cs b/src/SixLabors.Fonts/ShapedGlyphPosition.cs new file mode 100644 index 000000000..b74465d66 --- /dev/null +++ b/src/SixLabors.Fonts/ShapedGlyphPosition.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.Fonts; + +/// +/// The geometry half of a shaped glyph: pure numbers in font design units with no +/// object references. Identity lives in the parallel info array. +/// +internal readonly struct ShapedGlyphPosition +{ + /// + /// Initializes a new instance of the struct. + /// + /// The positioned horizontal advance. + /// The positioned vertical advance. + /// The placement offset written by positioning. + /// The glyph-origin bearing offset copied from the glyph metrics. + public ShapedGlyphPosition(ushort advanceWidth, ushort advanceHeight, Vector2 offset, Vector2 bearing) + { + this.AdvanceWidth = advanceWidth; + this.AdvanceHeight = advanceHeight; + this.Offset = offset; + this.Bearing = bearing; + } + + /// + /// Gets the positioned horizontal advance in font design units. + /// + public ushort AdvanceWidth { get; } + + /// + /// Gets the positioned vertical advance in font design units. + /// + public ushort AdvanceHeight { get; } + + /// + /// Gets the placement offset written by positioning, in font design units. + /// + public Vector2 Offset { get; } + + /// + /// Gets the glyph-origin bearing offset in font design units, copied out of the glyph metrics so projection needs no metrics lookup. + /// + public Vector2 Bearing { get; } +} diff --git a/src/SixLabors.Fonts/ShapedText.cs b/src/SixLabors.Fonts/ShapedText.cs index b91307d03..6335522e0 100644 --- a/src/SixLabors.Fonts/ShapedText.cs +++ b/src/SixLabors.Fonts/ShapedText.cs @@ -6,33 +6,74 @@ namespace SixLabors.Fonts; /// -/// Contains the width-independent result of shaping text before logical line composition. +/// The width-independent result of shaping text before logical line composition: +/// a run table holding run-constant state once, and parallel per-glyph info and +/// position arrays holding identities and pure numbers. The arrays are views over +/// pooled scratch storage and may exceed the live counts; the view is valid only +/// while the renting scope holds its scratch, so consumers copy what they retain. /// internal readonly struct ShapedText { /// /// Initializes a new instance of the struct. /// - /// The positioned glyph shaping collection. + /// The shaped run table. + /// The per-glyph identity records, parallel to . + /// The per-glyph geometry records, parallel to . + /// The number of live entries in the per-glyph arrays. /// The resolved bidi runs covering the shaped text. - /// The code point to bidi-run mapping built during shaping. + /// + /// The contiguous visual glyph range belonging to each entry in . + /// + /// The number of live bidi runs and glyph ranges. + /// + /// The code point index to bidi-run index mapping built during shaping. Entries for + /// code points no shaping pass visited hold -1. + /// /// The layout mode used while shaping. public ShapedText( - GlyphPositioningCollection positionings, + ShapedTextRun[] runs, + ShapedGlyphInfo[] infos, + ShapedGlyphPosition[] positions, + int glyphCount, BidiRun[] bidiRuns, - Dictionary bidiMap, + ShapedGlyphRange[] bidiGlyphRanges, + int bidiRunCount, + int[] bidiMap, LayoutMode layoutMode) { - this.Positionings = positionings; + this.Runs = runs; + this.Infos = infos; + this.Positions = positions; + this.GlyphCount = glyphCount; this.BidiRuns = bidiRuns; + this.BidiGlyphRanges = bidiGlyphRanges; + this.BidiRunCount = bidiRunCount; this.BidiMap = bidiMap; this.LayoutMode = layoutMode; } /// - /// Gets the positioned glyph shaping collection. + /// Gets the number of live entries in and . /// - public GlyphPositioningCollection Positionings { get; } + public int GlyphCount { get; } + + /// + /// Gets the shaped run table: run-constant state referenced per glyph by + /// . + /// + public ShapedTextRun[] Runs { get; } + + /// + /// Gets the per-glyph identity records in logical directional-run order and + /// visual order within each run. + /// + public ShapedGlyphInfo[] Infos { get; } + + /// + /// Gets the per-glyph geometry records, parallel to . + /// + public ShapedGlyphPosition[] Positions { get; } /// /// Gets the resolved bidi runs covering the shaped text. @@ -40,12 +81,230 @@ public ShapedText( public BidiRun[] BidiRuns { get; } /// - /// Gets the code point to bidi-run mapping built during shaping. + /// Gets the contiguous visual glyph range belonging to each resolved bidi run. /// - public Dictionary BidiMap { get; } + public ShapedGlyphRange[] BidiGlyphRanges { get; } + + /// + /// Gets the number of live entries in and + /// . + /// + public int BidiRunCount { get; } + + /// + /// Gets the code point index to bidi-run index mapping built during shaping, + /// indexed by code point position. Unvisited positions hold -1. + /// + public int[] BidiMap { get; } /// /// Gets the layout mode used while shaping. /// public LayoutMode LayoutMode { get; } + + /// + /// Gets the contiguous range of shaped glyphs at the given codepoint offset, along + /// with the aggregate shaping state of the range's non-placeholder entries. + /// + /// The zero-based index within the input codepoint collection. + /// + /// The bidi-run index used by the previous lookup. Updated to the run containing + /// . + /// + /// + /// The glyph index at which the next logical-source lookup starts. It advances + /// through left-to-right visual storage and retreats through right-to-left visual + /// storage. + /// + /// The index of the first matching glyph. + /// The number of matching glyphs. + /// The font size in PT units of the font containing the glyphs. + /// Whether the range is the result of a substitution. + /// Whether a vertical alternate feature changed any glyph in the range. + /// Whether the range is the result of a decomposition substitution. + /// + /// The next shaped source codepoint index in logical order, or + /// when the run contains no later shaped source. + /// + /// when at least one glyph matches the offset. + public bool TryGetGlyphsAtOffset( + int offset, + ref int searchBidiRunIndex, + ref int searchIndex, + out int start, + out int count, + out float pointSize, + out bool isSubstituted, + out bool isVerticalSubstitution, + out bool isDecomposed, + out int nextCodePointIndex) + { + ShapedGlyphInfo[] infos = this.Infos; + start = 0; + count = 0; + pointSize = 0; + isSubstituted = false; + isVerticalSubstitution = false; + isDecomposed = false; + nextCodePointIndex = int.MaxValue; + + int bidiRunIndex = Math.Max(searchBidiRunIndex, 0); + while (bidiRunIndex < this.BidiRunCount && offset >= this.BidiRuns[bidiRunIndex].End) + { + bidiRunIndex++; + } + + int rangeStart; + int rangeEnd; + bool readsRightToLeft; + if (bidiRunIndex < this.BidiRunCount + && offset >= this.BidiRuns[bidiRunIndex].Start) + { + ShapedGlyphRange range = this.BidiGlyphRanges[bidiRunIndex]; + rangeStart = range.Start; + rangeEnd = range.End; + readsRightToLeft = (this.BidiRuns[bidiRunIndex].Level & 1) != 0; + } + else + { + // Placeholders can sit at the exclusive end of all source text. They + // are not owned by a source bidi run, so they occupy the small tail + // after the final recorded run range and retain insertion order. + rangeStart = this.BidiRunCount > 0 + ? this.BidiGlyphRanges[this.BidiRunCount - 1].End + : 0; + + rangeEnd = this.GlyphCount; + readsRightToLeft = false; + bidiRunIndex = this.BidiRunCount; + } + + if (searchBidiRunIndex != bidiRunIndex) + { + // Browsers perform the same direction-aware source lookup over an + // already-visual glyph array. RTL source indices therefore run + // opposite the stored glyph order, while the returned glyph span + // itself remains visual. + searchBidiRunIndex = bidiRunIndex; + searchIndex = readsRightToLeft ? rangeEnd - 1 : rangeStart; + } + + if (readsRightToLeft) + { + int i = Math.Min(searchIndex, rangeEnd - 1); + while (i >= rangeStart) + { + int codePointIndex = infos[i].CodePointIndex; + if (codePointIndex < offset) + { + i--; + continue; + } + + if (codePointIndex > offset) + { + searchIndex = i; + return false; + } + + int end = i + 1; + while (i > rangeStart && infos[i - 1].CodePointIndex == offset) + { + i--; + } + + start = i; + count = end - i; + searchIndex = i - 1; + break; + } + } + else + { + int i = Math.Max(searchIndex, rangeStart); + while (i < rangeEnd) + { + int codePointIndex = infos[i].CodePointIndex; + if (codePointIndex < offset) + { + i++; + continue; + } + + if (codePointIndex > offset) + { + searchIndex = i; + return false; + } + + start = i; + int end = i + 1; + while (end < rangeEnd && infos[end].CodePointIndex == offset) + { + end++; + } + + count = end - i; + searchIndex = end; + break; + } + } + + if (count == 0) + { + return false; + } + + int matchEnd = start + count; + for (int i = start; i < matchEnd; i++) + { + ref readonly ShapedGlyphInfo info = ref infos[i]; + if (!info.IsPlaceholder) + { + isSubstituted = info.IsSubstituted; + isDecomposed = info.IsDecomposed; + isVerticalSubstitution |= info.IsVerticalSubstituted; + pointSize = this.Runs[info.RunIndex].PointSize; + } + } + + if (searchIndex >= rangeStart && searchIndex < rangeEnd) + { + nextCodePointIndex = infos[searchIndex].CodePointIndex; + } + + return true; + } +} + +/// +/// A half-open contiguous glyph range over the projected shaping arrays. +/// +internal readonly struct ShapedGlyphRange +{ + /// + /// Initializes a new instance of the struct. + /// + /// The zero-based index of the first glyph. + /// The number of glyphs in the range. + public ShapedGlyphRange(int start, int count) + { + this.Start = start; + this.Count = count; + } + + /// + /// Gets the zero-based index of the first glyph. + /// + public int Start { get; } + + /// + /// Gets the number of glyphs in the range. + /// + public int Count { get; } + + /// + /// Gets the index immediately after the final glyph. + /// + public int End => this.Start + this.Count; } diff --git a/src/SixLabors.Fonts/ShapedTextRun.cs b/src/SixLabors.Fonts/ShapedTextRun.cs new file mode 100644 index 000000000..2a113496c --- /dev/null +++ b/src/SixLabors.Fonts/ShapedTextRun.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; + +namespace SixLabors.Fonts; + +/// +/// Run-constant shaping state shared by a consecutive range of shaped glyphs: the +/// resolved font, its point size, the source text run, and, for placeholder entries, +/// the bidi run at the insertion point. Run-level state lives once here rather than +/// being repeated per glyph. +/// +internal readonly struct ShapedTextRun +{ + /// + /// Initializes a new instance of the struct. + /// + /// The font that resolved the run's glyphs. + /// The font size in PT units. + /// The source text run. + /// The bidi run assigned to a placeholder insertion point. + public ShapedTextRun(Font font, float pointSize, TextRun textRun, BidiRun bidiRun) + { + this.Font = font; + this.PointSize = pointSize; + this.TextRun = textRun; + this.BidiRun = bidiRun; + } + + /// + /// Gets the font that resolved the run's glyphs. + /// + public Font Font { get; } + + /// + /// Gets the font size in PT units. + /// + public float PointSize { get; } + + /// + /// Gets the source text run. + /// + public TextRun TextRun { get; } + + /// + /// Gets the bidi run assigned to a placeholder insertion point. + /// + public BidiRun BidiRun { get; } +} diff --git a/src/SixLabors.Fonts/ShapingBuffer.cs b/src/SixLabors.Fonts/ShapingBuffer.cs new file mode 100644 index 000000000..ee51c605c --- /dev/null +++ b/src/SixLabors.Fonts/ShapingBuffer.cs @@ -0,0 +1,2685 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; +using System.Runtime.CompilerServices; +using SixLabors.Fonts.Tables.AdvancedTypographic; +using SixLabors.Fonts.Unicode; + +namespace SixLabors.Fonts; + +/// +/// The shaping pipeline's glyph buffer. Glyph state lives in one flat array of +/// records with a parallel metrics stream seeded after +/// substitution, both mutated in place through interior references. Storage doubles on +/// demand, is truncated rather than released on reset, and so stays at the workload's +/// high-water mark across pooled shaping passes. +/// +/// +/// Mutation contract: write through the indexer expression +/// (buffer[i].GlyphId = x), which addresses storage directly. Binding an element +/// to a local without copies it and silently discards writes; a +/// convention test rejects such bindings. Interior references are invalidated by any +/// operation that inserts or removes glyphs. +/// +internal sealed class ShapingBuffer +{ + /// + /// The flat glyph storage. Only the first records are live; + /// records beyond the count are stale leftovers awaiting overwrite. + /// + private GlyphShapingData[] data = new GlyphShapingData[64]; + + /// + /// The metrics stream, parallel to . Entries are seeded by + /// and ; substitution-phase buffers + /// never populate it. + /// + private GlyphMetricsEntry[] metrics = new GlyphMetricsEntry[64]; + + /// + /// The positioning stream, parallel to : shaping bounds, + /// attachment links, and positioned and kerned marks. Seeded alongside the + /// metrics stream; keeping this state out of the glyph record keeps the record + /// narrow for the substitution walks that never touch it. + /// + private GlyphShapingPosition[] positions = new GlyphShapingPosition[64]; + + /// + /// Shaper scratch storage handed out by , grown + /// to the workload's high-water mark. Contents are undefined between passes. + /// + private byte[] shaperScratch = []; + + /// + /// The approximate membership filter over every glyph id the buffer has ever + /// contained. See for the growth contract. + /// + private GlyphSetDigest glyphDigest; + + /// + /// Validation tags for the direct-mapped glyph metrics cache. A slot's tag packs + /// every key field of the font's own metrics cache above a marker bit, so a hit is + /// one load and one compare. Zero marks a slot empty. + /// + private readonly ulong[] metricsCacheTags = new ulong[256]; + + /// + /// The resolved metrics for each slot of . + /// + private readonly FontGlyphMetrics?[] metricsCacheValues = new FontGlyphMetrics?[256]; + + /// + /// The font metrics instance the cache entries belong to. Seeding from a different + /// font clears the cache before use. + /// + private FontMetrics? metricsCacheOwner; + + /// + /// The bit offset of the encoded following codepoint in a glyph id cache entry. + /// + private const int GlyphIdCacheNextShift = 21; + + /// + /// The bit offset of the resolved glyph id in a glyph id cache entry. + /// + private const int GlyphIdCacheGlyphShift = 43; + + /// + /// The glyph id cache entry bit recording that the lookup found a glyph. + /// + private const ulong GlyphIdCacheFoundFlag = 1UL << 59; + + /// + /// The glyph id cache entry bit recording that the following codepoint was + /// consumed as part of a variation sequence. + /// + private const ulong GlyphIdCacheSkipFlag = 1UL << 60; + + /// + /// The glyph id cache entry bit distinguishing a populated slot from an empty + /// one, since a zero entry could otherwise read as a valid all-zero lookup. + /// + private const ulong GlyphIdCacheMarkerFlag = 1UL << 63; + + /// + /// The fraction slash, U+2044, which forms fractions from the digit runs + /// surrounding it. The solidus U+002F does not. + /// + private const uint FractionSlashCodePoint = 0x2044; + + /// + /// The lowest character that can begin a constrained vowel sequence, which + /// is the first Devanagari vowel letter. Text made only of characters below + /// it cannot contain such a sequence. + /// + private const uint FirstVowelConstraintCharacter = 0x0905; + + /// + /// The dotted circle, U+25CC, which stands in for a base a mark has no + /// valid one to attach to. + /// + private const int DottedCircleCodePoint = 0x25CC; + + /// + /// The multiplier used by the deterministic random sequence. + /// + private const uint RandomMultiplier = 48271; + + /// + /// The modulus used by the deterministic random sequence. + /// + private const uint RandomModulus = 2147483647; + + /// + /// The current deterministic random state. + /// + private uint randomState = 1; + + /// + /// The glyph id cache entry bits forming the lookup key: the marker, the + /// codepoint, and the encoded following codepoint. + /// + private const ulong GlyphIdCacheTagMask = GlyphIdCacheMarkerFlag | ((1UL << GlyphIdCacheGlyphShift) - 1); + + /// + /// Direct-mapped codepoint-to-glyph cache: one word per slot packing the lookup + /// key alongside the found flag, skip flag, and glyph id, so a repeat lookup is + /// one load and one masked compare. Zero marks a slot empty. + /// + private readonly ulong[] glyphIdCacheEntries = new ulong[256]; + + /// + /// The font metrics instance the glyph id cache entries belong to. Populating + /// from a different font clears the cache before use. + /// + private FontMetrics? glyphIdCacheOwner; + + /// + /// The bit offset of the packed class props word in a shaping class cache entry. + /// + private const int ShapingClassCachePropsShift = 16; + + /// + /// The shaping class cache entry bit distinguishing a populated slot from an + /// empty one, since a zero entry could otherwise read as a valid all-zero lookup. + /// + private const ulong ShapingClassCacheMarkerFlag = 1UL << 63; + + /// + /// The shaping class cache entry bits forming the lookup key: the marker and the + /// glyph id. + /// + private const ulong ShapingClassCacheTagMask = ShapingClassCacheMarkerFlag | ((1UL << ShapingClassCachePropsShift) - 1); + + /// + /// Direct-mapped glyph-id-to-class cache: one word per slot packing the glyph id + /// key alongside the packed class props word, so a repeat classification is one + /// load and one masked compare instead of a class definition table walk. Only + /// table-derived classes enter the cache; the codepoint fallback classification + /// depends on record state and stays out. Zero marks a slot empty. + /// + private readonly ulong[] shapingClassCacheEntries = new ulong[256]; + + /// + /// The font metrics instance the shaping class cache entries belong to. + /// Populating from a different font clears the cache before use. + /// + private FontMetrics? shapingClassCacheOwner; + + /// + /// The bidi runs recorded for inline placeholders, keyed by codepoint index. + /// Placeholder state lives here rather than on every glyph record because only + /// placeholders carry a bidi run of their own, and only the copy-out reads it. + /// + private readonly List<(int CodePointIndex, BidiRun Run)> placeholderBidiRuns = []; + + /// + /// Shape plans reused across segments and passes, keyed by script, script tag, + /// font, language, and effective feature list. Safe to reuse because the pooled + /// buffer is exclusively owned and a plan's per-segment shaper state is + /// reassigned at each pause invocation. Cleared when a reset changes an option + /// value captured by the plans. + /// + private readonly List<(ScriptClass Script, Tag ScriptTag, FontMetrics FontMetrics, string Language, IReadOnlyList FeatureTags, ShapePlan Plan)> planCache = new(4); + + /// + /// The language the cached language tags were resolved for. + /// + private string languageKey = string.Empty; + + /// + /// The feature tags the cached plans were built for. + /// + private IReadOnlyList? featureKey; + + /// + /// The layout mode the cached plans were built for. + /// + private LayoutMode layoutModeKey; + + /// + /// The kerning mode the cached plans were built for. + /// + private KerningMode kerningModeKey; + + /// + /// Whether tracking was enabled when the cached plans were built. + /// + private bool hasTrackingKey; + + /// + /// The output-side record storage for substitution passes. Allocated on the + /// first pass whose output grows past its read cursor and retained at the + /// workload's high-water mark afterwards; passes whose output never outgrows + /// the input keep writing into the primary storage and never touch this. + /// + private GlyphShapingData[] outData = []; + + /// + /// Whether the active pass's output has diverged into . + /// While false the output region aliases the head of the primary storage: + /// equal-length passes write every record onto itself and copy nothing, and + /// shrinking passes move records forward within the primary storage. + /// Divergence occurs only when output would overtake the read cursor. + /// + private bool passDiverged; + + /// + /// The depth of nested lookup application within contextual matches. Nested + /// replacements mutate the input side in place regardless of their type. + /// + private int nestedApplicationDepth; + + /// + /// Retained contextual-match positions, partitioned into one fixed-width slice + /// per nested lookup depth so a child lookup cannot overwrite its parent's + /// positions. The storage grows to the deepest level observed and is then reused. + /// + private int[] contextMatchPositions = []; + + /// + /// Whether the packed matcher flag bytes have been computed at least once; + /// until then the stamped lookup state always rebuilds them. + /// + private bool packedFlagsValid; + + /// + /// The role the packed matcher flag bytes were computed under; a role change + /// invalidates them. + /// + private ShapingBufferRole packedFlagsRole; + + /// + /// Whether the buffer held default ignorables when the packed matcher flag + /// bytes were computed; ignorables appearing invalidates them. + /// + private bool packedFlagsHadIgnorables; + + /// + /// Initializes a new instance of the class. + /// + /// The text options. + /// The shaping phase this buffer serves. + public ShapingBuffer(TextOptions textOptions, ShapingBufferRole role) + { + this.TextOptions = textOptions; + this.Role = role; + } + + /// + /// Gets the shaping phase this buffer serves. Shapers gate phase-specific work, + /// such as syllable analysis and reordering, on the substitution role. + /// + public ShapingBufferRole Role { get; private set; } + + /// + /// Gets the number of live glyph records. Substitution can leave this greater or + /// smaller than the input codepoint count. + /// + public int Count { get; private set; } + + /// + /// Gets the number of records already produced by the active pass; matching + /// walks backtrack context against these records, not the unconsumed input. + /// + public int PassOutputCount { get; private set; } + + /// + /// Gets a value indicating whether a substitution pass is active. + /// + public bool IsPassActive { get; private set; } + + /// + /// Gets or sets a value indicating whether the applying lookup consumes + /// records through the pass cursor. The substitution driver sets this for + /// top-level lookups whose replacements consume exactly the records they + /// match; contextual lookups leave it clear so their nested replacements + /// mutate the input side in place and the driver alone advances the cursor. + /// + public bool DirectConsume { get; set; } + + /// + /// Gets the read cursor of the active pass: the input-side position of the + /// next record to consume. + /// + public int ReadIndex { get; private set; } + + /// + /// Gets or sets a value indicating whether any record carries a default + /// ignorable codepoint. Recorded as records enter the buffer so the + /// hide-ignorables stage can skip plain text without a scan. + /// + public bool HasDefaultIgnorables { get; set; } + + /// + /// Gets or sets a value indicating whether any record carries the fraction + /// slash. Recorded as records enter the buffer so automatic fraction + /// forming costs nothing for the text that does not use it. + /// + public bool HasFractionSlash { get; set; } + + /// + /// Gets or sets a value indicating whether any record carries a character + /// that can begin a constrained vowel sequence. Recorded as records enter + /// the buffer so text that cannot contain one is never walked for it. + /// + public bool HasVowelConstraintCandidates { get; set; } + + /// + /// Gets the union of every feature bit enabled on any record, accumulated as + /// features are turned on. A lookup whose mask shares no bit with it cannot + /// match any record, so the drivers skip it without walking the buffer. The + /// union only ever grows within a pass, so it is a superset: it can cost a + /// walk that finds nothing, never skip a lookup that would have applied. + /// + public uint EnabledFeatureMaskUnion { get; private set; } = ShapePlanFeatures.GlobalFeatureMask; + + /// + /// Gets a value indicating whether the applying lookup skips the zero width + /// non-joiner during context matching instead of matching it. + /// + public bool LookupAutoZwnj { get; private set; } = true; + + /// + /// Gets a value indicating whether the applying lookup skips the zero width + /// joiner during sequence matching instead of matching it. + /// + public bool LookupAutoZwj { get; private set; } = true; + + /// + /// Gets a value indicating whether the applying lookup never matches across + /// syllable boundaries. + /// + public bool LookupPerSyllable { get; private set; } + + /// + /// Gets a value indicating whether the applying lookup selects random alternates. + /// + public bool LookupRandom { get; private set; } + + /// + /// Gets the applying lookup's combined mask. Nested lookups inside + /// contextual matches apply under the outer lookup's mask, so the stamped + /// value holds for the whole application. + /// + public uint LookupMask { get; private set; } = uint.MaxValue; + + /// + /// Gets the packed matcher flags for input sequence matching under the + /// applying lookup, precomputed when the lookup is stamped so every match + /// attempt copies them instead of re-deriving them. + /// + public byte InputMatchFlags { get; private set; } + + /// + /// Gets the packed matcher flags for backtrack and lookahead matching under + /// the applying lookup, precomputed when the lookup is stamped. + /// + public byte ContextMatchFlags { get; private set; } + + /// + /// Gets a value indicating whether lookup application is currently nested + /// inside a contextual match. Nested replacements never consume through the + /// pass cursor, whatever their type: the outer contextual owns the cursor. + /// + public bool IsNestedApplication => this.nestedApplicationDepth > 0; + + /// + /// Gets the number of records behind the pass position: those the pass has + /// produced while a pass is active, and those before the cursor otherwise. + /// Positions handed to are measured against this. + /// + public int PassBacktrackLength => this.IsPassActive ? this.PassOutputCount : this.ReadIndex; + + /// + /// Gets the number of records ahead of the pass position, still to be read. + /// + public int PassLookaheadLength => this.Count - this.ReadIndex; + + /// + /// Gets a value indicating whether a further nested lookup application would + /// exceed the maximum nesting depth. A font may chain contextual lookups + /// into each other without bound, so recursion is capped rather than trusted. + /// + public bool NestingLimitReached => this.nestedApplicationDepth >= AdvancedTypographicUtils.MaxNestingLevel; + + /// + /// Gets the text options used by this buffer. + /// + public TextOptions TextOptions { get; private set; } + + /// + /// Gets or sets a value indicating whether synthesized vertical origins follow + /// the public shaping contract rather than the browser layout contract. + /// + public bool UseShapingVerticalOrigin { get; set; } + + /// + /// Gets the approximate membership filter over every glyph id the buffer has ever + /// contained. The digest only grows: substituted-away ids remain, keeping a + /// definitive negative from sound while + /// lookups mutate the buffer mid-application. + /// + public GlyphSetDigest GlyphDigest => this.glyphDigest; + + /// + /// Gets or sets the running id of any ligature glyphs contained within this buffer. + /// + public int LigatureId { get; set; } = 1; + + /// + /// Gets the text runs covering the pass's input. Records store run indices into + /// this list; the substitution and positioning buffers of a pass must share one + /// list so the indices agree when records are seeded across buffers. + /// + public IReadOnlyList TextRuns { get; private set; } = Array.Empty(); + + /// + /// Gets the shaping segments recorded during substitution: each script segment's + /// final range, script, and the plan that shaped it. The in-place positioning + /// pass reuses these so one plan drives both tables; the list stays empty when + /// records were seeded across buffers and positioning must segment for itself. + /// + public List<(int Index, int Count, ScriptClass Script, ShapePlan Plan)> SegmentPlans { get; } = []; + + /// + /// Gets an interior reference to the glyph shaping data at the specified index. + /// The reference writes through to the buffer's storage and is invalidated by any + /// operation that inserts or removes glyphs. + /// + /// The zero-based index of the record to get. + /// The . + public ref GlyphShapingData this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this.data[index]; + } + + /// + /// Gets an interior reference to the metrics entry at the specified index. Valid + /// only after the buffer has been seeded by . + /// + /// The zero-based index of the entry to get. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref GlyphMetricsEntry MetricsAt(int index) => ref this.metrics[index]; + + /// + /// Gets an interior reference to the positioning entry at the specified index. + /// Valid only after the buffer has been seeded. + /// + /// The zero-based index of the entry to get. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref GlyphShapingPosition PositionAt(int index) => ref this.positions[index]; + + /// + /// Assigns the text runs for the pass. Must run on both of a pass's buffers + /// before any glyph is added, so record run indices resolve identically across + /// them. + /// + /// The resolved text runs covering the input. + public void SetTextRuns(IReadOnlyList textRuns) => this.TextRuns = textRuns; + + /// + /// Changes the shaping phase this buffer serves. The single-run fast path shapes + /// and seeds one buffer in place, flipping it from substitution to positioning + /// instead of copying every record into a second buffer. + /// + /// The shaping phase the buffer serves next. + public void SetRole(ShapingBufferRole role) => this.Role = role; + + /// + /// Seeds this buffer's metrics stream in place after substitution: fetches each + /// glyph's metrics from , clears the record's feature + /// registration for the positioning pass, and starts the shaping bounds from the + /// single-axis advance. Behaviorally the in-place equivalent of + /// without the cross-buffer record copy; valid only when the + /// buffer holds no placeholders. + /// + /// The font used to resolve metrics. + /// + /// when every mapped codepoint resolved a real glyph; + /// when fallback glyphs remain for a later font pass. + /// + public bool SeedMetricsInPlace(Font font) + { + bool hasFallBacks = false; + FontMetrics fontMetrics = font.FontMetrics; + LayoutMode layoutMode = this.TextOptions.LayoutMode; + ColorFontSupport colorFontSupport = this.TextOptions.ColorFontSupport; + + uint verticalMask = ShapePlanFeatures.VerticalFeatureMask; + + for (int i = 0; i < this.Count; i++) + { + ref GlyphShapingData slot = ref this.data[i]; + CodePoint codePoint = slot.CodePoint; + + TextRun textRun = this.TextRuns[slot.TextRunIndex]; + TextAttributes textAttributes = textRun.TextAttributes; + TextDecorations textDecorations = textRun.TextDecorations; + + bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(codePoint, layoutMode) + || (slot.AppliedFeatureMask & verticalMask) != 0; + + FontGlyphMetrics glyphMetrics = this.GetGlyphMetrics(fontMetrics, codePoint, slot.GlyphId, textAttributes, textDecorations, layoutMode, colorFontSupport); + + if (glyphMetrics.GlyphType == GlyphType.Fallback && !CodePoint.IsControl(codePoint)) + { + hasFallBacks = true; + } + + // Feature masks persist deliberately: the in-place positioning pass + // reuses the substitution pass's plan, whose registrations already cover + // the positioning features. + this.positions[i] = new(isVertical + ? new(0, 0, 0, glyphMetrics.AdvanceHeight) + : new(0, 0, glyphMetrics.AdvanceWidth, 0)); + + this.metrics[i] = new(font, font.Size, glyphMetrics); + } + + return !hasFallBacks; + } + + /// + /// Resets the buffer for reuse by a new shaping pass: adopts the new options, + /// re-resolves the language candidates, empties the digest, and truncates the glyph + /// count. Records are stored by value, so no per-record cleanup is required and + /// storage is retained at its high-water mark. + /// + /// The text options for the new pass. + public void Reset(TextOptions textOptions) + { + this.Count = 0; + this.LigatureId = 1; + this.randomState = 1; + this.EnabledFeatureMaskUnion = ShapePlanFeatures.GlobalFeatureMask; + this.glyphDigest = default; + this.placeholderBidiRuns.Clear(); + this.SegmentPlans.Clear(); + + // Cached plans and language tags captured option values when built, so what + // invalidates them is those values changing. A caller shaping run after run + // through one buffer hands the same options over each time with the members + // rewritten, so identity alone would never notice. Tracking only affects + // plan construction when it crosses zero; its magnitude is applied by + // layout and therefore does not belong in the shape-plan cache key. + string language = textOptions.Culture?.Name ?? string.Empty; + if (!ReferenceEquals(this.TextOptions, textOptions) + || !string.Equals(this.languageKey, language, StringComparison.Ordinal) + || !ReferenceEquals(this.featureKey, textOptions.FeatureTags) + || this.layoutModeKey != textOptions.LayoutMode + || this.kerningModeKey != textOptions.KerningMode + || this.hasTrackingKey != (textOptions.Tracking != 0)) + { + this.planCache.Clear(); + this.TextOptions = textOptions; + this.languageKey = language; + this.featureKey = textOptions.FeatureTags; + this.layoutModeKey = textOptions.LayoutMode; + this.kerningModeKey = textOptions.KerningMode; + this.hasTrackingKey = textOptions.Tracking != 0; + } + } + + /// + /// Removes all glyph records while keeping the pass-wide state, so a fresh font run + /// can populate the buffer without re-resolving options or language tags. + /// + public void Clear() + { + this.Count = 0; + this.LigatureId = 1; + this.EnabledFeatureMaskUnion = ShapePlanFeatures.GlobalFeatureMask; + this.HasDefaultIgnorables = false; + this.HasFractionSlash = false; + this.HasVowelConstraintCandidates = false; + this.placeholderBidiRuns.Clear(); + this.SegmentPlans.Clear(); + } + + /// + /// Sets the applying lookup's mask, joiner handling, and syllable scope for + /// the duration of its application. The drivers stamp this before each + /// merged lookup entry; sequence matching reads it through the skipping + /// iterator. + /// + /// The lookup's combined mask. + /// Whether the lookup skips the zero width non-joiner during context matching. + /// Whether the lookup skips the zero width joiner. + /// Whether alternate substitutions select randomly. + /// Whether matching is confined to one syllable. + public void SetLookupMatchState(uint mask, bool autoZwnj, bool autoZwj, bool random, bool perSyllable) + { + this.LookupMask = mask; + this.LookupRandom = random; + + // Consecutive lookups mostly share their joiner handling, so the packed + // bytes are only rebuilt when one of their five inputs actually changed. + if (this.packedFlagsValid + && autoZwnj == this.LookupAutoZwnj + && autoZwj == this.LookupAutoZwj + && perSyllable == this.LookupPerSyllable + && this.packedFlagsRole == this.Role + && this.packedFlagsHadIgnorables == this.HasDefaultIgnorables) + { + return; + } + + this.LookupAutoZwnj = autoZwnj; + this.LookupAutoZwj = autoZwj; + this.LookupPerSyllable = perSyllable; + this.packedFlagsValid = true; + this.packedFlagsRole = this.Role; + this.packedFlagsHadIgnorables = this.HasDefaultIgnorables; + this.InputMatchFlags = SkippingGlyphIterator.PackMatchFlags(this, false); + this.ContextMatchFlags = SkippingGlyphIterator.PackMatchFlags(this, true); + } + + /// + /// Advances and returns the deterministic random sequence used by alternate substitution. + /// + /// The next random value. + public uint NextRandomNumber() + { + this.randomState = unchecked(this.randomState * RandomMultiplier) % RandomModulus; + return this.randomState; + } + + /// + /// Gets shaper scratch storage of at least the given length, grown to the + /// workload's high-water mark and retained. Contents are undefined on entry; + /// callers write every slot they read. + /// + /// The capacity required. + /// The scratch storage; entries beyond the length are undefined. + public byte[] GetShaperScratch(int length) + { + if (this.shaperScratch.Length < length) + { + this.shaperScratch = new byte[Math.Max(length, Math.Max(64, this.shaperScratch.Length * 2))]; + } + + return this.shaperScratch; + } + + /// + /// Sets the glyph id at the specified index, recording the id in + /// . Callers outside the buffer must use this rather than + /// writing directly, which would leave the + /// digest unaware of the new id. + /// + /// The zero-based index of the record. + /// The glyph id to set. + public void SetGlyphId(int index, ushort glyphId) + { + this.glyphDigest.Add(glyphId); + this.data[index].GlyphId = glyphId; + } + + /// + /// Adds the shaping feature to every record in the given range. The caller + /// resolves the feature's mask bit once for the whole range: shaper plans + /// register each stage feature across the full run, so the per-glyph work must + /// be a single bitwise OR. + /// + /// The zero-based index of the first record. + /// The number of records in the range. + /// The feature to apply. + /// The feature's plan-assigned mask bit. + public void AddShapingFeatureRange(int index, int count, TagEntry feature, uint mask) + { + if (feature.Enabled) + { + this.EnabledFeatureMaskUnion |= mask; + } + + int end = index + count; + for (int i = index; i < end; i++) + { + ref GlyphShapingData item = ref this.data[i]; + item.RegisteredFeatureMask |= mask; + if (feature.Enabled) + { + item.FeatureMask |= mask; + } + } + } + + /// + /// Applies the plan's folded whole-segment feature masks in a single walk: + /// the registered and enabled bits every replayed planning pass would + /// otherwise add one feature range at a time. + /// + /// The zero-based index of the first record. + /// The number of records. + /// The fold of the registered feature bits. + /// The fold of the enabled feature bits. + public void AddShapingFeatureMasks(int index, int count, uint registeredMask, uint enabledMask) + { + this.EnabledFeatureMaskUnion |= enabledMask; + + int end = index + count; + for (int i = index; i < end; i++) + { + ref GlyphShapingData item = ref this.data[i]; + item.RegisteredFeatureMask |= registeredMask; + item.FeatureMask |= enabledMask; + } + } + + /// + /// Enables a previously added shaping feature by its plan-assigned mask bit. + /// + /// + /// Intersecting with the registered mask preserves the contract that enabling a + /// feature a shaper never added for this record is a no-op. + /// + /// The zero-based index of the record. + /// The feature's plan-assigned mask bit. + public void EnableShapingFeature(int index, uint mask) + { + this.EnabledFeatureMaskUnion |= mask; + ref GlyphShapingData item = ref this.data[index]; + item.FeatureMask |= item.RegisteredFeatureMask & mask; + } + + /// + /// Disables a previously added shaping feature by its plan-assigned mask bit. + /// + /// + /// An unassigned feature yields a zero mask whose complement clears nothing. + /// + /// The zero-based index of the record. + /// The feature's plan-assigned mask bit. + public void DisableShapingFeature(int index, uint mask) + { + ref GlyphShapingData item = ref this.data[index]; + item.FeatureMask &= ~mask; + } + + /// + /// Adds the glyph id and the codepoint it represents. + /// + /// The id of the glyph to add. + /// The codepoint the glyph represents. + /// The resolved text direction for the codepoint. + /// The index of the text run this glyph belongs to. + /// The zero-based index within the input codepoint buffer. + /// The zero-based char index in the original text. + /// The zero-based index of the grapheme the glyph belongs to. + public void AddGlyph(ushort glyphId, CodePoint codePoint, TextDirection direction, ushort textRunIndex, int codePointIndex, int stringIndex, int graphemeIndex) + { + this.glyphDigest.Add(glyphId); + ref GlyphShapingData slot = ref this.Append(); + slot = new(textRunIndex) + { + CodePointIndex = codePointIndex, + StringIndex = stringIndex, + GraphemeIndex = graphemeIndex, + CodePoint = codePoint, + Direction = direction, + GlyphId = glyphId, + }; + + // The render-as-whitespace carve-outs are default ignorables that fonts + // implement as regular spacing glyphs, such as the Hangul fillers; those + // keep their glyphs. The joiners and the substitution-visible ignorables + // (Mongolian free variation selectors, tag characters, the combining + // grapheme joiner) carry their own bits for the matcher's transparency + // rules. + uint value = (uint)codePoint.Value; + + // The fraction slash is the only trigger for automatic fraction + // forming; recording it here keeps that stage free for other text. + if (value == FractionSlashCodePoint) + { + this.HasFractionSlash = true; + } + + // Only a character that can begin a constrained vowel sequence makes the + // sequence worth looking for, and they all sit above this one. + if (value >= FirstVowelConstraintCharacter) + { + this.HasVowelConstraintCandidates = true; + } + + if (value >= 0x80 + && UnicodeUtility.IsDefaultIgnorableCodePoint(value) + && !UnicodeUtility.ShouldRenderWhiteSpaceOnly(codePoint)) + { + slot.IsDefaultIgnorable = true; + this.HasDefaultIgnorables = true; + + if (CodePoint.IsZeroWidthNonJoiner(codePoint)) + { + slot.IsZwnj = true; + } + else if (CodePoint.IsZeroWidthJoiner(codePoint)) + { + slot.IsZwj = true; + } + else if (value is (>= 0x180B and <= 0x180D) or 0x180F + or (>= 0xE0020 and <= 0xE007F) + or 0x034F) + { + // MONGOLIAN FREE VARIATION SELECTOR ONE..FOUR, TAG SPACE..CANCEL + // TAG, and COMBINING GRAPHEME JOINER: substitution must still see + // these, while positioning treats them as transparent. + slot.IsHiddenIgnorable = true; + } + } + } + + /// + /// Adds an atomic inline placeholder. + /// + /// The object replacement codepoint used for Unicode processing. + /// The resolved bidi run for the placeholder. + /// The index of the text run this placeholder belongs to. + /// The zero-based index within the input codepoint buffer. + public void AddPlaceholder(CodePoint codePoint, BidiRun bidiRun, ushort textRunIndex, int codePointIndex) + { + ref GlyphShapingData slot = ref this.Append(); + slot = new(textRunIndex) + { + CodePointIndex = codePointIndex, + CodePoint = codePoint, + Direction = (TextDirection)bidiRun.Direction, + GlyphId = 0, + IsPlaceholder = true, + }; + + this.placeholderBidiRuns.Add((codePointIndex, bidiRun)); + } + + /// + /// Gets the bidi run recorded for the placeholder at the given codepoint index. + /// Placeholders shape in isolated single-glyph runs, so their indices are stable + /// for the lifetime of the pass. + /// + /// The placeholder's zero-based codepoint index. + /// The recorded , or the default when none was recorded. + public BidiRun GetPlaceholderBidiRun(int codePointIndex) + { + List<(int CodePointIndex, BidiRun Run)> runs = this.placeholderBidiRuns; + for (int i = 0; i < runs.Count; i++) + { + if (runs[i].CodePointIndex == codePointIndex) + { + return runs[i].Run; + } + } + + return default; + } + + /// + /// Copies the placeholder bidi run recorded at the given codepoint index in + /// into this buffer, so seeding from a workspace + /// preserves placeholder bidi state without carrying it on every glyph record. + /// + /// The buffer to copy the recorded run from. + /// The placeholder's zero-based codepoint index. + public void CopyPlaceholderBidiRun(ShapingBuffer source, int codePointIndex) + => this.placeholderBidiRuns.Add((codePointIndex, source.GetPlaceholderBidiRun(codePointIndex))); + + /// + /// Moves the specified glyph and its original input indices to the specified + /// position. + /// + /// The index to move from. + /// The index to move to. + public void MoveGlyph(int fromIndex, int toIndex) + { + if (fromIndex == toIndex) + { + return; + } + + GlyphShapingData[] items = this.data; + GlyphShapingData moved = items[fromIndex]; + + if (fromIndex > toIndex) + { + Array.Copy(items, toIndex, items, toIndex + 1, fromIndex - toIndex); + } + else + { + Array.Copy(items, fromIndex + 1, items, fromIndex, toIndex - fromIndex); + } + + items[toIndex] = moved; + } + + /// + /// Assigns the earliest stored input starts to every record in a shaping range. + /// + /// The first record in the range. + /// The first record after the range. + public void CombineInputStarts(int startIndex, int endIndex) + { + GlyphShapingData[] items = this.data; + int sourceIndex = startIndex; + for (int i = startIndex + 1; i < endIndex; i++) + { + if (items[i].StringIndex < items[sourceIndex].StringIndex) + { + sourceIndex = i; + } + } + + int sourceStringIndex = items[sourceIndex].StringIndex; + int leadingStringIndex = items[startIndex].StringIndex; + int trailingStringIndex = items[endIndex - 1].StringIndex; + + // HarfBuzz extends a changed boundary across adjacent records that already + // share its old input start. This keeps every output copied from that input + // together when only part of it intersects the requested shaping range. + if (sourceStringIndex != trailingStringIndex) + { + while (endIndex < this.Count && items[endIndex].StringIndex == trailingStringIndex) + { + endIndex++; + } + } + + if (sourceStringIndex != leadingStringIndex) + { + while (startIndex > 0 && items[startIndex - 1].StringIndex == leadingStringIndex) + { + startIndex--; + } + } + + GlyphShapingData source = items[sourceIndex]; + for (int i = startIndex; i < endIndex; i++) + { + items[i].CodePointIndex = source.CodePointIndex; + items[i].StringIndex = source.StringIndex; + items[i].GraphemeIndex = source.GraphemeIndex; + } + } + + /// + /// Reverses the order of glyph records in the specified range. + /// + /// + /// The range is interpreted as half-open, from + /// (inclusive) to (exclusive). Both indices are clamped + /// to the valid range [0, ]. If the resulting range contains + /// fewer than two records, the method performs no action. + /// + /// The zero-based index at which to start reversing (inclusive). + /// The zero-based index at which to stop reversing (exclusive). + public void ReverseRange(int startIndex, int endIndex) + { + int s = Math.Max(0, Math.Min(startIndex, this.Count)); + int e = Math.Max(0, Math.Min(endIndex, this.Count)); + + if (e < s + 2) + { + return; + } + + // A record is its shaping state, its metrics, and its position, held in + // step across three arrays; all three move together or the record comes + // apart. + int length = e - s; + + Array.Reverse(this.data, s, length); + Array.Reverse(this.metrics, s, length); + Array.Reverse(this.positions, s, length); + } + + /// + /// Reverses the grapheme order in the specified range while preserving the + /// shaped glyph order within each grapheme. + /// + /// The zero-based index at which to start reversing (inclusive). + /// The zero-based index at which to stop reversing (exclusive). + public void ReverseGraphemeRange(int startIndex, int endIndex) + { + int start = Math.Max(0, Math.Min(startIndex, this.Count)); + int end = Math.Max(0, Math.Min(endIndex, this.Count)); + if (end < start + 2) + { + return; + } + + // Reversing all records puts the graphemes in the required order but also + // reverses every multi-glyph grapheme. Reverse each contiguous grapheme + // again to restore the glyph stream produced for that grapheme. + this.ReverseRange(start, end); + int graphemeStart = start; + while (graphemeStart < end) + { + int graphemeIndex = this.data[graphemeStart].GraphemeIndex; + int graphemeEnd = graphemeStart + 1; + while (graphemeEnd < end && this.data[graphemeEnd].GraphemeIndex == graphemeIndex) + { + graphemeEnd++; + } + + this.ReverseRange(graphemeStart, graphemeEnd); + graphemeStart = graphemeEnd; + } + } + + /// + /// Performs a stable sort of the glyph records by the comparison delegate. + /// + /// The start index. + /// The end index. + /// The comparison delegate. + public void Sort(int startIndex, int endIndex, Comparison comparer) + { + // The sorted ranges are typically small runs of marks or syllables, so a + // stable insertion sort avoids both allocation and general-purpose sort + // overhead. + GlyphShapingData[] items = this.data; + for (int i = startIndex + 1; i < endIndex; i++) + { + int j = i; + while (j > startIndex && comparer(items[j - 1], items[i]) > 0) + { + j--; + } + + if (j == i) + { + continue; + } + + this.CombineInputStarts(j, i + 1); + this.MoveGlyph(i, j); + } + } + + /// + /// Performs a 1:1 replacement of a glyph id at the given position. + /// + /// The zero-based index of the record to replace. + /// The replacement glyph id. + /// The feature to apply to the record at the specified index. + public void Replace(int index, ushort glyphId, Tag feature) + { + this.glyphDigest.Add(glyphId); + if (this.IsPassActive && this.DirectConsume && index == this.ReadIndex) + { + ref GlyphShapingData produced = ref this.ProduceFromCursor(); + produced.GlyphId = glyphId; + produced.LigatureId = 0; + produced.LigatureComponent = -1; + produced.IsSubstituted = true; + produced.AppliedFeatureMask |= ShapePlanFeatures.GetVerticalMask(feature); + return; + } + + ref GlyphShapingData current = ref this.data[index]; + current.GlyphId = glyphId; + current.LigatureId = 0; + current.LigatureComponent = -1; + current.IsSubstituted = true; + current.AppliedFeatureMask |= ShapePlanFeatures.GetVerticalMask(feature); + } + + /// + /// Performs a 1:1 replacement without changing the record's ligature attachment. + /// + /// The zero-based index of the record to replace. + /// The replacement glyph id. + /// The feature to apply to the record at the specified index. + public void ReplaceInPlace(int index, ushort glyphId, Tag feature) + { + this.glyphDigest.Add(glyphId); + + // Reverse substitutions change only the glyph identity. Attachment metadata + // must survive because later mark positioning still targets the same record. + ref GlyphShapingData current = ref this.data[index]; + current.GlyphId = glyphId; + current.IsSubstituted = true; + current.AppliedFeatureMask |= ShapePlanFeatures.GetVerticalMask(feature); + } + + /// + /// Performs a 1:1 replacement of a glyph id at the given position while removing a + /// series of records at the given positions within the sequence. + /// + /// The zero-based index of the record to replace. + /// The indices at which to remove records. + /// The replacement glyph id. + /// The ligature id. + /// The ligature component retained by a mark ligature, or -1 for a new ligature. + /// The feature to apply to the record at the specified index. + public void Replace(int index, ReadOnlySpan removalIndices, ushort glyphId, int ligatureId, int ligatureComponent, Tag feature) + { + if (!removalIndices.IsEmpty) + { + this.CombineInputStarts(index, removalIndices[^1] + 1); + } + + // Gather the merged codepoint bookkeeping from the component records + // before any of them move. + int codePointCount = 0; + CodePoint codePoint = default; + + for (int i = removalIndices.Length - 1; i >= 0; i--) + { + ref GlyphShapingData consumed = ref this.data[removalIndices[i]]; + + codePointCount += consumed.CodePointCount; + CodePoint currentCodePoint = consumed.CodePoint; + if (!UnicodeUtility.IsDefaultIgnorableCodePoint((uint)currentCodePoint.Value) || UnicodeUtility.ShouldRenderWhiteSpaceOnly(currentCodePoint)) + { + if (!CodePoint.IsZeroWidthJoiner(currentCodePoint) && !CodePoint.IsZeroWidthNonJoiner(currentCodePoint)) + { + // A visible matched component may identify the ligature for + // later Unicode-property fallbacks. Formatting controls must + // not replace that identity merely because the font consumed them. + codePoint = currentCodePoint; + } + } + } + + this.glyphDigest.Add(glyphId); + if (this.IsPassActive && this.DirectConsume && index == this.ReadIndex) + { + // Produce the ligature from the cursor, then stream the span it + // matched over: component records are consumed without output and + // everything between them, such as marks, is copied through. + ref GlyphShapingData produced = ref this.ProduceFromCursor(); + if (codePoint != default) + { + produced.CodePoint = codePoint; + } + + produced.CodePointCount += codePointCount; + produced.GlyphId = glyphId; + produced.LigatureId = ligatureId; + produced.IsLigated = true; + + // Only the most recent ligature/multiple transformation controls + // reordering decisions. Religation forgives an earlier expansion. + produced.IsDecomposed = false; + produced.LigatureComponent = ligatureComponent; + produced.IsSubstituted = true; + produced.AppliedFeatureMask |= ShapePlanFeatures.GetVerticalMask(feature); + + if (removalIndices.Length > 0) + { + int removal = 0; + int last = removalIndices[^1]; + for (int position = index + 1; position <= last; position++) + { + if (removal < removalIndices.Length && removalIndices[removal] == position) + { + this.SkipGlyph(); + removal++; + } + else + { + this.CopyGlyph(); + } + } + } + + return; + } + + for (int i = removalIndices.Length - 1; i >= 0; i--) + { + this.RemoveAt(removalIndices[i]); + } + + // Assign our new id at the index. The reference is taken after every removal + // so it addresses the record's final slot. + ref GlyphShapingData current = ref this.data[index]; + if (codePoint != default) + { + current.CodePoint = codePoint; + } + + current.CodePointCount += codePointCount; + current.GlyphId = glyphId; + current.LigatureId = ligatureId; + current.IsLigated = true; + + // Only the most recent ligature/multiple transformation controls + // reordering decisions. Religation forgives an earlier expansion. + current.IsDecomposed = false; + current.LigatureComponent = ligatureComponent; + current.IsSubstituted = true; + current.AppliedFeatureMask |= ShapePlanFeatures.GetVerticalMask(feature); + } + + /// + /// Performs a 1:1 replacement of a glyph id at the given position while removing a + /// series of following records. + /// + /// The zero-based index of the record to replace. + /// The number of following records to remove. + /// The replacement glyph id. + /// The feature to apply to the record at the specified index. + public void Replace(int index, int count, ushort glyphId, Tag feature) + { + if (count > 0) + { + this.CombineInputStarts(index, index + count + 1); + } + + // Gather the merged codepoint bookkeeping from the following records + // before any of them move. + int codePointCount = 0; + CodePoint codePoint = default; + + for (int i = count; i > 0; i--) + { + ref GlyphShapingData consumed = ref this.data[index + i]; + + codePointCount += consumed.CodePointCount; + CodePoint currentCodePoint = consumed.CodePoint; + if (!UnicodeUtility.IsDefaultIgnorableCodePoint((uint)currentCodePoint.Value) || UnicodeUtility.ShouldRenderWhiteSpaceOnly(currentCodePoint)) + { + if (!CodePoint.IsZeroWidthJoiner(currentCodePoint) && !CodePoint.IsZeroWidthNonJoiner(currentCodePoint)) + { + // Keep the last visible component as the replacement's + // Unicode identity; consumed formatting controls contribute + // to its text span but not to its shaping properties. + codePoint = currentCodePoint; + } + } + } + + this.glyphDigest.Add(glyphId); + if (this.IsPassActive && this.DirectConsume && index == this.ReadIndex) + { + // Produce the replacement from the cursor, then consume the + // contiguous following records without output. + ref GlyphShapingData produced = ref this.ProduceFromCursor(); + if (codePoint != default) + { + produced.CodePoint = codePoint; + } + + produced.CodePointCount += codePointCount; + produced.GlyphId = glyphId; + produced.LigatureId = 0; + produced.LigatureComponent = -1; + produced.IsSubstituted = true; + produced.AppliedFeatureMask |= ShapePlanFeatures.GetVerticalMask(feature); + + for (int i = 0; i < count; i++) + { + this.SkipGlyph(); + } + + return; + } + + // The consumed records are contiguous, so close their gap with one tail + // move. Removing them individually would move the same tail once per record. + this.RemoveRange(index + 1, count); + + // Assign our new id at the index. The reference is taken after every removal + // so it addresses the record's final slot. + ref GlyphShapingData current = ref this.data[index]; + if (codePoint != default) + { + current.CodePoint = codePoint; + } + + current.CodePointCount += codePointCount; + current.GlyphId = glyphId; + current.LigatureId = 0; + current.LigatureComponent = -1; + current.IsSubstituted = true; + current.AppliedFeatureMask |= ShapePlanFeatures.GetVerticalMask(feature); + } + + /// + /// Joins the record at into the one at + /// , which takes the given glyph, and removes it. The + /// two records need not sit next to each other: records between them keep both + /// their place and their order. + /// + /// + /// The joined record covers the text of both, so the codepoint-to-glyph + /// projection stays total. The caller sets the codepoint, because the character + /// the pair stands for is the caller's to name. + /// + /// The zero-based index of the record that remains. + /// The zero-based index of the record that is folded in. + /// The glyph the remaining record takes. + /// The feature to apply to the remaining record. + public void MergeGlyph(int index, int mergeIndex, ushort glyphId, Tag feature) + { + this.glyphDigest.Add(glyphId); + this.CombineInputStarts(index, mergeIndex + 1); + + GlyphShapingData merged = this.data[mergeIndex]; + int codePointCount = merged.CodePointCount; + + this.RemoveAt(mergeIndex); + + ref GlyphShapingData current = ref this.data[index]; + current.CodePointCount += codePointCount; + current.GlyphId = glyphId; + current.LigatureId = 0; + current.LigatureComponent = -1; + current.IsSubstituted = true; + current.AppliedFeatureMask |= ShapePlanFeatures.GetVerticalMask(feature); + } + + /// + /// Replaces a single glyph id with a buffer of glyph ids. + /// + /// The zero-based index of the record to replace. + /// The buffer of replacement glyph ids. + /// The feature to apply to the record at the specified index. + public void Replace(int index, ReadOnlySpan glyphIds, Tag feature) + { + if (this.IsPassActive && this.DirectConsume && index == this.ReadIndex) + { + if (glyphIds.Length == 0) + { + // Spec disallows removal of glyphs in this manner but it's common enough practice to allow it. + // https://github.com/MicrosoftDocs/typography-issues/issues/673 + this.SkipGlyph(); + return; + } + + ref GlyphShapingData first = ref this.ProduceFromCursor(); + bool preservesLigatureAttachment = first.LigatureId > 0; + first.GlyphId = glyphIds[0]; + if (!preservesLigatureAttachment) + { + // A free-standing expansion numbers its outputs as components. + // When the input is already attached to a ligature, every output + // must retain that existing attachment instead. + first.LigatureComponent = 0; + } + + first.IsSubstituted = true; + first.IsDecomposed = true; + this.glyphDigest.Add(glyphIds[0]); + + if (glyphIds.Length > 1) + { + // The produced record is captured by value as the template: the + // appends below may diverge or grow the output storage, which + // would invalidate a reference into it. + GlyphShapingData template = first; + uint mask = ShapePlanFeatures.GetVerticalMask(feature); + for (int i = 1; i < glyphIds.Length; i++) + { + // The expansion starts as an exact value copy. Assigning the new + // glyph through its property then invalidates only the class cache. + GlyphShapingData appended = template; + appended.GlyphId = glyphIds[i]; + + if (!preservesLigatureAttachment) + { + appended.LigatureComponent = i; + } + + appended.AppliedFeatureMask |= mask; + this.glyphDigest.Add(glyphIds[i]); + this.AppendOutputGlyph(in appended); + } + } + + return; + } + + if (glyphIds.Length > 0) + { + this.glyphDigest.Add(glyphIds[0]); + bool preservesLigatureAttachment = this.data[index].LigatureId > 0; + this.data[index].GlyphId = glyphIds[0]; + if (!preservesLigatureAttachment) + { + // Preserve an existing attachment across every output; only an + // unattached expansion creates new component indices. + this.data[index].LigatureComponent = 0; + } + + this.data[index].IsSubstituted = true; + this.data[index].IsDecomposed = true; + + // Add additional glyphs from the rest of the sequence. Insertion can grow + // the storage, so the mutated record is captured by value rather than held + // by reference. + if (glyphIds.Length > 1) + { + GlyphShapingData template = this.data[index]; + uint mask = ShapePlanFeatures.GetVerticalMask(feature); + int addedCount = glyphIds.Length - 1; + int insertionIndex = index + 1; + int tailCount = this.Count - insertionIndex; + + this.EnsureCapacity(this.Count + addedCount); + + // Open every output slot with one tail move. Repeated insertion would + // move the same suffix once for each decomposed glyph. + Array.Copy(this.data, insertionIndex, this.data, insertionIndex + addedCount, tailCount); + if (this.Role == ShapingBufferRole.Positioning) + { + Array.Copy(this.metrics, insertionIndex, this.metrics, insertionIndex + addedCount, tailCount); + Array.Copy(this.positions, insertionIndex, this.positions, insertionIndex + addedCount, tailCount); + Array.Clear(this.metrics, insertionIndex, addedCount); + Array.Clear(this.positions, insertionIndex, addedCount); + } + + for (int i = 0; i < addedCount; i++) + { + // Each opened slot starts from the complete first output. Assigning + // the glyph through its property invalidates only the class cache. + GlyphShapingData inserted = template; + inserted.GlyphId = glyphIds[i + 1]; + + if (!preservesLigatureAttachment) + { + inserted.LigatureComponent = i + 1; + } + + inserted.AppliedFeatureMask |= mask; + this.glyphDigest.Add(glyphIds[i + 1]); + this.data[insertionIndex + i] = inserted; + } + + this.Count += addedCount; + } + } + else + { + // Spec disallows removal of glyphs in this manner but it's common enough practice to allow it. + // https://github.com/MicrosoftDocs/typography-issues/issues/673 + this.RemoveAt(index); + } + } + + /// + /// Inserts the shaping data at the given index, adopting the slot's codepoint index. + /// + /// The zero-based index at which to insert. + /// The shaping data to insert. + public void Insert(int index, GlyphShapingData data) + { + data.CodePointIndex = this.data[index].CodePointIndex; + this.InsertAt(index, data); + } + + /// + /// Inserts a fully positioned glyph record while preserving its resolved + /// metrics and placement. This is used by post-position expansion, after the + /// three parallel streams have all become meaningful. + /// + /// The zero-based index at which to insert. + /// The shaping record to insert. + /// The resolved metrics to insert. + /// The positioned geometry to insert. + public void InsertPositioned(int index, in GlyphShapingData data, in GlyphMetricsEntry metricsEntry, in GlyphShapingPosition position) + { + this.EnsureCapacity(this.Count + 1); + + // Post-position insertion must shift and fill all three streams together; + // leaving even one at its old index would pair a glyph with another glyph's + // metrics or placement. + Array.Copy(this.data, index, this.data, index + 1, this.Count - index); + Array.Copy(this.metrics, index, this.metrics, index + 1, this.Count - index); + Array.Copy(this.positions, index, this.positions, index + 1, this.Count - index); + + this.data[index] = data; + this.metrics[index] = metricsEntry; + this.positions[index] = position; + this.Count++; + } + + /// + /// Seeds this buffer from a substituted workspace buffer: fetches each glyph's + /// metrics from , seeds the record's shaping bounds with the + /// single-axis advance so positioning starts from clean dirty-tracking, and appends + /// record and metrics entry. Placeholders receive synthetic metrics and skip glyph + /// lookup entirely. + /// + /// The font used to resolve metrics. + /// The substituted workspace buffer. + /// + /// when every mapped codepoint resolved a real glyph; + /// when fallback glyphs remain for a later font pass. + /// + public bool TryAdd(Font font, ShapingBuffer workspace) + { + bool hasFallBacks = false; + FontMetrics fontMetrics = font.FontMetrics; + LayoutMode layoutMode = this.TextOptions.LayoutMode; + ColorFontSupport colorFontSupport = this.TextOptions.ColorFontSupport; + + // The hide-ignorables stage runs against this buffer, so the workspace's + // knowledge of default ignorables must travel with its records. + this.HasDefaultIgnorables |= workspace.HasDefaultIgnorables; + this.HasFractionSlash |= workspace.HasFractionSlash; + this.HasVowelConstraintCandidates |= workspace.HasVowelConstraintCandidates; + + uint verticalMask = ShapePlanFeatures.VerticalFeatureMask; + + for (int i = 0; i < workspace.Count; i++) + { + ref GlyphShapingData source = ref workspace.data[i]; + CodePoint codePoint = source.CodePoint; + ushort id = source.GlyphId; + + if (source.IsPlaceholder) + { + // Placeholders are synthetic glyphs: they need layout metrics but must not + // go through font glyph lookup, fallback resolution, or GPOS positioning. + this.CopyPlaceholderBidiRun(workspace, source.CodePointIndex); + FontGlyphMetrics placeholderMetrics = PlaceholderGlyphMetrics.Create(font, this.TextRuns[source.TextRunIndex], this.TextOptions.Dpi); + + this.glyphDigest.Add(placeholderMetrics.GlyphId); + ref GlyphShapingData placeholderSlot = ref this.Append(); + placeholderSlot = source; + placeholderSlot.ClearFeatures(); + this.positions[this.Count - 1] = new(layoutMode.IsVertical() + ? new(0, 0, 0, placeholderMetrics.AdvanceHeight) + : new(0, 0, placeholderMetrics.AdvanceWidth, 0)) + { + IsPositioned = true, + }; + + this.metrics[this.Count - 1] = new(font, font.Size, placeholderMetrics); + continue; + } + + TextRun sourceRun = this.TextRuns[source.TextRunIndex]; + TextAttributes textAttributes = sourceRun.TextAttributes; + TextDecorations textDecorations = sourceRun.TextDecorations; + + bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(codePoint, layoutMode) + || (source.AppliedFeatureMask & verticalMask) != 0; + + FontGlyphMetrics glyphMetrics = this.GetGlyphMetrics(fontMetrics, codePoint, id, textAttributes, textDecorations, layoutMode, colorFontSupport); + + if (glyphMetrics.GlyphType == GlyphType.Fallback && !CodePoint.IsControl(codePoint)) + { + hasFallBacks = true; + } + + // We only want a single dimensional advance for positioning; assigning a + // fresh bounds value starts dirty tracking clean for GPOS. + this.glyphDigest.Add(glyphMetrics.GlyphId); + ref GlyphShapingData slot = ref this.Append(); + slot = source; + slot.ClearFeatures(); + this.positions[this.Count - 1] = new(isVertical + ? new(0, 0, 0, glyphMetrics.AdvanceHeight) + : new(0, 0, glyphMetrics.AdvanceWidth, 0)); + + this.metrics[this.Count - 1] = new(font, font.Size, glyphMetrics); + } + + return !hasFallBacks; + } + + /// + /// Replaces fallback glyphs in this buffer with glyphs shaped by a fallback font. + /// Each surviving workspace codepoint index supersedes the source interval up to + /// the next surviving index, including records consumed by substitution. + /// + /// The fallback font used to resolve metrics. + /// The substituted workspace buffer for the fallback font. + /// + /// when no fallback glyphs remain; + /// when further font passes are required. + /// + public bool TryUpdate(Font font, ShapingBuffer workspace) + { + // The fallback font supplies outlines and font tables, while layout mode and + // color policy remain properties of the destination shaping operation. + FontMetrics fontMetrics = font.FontMetrics; + LayoutMode layoutMode = this.TextOptions.LayoutMode; + ColorFontSupport colorFontSupport = this.TextOptions.ColorFontSupport; + bool hasFallBacks = false; + + // The hide-ignorables stage runs against this buffer, so the workspace's + // knowledge of default ignorables must travel with its records. + this.HasDefaultIgnorables |= workspace.HasDefaultIgnorables; + this.HasFractionSlash |= workspace.HasFractionSlash; + this.HasVowelConstraintCandidates |= workspace.HasVowelConstraintCandidates; + + uint verticalMask = ShapePlanFeatures.VerticalFeatureMask; + + for (int i = 0; i < this.Count;) + { + if (this.metrics[i].Metrics.GlyphType != GlyphType.Fallback) + { + // A primary or earlier fallback font already resolved this record. + // Later fallback passes must not replace a successful choice. + i++; + continue; + } + + // Fallback fonts inherit the point size of the unresolved destination + // record. Their Font instance identifies the face, but an explicit text + // run can have a different size from the options' default font. + int codePointIndex = this.data[i].CodePointIndex; + float pointSize = this.metrics[i].PointSize; + + // Shaping preserves ascending source codepoint indices even when substitution + // changes the number of glyphs. Locate the contiguous replacement group + // directly in the workspace instead of allocating a temporary list. + int replacementStart = 0; + while (replacementStart < workspace.Count && workspace.data[replacementStart].CodePointIndex < codePointIndex) + { + replacementStart++; + } + + int replacementEnd = replacementStart; + while (replacementEnd < workspace.Count && workspace.data[replacementEnd].CodePointIndex == codePointIndex) + { + replacementEnd++; + } + + if (replacementStart == replacementEnd) + { + // The fallback shaping result has no glyph at this source codepoint + // index and no earlier result in this pass claimed it. Leave the + // destination record available for the next configured fallback font. + hasFallBacks = true; + i++; + continue; + } + + // A substitution can consume later source records into the glyphs at + // this codepoint index. The next surviving index marks the end of that + // source interval; replacing only the first record would leave the + // consumed primary-font .notdef records visible. + int replacementLimit = replacementEnd < workspace.Count + ? workspace.data[replacementEnd].CodePointIndex + : int.MaxValue; + + // Validate the whole replacement group before mutating the destination. + // Installing only the glyphs this font can draw would mix independently + // shaped fragments and destroy the substitution result. Controls retain + // the established exception because their fallback metrics are layout + // placeholders rather than visible missing-glyph boxes. + bool replacementsComplete = true; + for (int j = replacementStart; j < replacementEnd; j++) + { + ref GlyphShapingData shape = ref workspace.data[j]; + TextRun shapeRun = this.TextRuns[shape.TextRunIndex]; + FontGlyphMetrics glyphMetrics = this.GetGlyphMetrics( + fontMetrics, + shape.CodePoint, + shape.GlyphId, + shapeRun.TextAttributes, + shapeRun.TextDecorations, + layoutMode, + colorFontSupport); + + if (glyphMetrics.GlyphType == GlyphType.Fallback && !CodePoint.IsControl(shape.CodePoint)) + { + replacementsComplete = false; + break; + } + } + + if (!replacementsComplete) + { + // Keep the original group intact so another fallback font can try the + // same source interval as one shaping unit. + hasFallBacks = true; + i++; + continue; + } + + // Multiple glyphs can survive at the same source codepoint index. Walk back to + // include every primary-font result at the replacement boundary, then + // extend through indices consumed by the fallback substitution. + int destinationStart = i; + while (destinationStart > 0 && this.data[destinationStart - 1].CodePointIndex == codePointIndex) + { + destinationStart--; + } + + int destinationEnd = destinationStart; + while (destinationEnd < this.Count && this.data[destinationEnd].CodePointIndex < replacementLimit) + { + destinationEnd++; + } + + // Remove backwards so each deletion cannot change the indices of records + // still awaiting removal. RemoveAt keeps shaping data, metrics, and + // positions aligned across their parallel arrays. + for (int j = destinationEnd - 1; j >= destinationStart; j--) + { + this.RemoveAt(j); + } + + int replacementCount = 0; + for (int j = replacementStart; j < replacementEnd; j++) + { + GlyphShapingData shape = workspace.data[j]; + CodePoint codePoint = shape.CodePoint; + TextRun shapeRun = this.TextRuns[shape.TextRunIndex]; + + // The validation pass primed the direct-mapped metrics cache, so this + // second lookup retrieves the value needed for insertion without a + // second font-table or dictionary lookup. + FontGlyphMetrics glyphMetrics = this.GetGlyphMetrics( + fontMetrics, + codePoint, + shape.GlyphId, + shapeRun.TextAttributes, + shapeRun.TextDecorations, + layoutMode, + colorFontSupport); + + bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(codePoint, layoutMode) + || (shape.AppliedFeatureMask & verticalMask) != 0; + + // Substitution masks belong to the temporary workspace plan. The + // destination retains the substitution result but positioning starts + // with clean feature state for the fallback font's positioning plan. + shape.ClearFeatures(); + + this.glyphDigest.Add(glyphMetrics.GlyphId); + this.InsertAt(destinationStart + replacementCount, shape, new(font, pointSize, glyphMetrics)); + + // Positioning begins from the fallback glyph's natural advance on the + // active layout axis. Offsets and cross-axis adjustments remain zero + // until the fallback font's positioning tables run. + this.positions[destinationStart + replacementCount] = new(isVertical + ? new(0, 0, 0, glyphMetrics.AdvanceHeight) + : new(0, 0, glyphMetrics.AdvanceWidth, 0)); + replacementCount++; + } + + // Continue after the inserted group. Reexamining it would treat neither + // its resolved metrics nor its workspace offsets as new fallback work. + i = destinationStart + replacementCount; + } + + return !hasFallBacks; + } + + /// + /// Resolves a glyph id through a direct-mapped cache in front of the font's own + /// resolver, which hashes a dictionary per lookup. A hit is one load and one + /// masked compare. No synchronization is needed: a pooled buffer is exclusively + /// owned for the duration of a shaping pass. + /// + /// The font metrics to resolve against. + /// The codepoint to look up. + /// The optional following codepoint for variation sequence matching. + /// When this method returns, contains the glyph id if found. + /// When this method returns, indicates whether the following codepoint was consumed. + /// if a glyph was found. + public bool TryGetGlyphId(FontMetrics fontMetrics, CodePoint codePoint, CodePoint? nextCodePoint, out ushort glyphId, out bool skipNextCodePoint) + { + if (!ReferenceEquals(this.glyphIdCacheOwner, fontMetrics)) + { + Array.Clear(this.glyphIdCacheEntries); + this.glyphIdCacheOwner = fontMetrics; + } + + ulong tag = GlyphIdCacheMarkerFlag + | (uint)codePoint.Value + | ((ulong)(uint)((nextCodePoint?.Value + 1) ?? 0) << GlyphIdCacheNextShift); + + int slot = codePoint.Value & 0xFF; + ulong entry = this.glyphIdCacheEntries[slot]; + if ((entry & GlyphIdCacheTagMask) == tag) + { + glyphId = (ushort)(entry >> GlyphIdCacheGlyphShift); + skipNextCodePoint = (entry & GlyphIdCacheSkipFlag) != 0; + return (entry & GlyphIdCacheFoundFlag) != 0; + } + + bool found = fontMetrics.TryGetGlyphId(codePoint, nextCodePoint, out glyphId, out skipNextCodePoint); + this.glyphIdCacheEntries[slot] = tag + | ((ulong)glyphId << GlyphIdCacheGlyphShift) + | (found ? GlyphIdCacheFoundFlag : 0) + | (skipNextCodePoint ? GlyphIdCacheSkipFlag : 0); + return found; + } + + /// + /// Looks up a table-derived shaping class through a direct-mapped cache in front + /// of the font's class definition tables, whose walks bisect range records per + /// query. A hit is one load and one masked compare. No synchronization is needed: + /// a pooled buffer is exclusively owned for the duration of a shaping pass. + /// + /// The font metrics the class belongs to. + /// The glyph id to look up. + /// When this method returns, contains the cached class if found. + /// if a cached class was found. + public bool TryGetShapingClass(FontMetrics fontMetrics, ushort glyphId, out GlyphShapingClass shapingClass) + { + if (!ReferenceEquals(this.shapingClassCacheOwner, fontMetrics)) + { + Array.Clear(this.shapingClassCacheEntries); + this.shapingClassCacheOwner = fontMetrics; + } + + ulong entry = this.shapingClassCacheEntries[glyphId & 0xFF]; + if ((entry & ShapingClassCacheTagMask) == (ShapingClassCacheMarkerFlag | glyphId)) + { + shapingClass = new((ushort)(entry >> ShapingClassCachePropsShift)); + return true; + } + + shapingClass = default; + return false; + } + + /// + /// Stores a table-derived shaping class in the direct-mapped class cache. Must + /// only be called for classes computed purely from the font's class definition + /// tables, after has established the cache + /// owner for the same font. + /// + /// The glyph id the class was computed for. + /// The computed class. + public void SetShapingClass(ushort glyphId, GlyphShapingClass shapingClass) + => this.shapingClassCacheEntries[glyphId & 0xFF] = ShapingClassCacheMarkerFlag + | glyphId + | ((ulong)shapingClass.Props << ShapingClassCachePropsShift); + + /// + /// Gets a shaper for the given script and font, reusing a cached instance when + /// one was built for the same key. Plans persist across passes while the options + /// instance is unchanged, so steady-state shaping builds no plans and constructs + /// no shapers. Plans whose resolution depends on live variation coordinates are + /// rebuilt every call and never cached. + /// + /// The script class to shape. + /// The resolved OpenType script tag. + /// The font metrics the plan binds to. + /// The culture whose language system the plan selects. + /// The effective additional feature tags. + /// The . + public ShapePlan GetOrCreatePlan(ScriptClass script, Tag unicodeScriptTag, FontMetrics fontMetrics, CultureInfo culture, IReadOnlyList featureTags) + { + // The plan carries the language system the font's features were selected + // through, so a plan built for one language cannot stand in for another. + string language = culture.Name; + + // The itemizer gives adjacent records with equivalent effective features + // one shared list instance. Reference comparison therefore keeps this hot + // cache lookup allocation-free and avoids enumerating tags per segment. + List<(ScriptClass Script, Tag ScriptTag, FontMetrics FontMetrics, string Language, IReadOnlyList FeatureTags, ShapePlan Plan)> cache = this.planCache; + for (int i = 0; i < cache.Count; i++) + { + (ScriptClass cachedScript, Tag cachedTag, FontMetrics cachedMetrics, string cachedLanguage, IReadOnlyList cachedFeatures, ShapePlan cachedPlan) = cache[i]; + if (cachedScript == script + && cachedTag == unicodeScriptTag + && ReferenceEquals(cachedMetrics, fontMetrics) + && string.Equals(cachedLanguage, language, StringComparison.Ordinal) + && ReferenceEquals(cachedFeatures, featureTags)) + { + return cachedPlan; + } + } + + Tag[] languageTags = ResolveLanguageTags(culture); + ShapePlan plan = ShapePlan.Build(fontMetrics, script, unicodeScriptTag, this.TextOptions, featureTags, languageTags); + if (plan.IsCacheable) + { + // Variation-dependent plans opt out because their resolved lookups can + // change without any managed cache-key value changing. + cache.Add((script, unicodeScriptTag, fontMetrics, language, featureTags, plan)); + } + + return plan; + } + + /// + /// Resolves glyph metrics through a direct-mapped cache in front of the font's own + /// resolver. The tag packs the same key fields the font's cache hashes, so a hit + /// replaces a dictionary probe with one load and one compare. No synchronization is + /// needed: a pooled buffer is exclusively owned for the duration of a shaping pass. + /// + /// The font metrics to resolve against. + /// The code point represented by the glyph. + /// The glyph id. + /// The text attributes applied to the glyph. + /// The text decorations applied to the glyph. + /// The layout mode. + /// The color font support level. + /// The resolved . + private FontGlyphMetrics GetGlyphMetrics( + FontMetrics fontMetrics, + CodePoint codePoint, + ushort glyphId, + TextAttributes textAttributes, + TextDecorations textDecorations, + LayoutMode layoutMode, + ColorFontSupport colorFontSupport) + { + if (!ReferenceEquals(this.metricsCacheOwner, fontMetrics)) + { + Array.Clear(this.metricsCacheTags); + this.metricsCacheOwner = fontMetrics; + } + + bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(codePoint, layoutMode); + ulong tag = (1UL << 63) + | (uint)codePoint.Value + | ((ulong)glyphId << 21) + | ((ulong)(uint)textAttributes << 37) + | ((ulong)(uint)colorFontSupport << 45) + | ((isVertical ? 1UL : 0UL) << 49); + + int slot = glyphId & 0xFF; + if (this.metricsCacheTags[slot] == tag) + { + return this.metricsCacheValues[slot]!; + } + + FontGlyphMetrics glyphMetrics = fontMetrics.GetGlyphMetrics(codePoint, glyphId, textAttributes, textDecorations, layoutMode, colorFontSupport); + this.metricsCacheTags[slot] = tag; + this.metricsCacheValues[slot] = glyphMetrics; + return glyphMetrics; + } + + /// + /// Marks the glyph at the specified index as positioned. Positions accumulate in + /// the position entry's shaping bounds and are read from there by consumers, so + /// the shared metrics instance is never mutated. + /// + /// The zero-based index of the record. + public void UpdatePosition(int index) => this.positions[index].IsPositioned = true; + + /// + /// Adds dx and dy to the positioned advance of the glyph at the given index and id. + /// Advances accumulate in the position entry's shaping bounds so the shared + /// metrics instance is never mutated. + /// + /// The font face with metrics. + /// The zero-based index of the record. + /// The id of the glyph to offset. + /// The delta x-advance. + /// The delta y-advance. + public void Advance(FontMetrics fontMetrics, int index, ushort glyphId, short dx, short dy) + { + FontGlyphMetrics m = this.metrics[index].Metrics; + if (m.GlyphId != glyphId || fontMetrics != m.FontMetrics) + { + return; + } + + bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(m.CodePoint, this.TextOptions.LayoutMode) + || (this.data[index].AppliedFeatureMask & ShapePlanFeatures.VerticalFeatureMask) != 0; + + // Advance heights grow downward but font-space grows upward, hence the negation. + this.positions[index].Bounds.Width += dx; + if (isVertical) + { + this.positions[index].Bounds.Height -= dy; + } + } + + /// + /// Returns a value indicating whether the record at the given index should be + /// processed by the given font's positioning pass. + /// + /// The font face with metrics. + /// The zero-based index of the record. + /// if the record should be processed. + public bool ShouldProcess(FontMetrics fontMetrics, int index) + => !this.positions[index].IsPositioned && this.metrics[index].Metrics.FontMetrics == fontMetrics; + + /// + /// Resolves the candidate OpenType language system tags for a culture. The + /// invariant culture expresses no language preference. + /// + /// The culture to resolve. + /// The candidate tags, most specific first. + private static Tag[] ResolveLanguageTags(CultureInfo culture) + => OpenTypeLanguageTagMap.TryGetTags(culture, out Tag[] tags) ? tags : []; + + /// + /// Appends one record and returns an interior reference to it. The slot may hold a + /// stale record from an earlier pass; callers overwrite it entirely. The metrics + /// slot is grown in lockstep but left untouched. + /// + /// The appended record. + private ref GlyphShapingData Append() + { + if (this.Count == this.data.Length) + { + Array.Resize(ref this.data, this.data.Length * 2); + Array.Resize(ref this.metrics, this.metrics.Length * 2); + Array.Resize(ref this.positions, this.positions.Length * 2); + } + + return ref this.data[this.Count++]; + } + + /// + /// Inserts one record at the given index, shifting later records right. + /// + /// The zero-based index at which to insert. + /// The record to insert. + private void InsertAt(int index, GlyphShapingData item) => this.InsertAt(index, item, default); + + /// + /// Inserts one record and its metrics entry at the given index, shifting later + /// entries right. The metrics and positioning streams shift only on a positioning + /// buffer: substitution-phase edits precede stream seeding, so their contents are + /// undefined and copying them would be pure waste per edit. + /// + /// The zero-based index at which to insert. + /// The record to insert. + /// The metrics entry to insert. + private void InsertAt(int index, GlyphShapingData item, GlyphMetricsEntry metricsEntry) + { + if (this.Count == this.data.Length) + { + Array.Resize(ref this.data, this.data.Length * 2); + Array.Resize(ref this.metrics, this.metrics.Length * 2); + Array.Resize(ref this.positions, this.positions.Length * 2); + } + + Array.Copy(this.data, index, this.data, index + 1, this.Count - index); + if (this.Role == ShapingBufferRole.Positioning) + { + Array.Copy(this.metrics, index, this.metrics, index + 1, this.Count - index); + Array.Copy(this.positions, index, this.positions, index + 1, this.Count - index); + this.positions[index] = default; + } + + this.data[index] = item; + this.metrics[index] = metricsEntry; + this.Count++; + } + + /// + /// Removes the record at the given index, shifting later entries left. The + /// metrics and positioning streams shift only on a positioning buffer, matching + /// the insertion contract. Stale entries beyond the count are overwritten by + /// later appends. + /// + /// The zero-based index to remove at. + private void RemoveAt(int index) + { + Array.Copy(this.data, index + 1, this.data, index, this.Count - index - 1); + if (this.Role == ShapingBufferRole.Positioning) + { + Array.Copy(this.metrics, index + 1, this.metrics, index, this.Count - index - 1); + Array.Copy(this.positions, index + 1, this.positions, index, this.Count - index - 1); + } + + this.Count--; + } + + /// + /// Removes a contiguous range of records, shifting the remaining tail once and + /// keeping the parallel positioning streams aligned. + /// + /// The zero-based index of the first record to remove. + /// The number of records to remove. + private void RemoveRange(int index, int count) + { + int tailCount = this.Count - index - count; + Array.Copy(this.data, index + count, this.data, index, tailCount); + if (this.Role == ShapingBufferRole.Positioning) + { + Array.Copy(this.metrics, index + count, this.metrics, index, tailCount); + Array.Copy(this.positions, index + count, this.positions, index, tailCount); + } + + this.Count -= count; + } + + /// + /// Deletes every record matching the filter in one forward compaction pass, + /// keeping the parallel streams aligned. A deleted record's codepoint coverage + /// folds into the preceding kept record, or into the next kept record when + /// nothing precedes it, so the codepoint-to-glyph projection stays total. + /// + /// The predicate selecting records to delete. + public void DeleteGlyphsInPlace(Func filter) + { + bool positioning = this.Role == ShapingBufferRole.Positioning; + int kept = 0; + int pendingCodePointIndex = -1; + int pendingStringIndex = -1; + int pendingGraphemeIndex = -1; + int pendingCodePointCount = 0; + for (int i = 0; i < this.Count; i++) + { + if (filter(this.data[i])) + { + ref GlyphShapingData deleted = ref this.data[i]; + if (kept > 0 && deleted.Direction != TextDirection.RightToLeft) + { + int previousCodePointIndex = this.data[kept - 1].CodePointIndex; + int previousStringIndex = this.data[kept - 1].StringIndex; + if (deleted.CodePointIndex < previousCodePointIndex) + { + // One input codepoint can produce several adjacent glyphs. + // Update every glyph carrying the old index so none of the + // surviving output disagrees about the combined input. + for (int j = kept - 1; j >= 0 && this.data[j].CodePointIndex == previousCodePointIndex; j--) + { + this.data[j].CodePointIndex = deleted.CodePointIndex; + this.data[j].GraphemeIndex = Math.Min(this.data[j].GraphemeIndex, deleted.GraphemeIndex); + } + } + + if (deleted.StringIndex >= 0 && deleted.StringIndex < previousStringIndex) + { + // The char index travels with glyph records. Update every + // glyph representing the preceding input before compaction + // removes the record that supplied the earlier index. + for (int j = kept - 1; j >= 0 && this.data[j].StringIndex == previousStringIndex; j--) + { + this.data[j].StringIndex = deleted.StringIndex; + } + } + + this.data[kept - 1].CodePointCount += deleted.CodePointCount; + } + else + { + // The buffer remains logical until layout reorders it. For a + // right-to-left run, the next logical glyph is the preceding + // glyph visually and therefore receives the deleted input. + pendingCodePointIndex = pendingCodePointIndex < 0 + ? deleted.CodePointIndex + : Math.Min(pendingCodePointIndex, deleted.CodePointIndex); + pendingStringIndex = pendingStringIndex < 0 + ? deleted.StringIndex + : Math.Min(pendingStringIndex, deleted.StringIndex); + pendingGraphemeIndex = pendingGraphemeIndex < 0 + ? deleted.GraphemeIndex + : Math.Min(pendingGraphemeIndex, deleted.GraphemeIndex); + pendingCodePointCount += deleted.CodePointCount; + } + + continue; + } + + if (pendingCodePointCount > 0) + { + int nextCodePointIndex = this.data[i].CodePointIndex; + int nextStringIndex = this.data[i].StringIndex; + int nextGraphemeIndex = this.data[i].GraphemeIndex; + + // The following input can already be represented by several glyphs. + // Update every sibling before compaction so deleting a leading + // record cannot leave part of that input with the later indices. + for (int j = i; j < this.Count && this.data[j].CodePointIndex == nextCodePointIndex; j++) + { + this.data[j].CodePointIndex = Math.Min(this.data[j].CodePointIndex, pendingCodePointIndex); + } + + if (pendingStringIndex >= 0) + { + for (int j = i; j < this.Count && this.data[j].StringIndex == nextStringIndex; j++) + { + this.data[j].StringIndex = Math.Min(this.data[j].StringIndex, pendingStringIndex); + } + } + + for (int j = i; j < this.Count && this.data[j].GraphemeIndex == nextGraphemeIndex; j++) + { + this.data[j].GraphemeIndex = Math.Min(this.data[j].GraphemeIndex, pendingGraphemeIndex); + } + + this.data[i].CodePointCount += pendingCodePointCount; + pendingCodePointCount = 0; + } + + if (kept != i) + { + this.data[kept] = this.data[i]; + if (positioning) + { + this.metrics[kept] = this.metrics[i]; + this.positions[kept] = this.positions[i]; + } + } + + kept++; + } + + if (pendingCodePointCount > 0 && kept > 0) + { + int lastCodePointIndex = this.data[kept - 1].CodePointIndex; + int lastStringIndex = this.data[kept - 1].StringIndex; + for (int i = kept - 1; i >= 0 && this.data[i].CodePointIndex == lastCodePointIndex; i--) + { + this.data[i].CodePointIndex = Math.Min(this.data[i].CodePointIndex, pendingCodePointIndex); + this.data[i].GraphemeIndex = Math.Min(this.data[i].GraphemeIndex, pendingGraphemeIndex); + } + + if (pendingStringIndex >= 0) + { + for (int i = kept - 1; i >= 0 && this.data[i].StringIndex == lastStringIndex; i--) + { + this.data[i].StringIndex = Math.Min(this.data[i].StringIndex, pendingStringIndex); + } + } + + this.data[kept - 1].CodePointCount += pendingCodePointCount; + } + + this.Count = kept; + } + + /// + /// Gets a reference to a record on the output side of the active pass. + /// + /// The zero-based output-side index. + /// A reference to the record. + public ref GlyphShapingData PassOutputAt(int index) + { + if (this.passDiverged) + { + return ref this.outData[index]; + } + + return ref this.data[index]; + } + + /// + /// Begins a substitution pass with both cursors at the given position: the + /// records before it are untouched by the pass by construction, so the + /// aliased output region simply adopts them. Output aliases the primary + /// storage until a write would overtake unread input. + /// + /// The position at which the pass begins. + public void BeginOutputPass(int startIndex) + { + this.IsPassActive = true; + this.passDiverged = false; + this.PassOutputCount = startIndex; + this.ReadIndex = startIndex; + } + + /// + /// Ends the active pass: unconsumed input records stream to the output side, + /// the output becomes the buffer content, and in-place semantics resume. A + /// pass that is still aliased and level has changed nothing structural, so + /// the tail is already in place and the pass closes with cursor resets alone. + /// + public void EndOutputPass() + { + if (!this.passDiverged && this.PassOutputCount == this.ReadIndex) + { + this.IsPassActive = false; + this.ReadIndex = 0; + this.PassOutputCount = 0; + return; + } + + this.CopyGlyphs(this.Count - this.ReadIndex); + + if (this.passDiverged) + { + (this.data, this.outData) = (this.outData, this.data); + } + + this.Count = this.PassOutputCount; + this.EnsureCapacity(this.Count); + this.IsPassActive = false; + this.passDiverged = false; + this.ReadIndex = 0; + this.PassOutputCount = 0; + } + + /// + /// Copies the record at the read cursor to the output side and advances both + /// cursors. While the sides are aliased and level this is two cursor + /// increments and nothing else; the copying forms live in the cold method so + /// the overwhelmingly common no-op inlines into the pass walk. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyGlyph() + { + if (!this.passDiverged && this.PassOutputCount == this.ReadIndex) + { + this.PassOutputCount++; + this.ReadIndex++; + return; + } + + this.CopyGlyphMoved(); + } + + /// + /// Copies the given number of consecutive records from the read cursor to the + /// output side and advances both cursors. An aligned pass adopts the range by + /// moving its cursors; a shifted pass moves the range as one block. + /// + /// The number of records to copy. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyGlyphs(int count) + { + if (!this.passDiverged && this.PassOutputCount == this.ReadIndex) + { + this.PassOutputCount += count; + this.ReadIndex += count; + return; + } + + this.CopyGlyphsMoved(count); + } + + /// + /// Copies the record at the read cursor to the output side when the sides + /// have shifted or diverged. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void CopyGlyphMoved() + { + if (this.passDiverged) + { + this.EnsureOutCapacity(this.PassOutputCount + 1); + this.outData[this.PassOutputCount] = this.data[this.ReadIndex]; + } + else + { + this.data[this.PassOutputCount] = this.data[this.ReadIndex]; + } + + this.PassOutputCount++; + this.ReadIndex++; + } + + /// + /// Copies consecutive records at the read cursor when the pass sides have + /// shifted or diverged. + /// + /// The number of records to copy. + [MethodImpl(MethodImplOptions.NoInlining)] + private void CopyGlyphsMoved(int count) + { + if (this.passDiverged) + { + this.EnsureOutCapacity(this.PassOutputCount + count); + Array.Copy(this.data, this.ReadIndex, this.outData, this.PassOutputCount, count); + } + else + { + Array.Copy(this.data, this.ReadIndex, this.data, this.PassOutputCount, count); + } + + this.PassOutputCount += count; + this.ReadIndex += count; + } + + /// + /// Consumes the record at the read cursor without producing output, deleting + /// it from the pass result. The sides stay aliased: output only ever trails + /// the cursor after a deletion. + /// + public void SkipGlyph() => this.ReadIndex++; + + /// + /// Moves the pass position so that the given number of records sit on the + /// output side. Advancing streams records forward; rewinding returns produced + /// records to the input side ahead of the read cursor, exactly reversing the + /// stream. + /// + /// The output-side record count to move to. + public void MoveTo(int outputPosition) + { + if (this.PassOutputCount < outputPosition && this.ReadIndex < this.Count) + { + int count = Math.Min(outputPosition - this.PassOutputCount, this.Count - this.ReadIndex); + this.CopyGlyphs(count); + } + + if (outputPosition < this.PassOutputCount) + { + int rewound = this.PassOutputCount - outputPosition; + + // Produced records return to the input side ahead of the read + // cursor, which needs that many free slots behind it. A pass that + // produced more than it consumed has fewer, so the unread input + // moves up to open them. + if (rewound > this.ReadIndex) + { + this.ShiftInputForward(rewound - this.ReadIndex); + } + + this.ReadIndex -= rewound; + if (this.passDiverged) + { + Array.Copy(this.outData, outputPosition, this.data, this.ReadIndex, rewound); + } + else + { + Array.Copy(this.data, outputPosition, this.data, this.ReadIndex, rewound); + } + + this.PassOutputCount = outputPosition; + } + } + + /// + /// Moves the unread input up by the given number of slots, opening room + /// between the produced records and the read cursor. The records that fill + /// them come back from the produced side, so the buffer's total is + /// unchanged even though its input length grows. + /// + /// The number of slots to open. + private void ShiftInputForward(int count) + { + int unread = this.Count - this.ReadIndex; + this.EnsureCapacity(this.Count + count); + if (unread > 0) + { + Array.Copy(this.data, this.ReadIndex, this.data, this.ReadIndex + count, unread); + } + + this.ReadIndex += count; + this.Count += count; + } + + /// + /// Grows the record storage and the streams parallel to it. The three stay + /// the same length: every record addresses its metrics and its position by + /// its own index. + /// + /// The required record capacity. + private void EnsureCapacity(int required) + { + if (required > this.data.Length) + { + Array.Resize(ref this.data, Math.Max(this.data.Length * 2, required)); + } + + // The parallel streams are sized independently because ending a pass + // swaps the record storage with the output storage, which grew on its + // own; after such a swap the records can outnumber the slots that were + // allocated alongside them. + if (required > this.metrics.Length) + { + Array.Resize(ref this.metrics, Math.Max(this.metrics.Length * 2, required)); + } + + if (required > this.positions.Length) + { + Array.Resize(ref this.positions, Math.Max(this.positions.Length * 2, required)); + } + } + + /// + /// Places a dotted circle before the record at the given position. The + /// circle copies the following record's run and direction so it travels + /// with it, and stands for itself rather than continuing what it precedes. + /// + /// The position to insert before. + /// The dotted circle's glyph id. + public void InsertDottedCircle(int index, ushort glyphId) + { + this.EnsureCapacity(this.Count + 1); + Array.Copy(this.data, index, this.data, index + 1, this.Count - index); + this.Count++; + + GlyphShapingData following = this.data[index + 1]; + this.data[index] = new GlyphShapingData(following, true) + { + GlyphId = glyphId, + CodePoint = new CodePoint(DottedCircleCodePoint), + CodePointCount = 1, + LigatureComponent = -1, + }; + + this.glyphDigest.Add(glyphId); + } + + /// + /// Gets the contextual-match position slice for the current lookup depth. + /// + /// + /// Contextual subtables are entered from per-glyph lookup loops, so reserving + /// the maximum match array on the stack in each call creates avoidable stack + /// pressure. Depth partitioning preserves the parent match while a nested + /// contextual lookup uses the next slice. + /// + /// A reusable span large enough for the maximum shaping context. + public Span GetContextMatchPositions() + { + int stride = AdvancedTypographicUtils.MaxContextLength + 1; + int offset = this.nestedApplicationDepth * stride; + int required = offset + stride; + if (this.contextMatchPositions.Length < required) + { + Array.Resize(ref this.contextMatchPositions, required); + } + + return this.contextMatchPositions.AsSpan(offset, stride); + } + + /// + /// Enters a nested lookup application within a contextual match. + /// + public void PushNestedApplication() => this.nestedApplicationDepth++; + + /// + /// Leaves a nested lookup application within a contextual match. + /// + public void PopNestedApplication() => this.nestedApplicationDepth--; + + /// + /// Consumes the record at the read cursor onto the output side and returns a + /// reference to the produced record for mutation. While the sides are aliased + /// and level the record is produced onto itself. + /// + /// A reference to the produced record. + private ref GlyphShapingData ProduceFromCursor() + { + if (this.passDiverged) + { + this.EnsureOutCapacity(this.PassOutputCount + 1); + this.outData[this.PassOutputCount] = this.data[this.ReadIndex]; + this.ReadIndex++; + return ref this.outData[this.PassOutputCount++]; + } + + if (this.PassOutputCount != this.ReadIndex) + { + this.data[this.PassOutputCount] = this.data[this.ReadIndex]; + } + + this.ReadIndex++; + return ref this.data[this.PassOutputCount++]; + } + + /// + /// Appends a record to the output side without consuming input, diverging + /// first if the write would otherwise overtake unread input. + /// + /// The record to append. + private void AppendOutputGlyph(in GlyphShapingData record) + { + if (!this.passDiverged && this.PassOutputCount >= this.ReadIndex) + { + this.Diverge(); + } + + if (this.passDiverged) + { + this.EnsureOutCapacity(this.PassOutputCount + 1); + this.outData[this.PassOutputCount++] = record; + } + else + { + this.data[this.PassOutputCount++] = record; + } + } + + /// + /// Diverges the active pass into the output storage: everything produced so + /// far is copied out of the primary storage, and later writes stream there. + /// Called only when output would otherwise overtake unread input. + /// + private void Diverge() + { + if (this.outData.Length < this.data.Length) + { + this.outData = new GlyphShapingData[this.data.Length]; + } + + Array.Copy(this.data, this.outData, this.PassOutputCount); + this.passDiverged = true; + } + + /// + /// Grows the output storage to hold at least the required record count. + /// + /// The required record capacity. + private void EnsureOutCapacity(int required) + { + if (required > this.outData.Length) + { + int length = Math.Max(this.outData.Length * 2, required); + Array.Resize(ref this.outData, length); + } + } + + /// + /// One glyph's metrics-phase state: the resolving font, its point size, and the + /// resolved metrics instance. Stored in a stream parallel to the glyph records. + /// + public struct GlyphMetricsEntry + { + /// + /// The font that resolved the glyph. + /// + public Font Font; + + /// + /// The font size in PT units of the font containing this glyph. + /// + public float PointSize; + + /// + /// The font glyph metrics. + /// + public FontGlyphMetrics Metrics; + + /// + /// Initializes a new instance of the struct. + /// + /// The font that resolved the glyph. + /// The font size in PT units. + /// The font glyph metrics. + public GlyphMetricsEntry(Font font, float pointSize, FontGlyphMetrics metrics) + { + this.Font = font; + this.PointSize = pointSize; + this.Metrics = metrics; + } + + /// + /// Gets the positioned horizontal advance in font design units for the paired + /// entry: the shaping bounds value once positioning has written one, otherwise + /// the metrics advance. + /// + /// The paired positioning entry. + /// The advance. + public readonly ushort GetAdvanceWidth(in GlyphShapingPosition position) + => position.Bounds.IsDirtyWH ? (ushort)position.Bounds.Width : this.Metrics.AdvanceWidth; + + /// + /// Gets the positioned vertical advance in font design units for the paired + /// entry: the shaping bounds value once positioning has written one, otherwise + /// the metrics advance. + /// + /// The paired positioning entry. + /// The advance. + public readonly ushort GetAdvanceHeight(in GlyphShapingPosition position) + => position.Bounds.IsDirtyWH ? (ushort)position.Bounds.Height : this.Metrics.AdvanceHeight; + } +} diff --git a/src/SixLabors.Fonts/ShapingBufferRole.cs b/src/SixLabors.Fonts/ShapingBufferRole.cs new file mode 100644 index 000000000..657d4b134 --- /dev/null +++ b/src/SixLabors.Fonts/ShapingBufferRole.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts; + +/// +/// Identifies which phase of the shaping pass a serves. +/// Shapers use the role to run phase-specific work: syllable analysis and reordering +/// belong to substitution and must not run again during positioning. +/// +internal enum ShapingBufferRole +{ + /// + /// The per-font-run workspace buffer glyphs are substituted in. + /// + Substitution, + + /// + /// The accumulated result buffer glyphs are seeded and positioned in. + /// + Positioning, +} diff --git a/src/SixLabors.Fonts/ShapingScratch.cs b/src/SixLabors.Fonts/ShapingScratch.cs new file mode 100644 index 000000000..a1e2a0450 --- /dev/null +++ b/src/SixLabors.Fonts/ShapingScratch.cs @@ -0,0 +1,289 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; +using SixLabors.Fonts.Tables.AdvancedTypographic; +using SixLabors.Fonts.Unicode; + +namespace SixLabors.Fonts; + +/// +/// Reusable shaping pipeline state: the per-font-run workspace buffer, the accumulated +/// result buffer, and their shared feature map. Buffer storage grows to the workload's +/// high-water mark and is reused across calls, so steady-state shaping performs no +/// per-call allocation for pipeline state. +/// +/// +/// A scratch is exclusively owned by one shaping call at a time, enforced by +/// ownership in . Reuse is safe +/// because every public shaping result is materialized by value before the scratch is +/// returned; nothing the pipeline pools can escape a call. +/// +internal sealed class ShapingScratch +{ + /// + /// The per-font-run workspace buffer glyphs are substituted in. + /// + private ShapingBuffer? workspace; + + /// + /// The options the shaping pipeline is driven through, held for the lifetime of + /// the pooled scratch so shaping a run never builds them. + /// + private TextOptions? shapingOptions; + + /// + /// The accumulated result buffer glyphs are seeded and positioned in. + /// + private ShapingBuffer? result; + + /// + /// The synthesized run covering the whole text when the options carry no + /// user-defined runs, reused across passes together with its single-element + /// list. + /// + private readonly TextRun[] defaultRun = [new()]; + + /// + /// The reusable bidi algorithm instance backing . + /// + private BidiAlgorithm? bidiAlgorithm; + + /// + /// The reusable bidi analysis data backing . + /// + private BidiData? bidiData; + + /// + /// The resolved bidi runs of the current pass, grown to the high-water count. + /// + private BidiRun[] bidiRuns = new BidiRun[4]; + + /// + /// The contiguous visual glyph range of each resolved bidi run, parallel to + /// and grown to the same high-water run count. + /// + private ShapedGlyphRange[] bidiGlyphRanges = new ShapedGlyphRange[4]; + + /// + /// The per-glyph identity records of the current pass's projection, grown to + /// the high-water glyph count. + /// + private ShapedGlyphInfo[] infos = []; + + /// + /// The per-glyph geometry records of the current pass's projection, parallel + /// to . + /// + private ShapedGlyphPosition[] positions = []; + + /// + /// The run table of the current pass's projection, grown to the high-water + /// run count. + /// + private ShapedTextRun[] runs = new ShapedTextRun[4]; + + /// + /// Gets the reusable bidi algorithm instance. Its work buffers grow to the + /// workload's high-water mark and are reused across passes. + /// + public BidiAlgorithm BidiAlgorithm => this.bidiAlgorithm ??= new(); + + /// + /// Gets the reusable bidi analysis data. Its builders grow to the workload's + /// high-water mark and resets them per pass. + /// + public BidiData BidiData => this.bidiData ??= new(); + + /// + /// Gets the resolved bidi runs of the current pass. Only the first + /// entries are live. + /// + public BidiRun[] BidiRuns => this.bidiRuns; + + /// + /// Gets the contiguous visual glyph range of each resolved bidi run. Only the + /// first entries are live. + /// + public ShapedGlyphRange[] BidiGlyphRanges => this.bidiGlyphRanges; + + /// + /// Gets the number of live entries in . + /// + public int BidiRunCount { get; private set; } + + /// + /// Gets the run table of the current pass's projection. Only the first + /// entries are live. + /// + public ShapedTextRun[] Runs => this.runs; + + /// + /// Gets the number of live entries in . + /// + public int RunCount { get; private set; } + + /// + /// Gets the codepoint index to bidi-run index mapping storage of the current + /// pass, as last prepared by . + /// + public int[] BidiMap { get; private set; } = []; + + /// + /// Empties the bidi run storage for a new pass. + /// + public void ClearBidiRuns() => this.BidiRunCount = 0; + + /// + /// Returns the options carrying the public shaping request, refreshed in place. + /// + /// The font being shaped against. + /// The line base direction or directional-run direction. + /// The language the text is written in. + /// The script applied to the whole request, or to infer scripts from the text. + /// The horizontal or vertical layout mode. + /// The kerning mode. + /// The feature tags to turn on. + /// Whether the request is a logical line or one directional run. + /// The options. + public TextOptions GetShapingOptions(Font font, TextDirection direction, CultureInfo language, ScriptClass? script, LayoutMode layoutMode, KerningMode kerningMode, Tag[] features, TextBidiMode bidiMode) + { + TextOptions current = this.shapingOptions ??= new TextOptions(font); + + // The object is retained with the pooled scratch state to avoid allocating + // TextOptions per call. Overwrite every value the public shaping API can + // control so no state leaks from the previous use of the pool entry. + current.Font = font; + current.TextDirection = direction; + current.TextBidiMode = bidiMode; + current.Culture = language; + current.Script = script; + current.LayoutMode = layoutMode; + current.KerningMode = kerningMode; + current.FeatureTags = features; + + return current; + } + + /// + /// Appends a resolved bidi run. + /// + /// The run to append. + public void AddBidiRun(in BidiRun run) + { + if (this.BidiRunCount == this.bidiRuns.Length) + { + Array.Resize(ref this.bidiRuns, this.bidiRuns.Length * 2); + Array.Resize(ref this.bidiGlyphRanges, this.bidiGlyphRanges.Length * 2); + } + + this.bidiRuns[this.BidiRunCount++] = run; + } + + /// + /// Records the contiguous visual glyph range owned by one resolved bidi run. + /// + /// The zero-based resolved bidi-run index. + /// The glyph range over the projected shaping arrays. + public void SetBidiGlyphRange(int runIndex, in ShapedGlyphRange range) + => this.bidiGlyphRanges[runIndex] = range; + + /// + /// Empties the projected run table for a new pass. + /// + public void ClearRuns() => this.RunCount = 0; + + /// + /// Appends a projected run table entry. + /// + /// The entry to append. + public void AddRun(in ShapedTextRun run) + { + if (this.RunCount == this.runs.Length) + { + Array.Resize(ref this.runs, this.runs.Length * 2); + } + + this.runs[this.RunCount++] = run; + } + + /// + /// Gets the parallel projection storage for the given glyph count. + /// + /// The glyph capacity required. + /// The identity and geometry storage; entries beyond the count are undefined. + public (ShapedGlyphInfo[] Infos, ShapedGlyphPosition[] Positions) GetProjection(int count) + { + if (this.infos.Length < count) + { + int capacity = Math.Max(count, Math.Max(64, this.infos.Length * 2)); + this.infos = new ShapedGlyphInfo[capacity]; + this.positions = new ShapedGlyphPosition[capacity]; + } + + return (this.infos, this.positions); + } + + /// + /// Gets the codepoint index to bidi-run index mapping for the given text + /// length, every entry reset to the unvisited -1 sentinel. + /// + /// The codepoint capacity required. + /// The mapping storage; entries beyond the length are undefined. + public int[] GetBidiMap(int length) + { + if (this.BidiMap.Length < length) + { + this.BidiMap = new int[Math.Max(length, Math.Max(64, this.BidiMap.Length * 2))]; + } + + Array.Fill(this.BidiMap, -1, 0, length); + return this.BidiMap; + } + + /// + /// Gets the reusable single-run list configured for the given options. The + /// shaping pass sets its exclusive end after enumerating the text. + /// + /// The text options supplying the font. + /// The run list. + public IReadOnlyList GetDefaultTextRuns(TextOptions options) + { + TextRun run = this.defaultRun[0]; + run.Start = 0; + run.End = 0; + run.Font = options.Font; + run.ResolveFontWeight(options.FontWeight); + return this.defaultRun; + } + + /// + /// Gets the reusable shaping buffers, reset for a new pass over the given options. + /// + /// The text options for the pass. + /// The reusable buffers, sharing one feature map. + public (ShapingBuffer Workspace, ShapingBuffer Result) Prepare(TextOptions options) + { + ShapingBuffer? workspace = this.workspace; + ShapingBuffer? result = this.result; + if (workspace is null || result is null) + { + workspace = new(options, ShapingBufferRole.Substitution); + result = new(options, ShapingBufferRole.Positioning); + this.workspace = workspace; + this.result = result; + } + else + { + workspace.Reset(options); + result.Reset(options); + + // The single-run fast path flips the workspace to the positioning role in + // place; a pooled scratch must hand out buffers in their home roles. + workspace.SetRole(ShapingBufferRole.Substitution); + result.SetRole(ShapingBufferRole.Positioning); + } + + return (workspace, result); + } +} diff --git a/src/SixLabors.Fonts/StreamFontMetrics.Cff.cs b/src/SixLabors.Fonts/StreamFontMetrics.Cff.cs index d09fb89d2..377db0752 100644 --- a/src/SixLabors.Fonts/StreamFontMetrics.Cff.cs +++ b/src/SixLabors.Fonts/StreamFontMetrics.Cff.cs @@ -32,6 +32,7 @@ private static StreamFontMetrics LoadCompactFont(FontReader reader, FontSource s OS2Table os2 = reader.GetTable(); NameTable name = reader.GetTable(); CMapTable cmap = reader.GetTable(); + cmap.SetSymbolFontPage(os2.FontPage); PostTable post = reader.GetTable(); ICffTable? cff = (reader.TryGetTable() ?? (ICffTable?)reader.TryGetTable()) @@ -138,7 +139,7 @@ private FontGlyphMetrics CreateCffGlyphMetrics( // Apply HVAR advance width adjustment if available. if (this.GlyphVariationProcessor is not null) { - advanceWidth = (ushort)(advanceWidth + MathF.Round(this.GlyphVariationProcessor.AdvanceAdjustment(glyphId))); + advanceWidth = (ushort)Math.Max(0, advanceWidth + this.GlyphVariationProcessor.AdvanceAdjustment(glyphId, 0)); } IMetricsHeader metrics = isVerticalLayout ? this.VerticalMetrics : this.HorizontalMetrics; @@ -153,7 +154,7 @@ private FontGlyphMetrics CreateCffGlyphMetrics( // Apply VVAR advance height adjustment if available. if (this.GlyphVariationProcessor is not null) { - advancedHeight = (ushort)(advancedHeight + MathF.Round(this.GlyphVariationProcessor.VerticalAdvanceAdjustment(glyphId))); + advancedHeight = (ushort)Math.Max(0, advancedHeight + this.GlyphVariationProcessor.VerticalAdvanceAdjustment(glyphId, 0)); } // TODO: Support CFF based COLR glyphs. diff --git a/src/SixLabors.Fonts/StreamFontMetrics.TrueType.cs b/src/SixLabors.Fonts/StreamFontMetrics.TrueType.cs index 0c3fe0efc..d3b135236 100644 --- a/src/SixLabors.Fonts/StreamFontMetrics.TrueType.cs +++ b/src/SixLabors.Fonts/StreamFontMetrics.TrueType.cs @@ -107,6 +107,7 @@ private static StreamFontMetrics LoadTrueTypeFont(FontReader reader, FontSource OS2Table os2 = reader.GetTable(); HorizontalMetricsTable htmx = reader.GetTable(); CMapTable cmap = reader.GetTable(); + cmap.SetSymbolFontPage(os2.FontPage); FpgmTable? fpgm = reader.TryGetTable(); PrepTable? prep = reader.TryGetTable(); CvtTable? cvt = reader.TryGetTable(); @@ -200,13 +201,14 @@ private FontGlyphMetrics CreateTrueTypeGlyphMetrics( VerticalMetricsTable? vtmx = tables.Vmtx; GlyphVector vector = glyf.GetGlyph(glyphId); + Vector2 glyphAdvanceAdjustment = Vector2.Zero; // Apply gvar deltas to the glyph outline if a variation processor is present. // Clone first so we don't mutate the shared glyph cache. if (this.GlyphVariationProcessor is not null) { vector = GlyphVector.DeepClone(vector); - this.GlyphVariationProcessor.TransformPoints(glyphId, ref vector); + this.GlyphVariationProcessor.TransformPoints(glyphId, ref vector, out glyphAdvanceAdjustment); } Bounds bounds = vector.Bounds; @@ -214,10 +216,11 @@ private FontGlyphMetrics CreateTrueTypeGlyphMetrics( ushort advanceWidth = htmx.GetAdvancedWidth(glyphId); short lsb = htmx.GetLeftSideBearing(glyphId); - // Apply HVAR advance width adjustment if available. + // A dedicated metrics table takes precedence over the adjustment carried + // by the glyph's metric points. if (this.GlyphVariationProcessor is not null) { - advanceWidth = (ushort)(advanceWidth + MathF.Round(this.GlyphVariationProcessor.AdvanceAdjustment(glyphId))); + advanceWidth = (ushort)Math.Max(0, advanceWidth + this.GlyphVariationProcessor.AdvanceAdjustment(glyphId, glyphAdvanceAdjustment.X)); } IMetricsHeader metrics = isVerticalLayout ? this.VerticalMetrics : this.HorizontalMetrics; @@ -229,10 +232,10 @@ private FontGlyphMetrics CreateTrueTypeGlyphMetrics( tsb = vtmx.GetTopSideBearing(glyphId); } - // Apply VVAR advance height adjustment if available. + // Vertical metrics use the same precedence rule as horizontal metrics. if (this.GlyphVariationProcessor is not null) { - advancedHeight = (ushort)(advancedHeight + MathF.Round(this.GlyphVariationProcessor.VerticalAdvanceAdjustment(glyphId))); + advancedHeight = (ushort)Math.Max(0, advancedHeight + this.GlyphVariationProcessor.VerticalAdvanceAdjustment(glyphId, glyphAdvanceAdjustment.Y)); } ColrTable? colr = tables.Colr; diff --git a/src/SixLabors.Fonts/StreamFontMetrics.cs b/src/SixLabors.Fonts/StreamFontMetrics.cs index bcd197f41..ffca87244 100644 --- a/src/SixLabors.Fonts/StreamFontMetrics.cs +++ b/src/SixLabors.Fonts/StreamFontMetrics.cs @@ -181,6 +181,19 @@ private StreamFontMetrics( /// public override VerticalMetrics VerticalMetrics => this.verticalMetrics; + /// + internal override bool HasUnicodeVariationSequences + { + get + { + CMapTable cmap = this.outlineType == OutlineType.TrueType + ? this.trueTypeFontTables!.Cmap + : this.compactFontTables!.Cmap; + + return cmap.HasVariationSequences; + } + } + /// public override short SubscriptXSize => this.subscriptXSize; @@ -434,6 +447,16 @@ internal override bool TryGetGSubTable([NotNullWhen(true)] out GSubTable? gSubTa return gSubTable is not null; } + /// + internal override bool TryGetGPosTable([NotNullWhen(true)] out GPosTable? gPosTable) + { + gPosTable = this.outlineType == OutlineType.TrueType + ? this.trueTypeFontTables!.GPos + : this.compactFontTables!.GPos; + + return gPosTable is not null; + } + /// internal override bool TryGetBaselineCoordinate(Tag baselineTag, bool isVerticalLayout, out short coordinate) { @@ -451,12 +474,19 @@ internal override bool TryGetBaselineCoordinate(Tag baselineTag, bool isVertical } /// - internal override void ApplySubstitution(GlyphSubstitutionCollection collection) + internal override void ApplySubstitution(ShapingBuffer buffer) { if (this.TryGetGSubTable(out GSubTable? gSubTable)) { - gSubTable.ApplySubstitution(this, collection); + gSubTable.ApplySubstitution(this, buffer); + return; } + + // A font carrying no substitution table has no lookups to apply, but its + // runs are still planned: preparing a run's text is what supplies the + // characters a script cannot be read without, and the positioning pass + // reuses the runs planning records. + ScriptItemizer.PlanRuns(this, buffer); } /// @@ -477,7 +507,7 @@ internal override bool TryGetKerningOffset(ushort currentId, ushort nextId, out } /// - internal override void UpdatePositions(GlyphPositioningCollection collection) + internal override void UpdatePositions(ShapingBuffer buffer) { bool isTTF = this.outlineType == OutlineType.TrueType; GPosTable? gpos = isTTF @@ -485,9 +515,13 @@ internal override void UpdatePositions(GlyphPositioningCollection collection) : this.compactFontTables!.GPos; bool kerned = false; - KerningMode kerningMode = collection.TextOptions.KerningMode; + KerningMode kerningMode = buffer.TextOptions.KerningMode; - gpos?.TryUpdatePositions(this, collection, out kerned); + gpos?.TryUpdatePositions(this, buffer, out kerned); + if (gpos is null) + { + ZeroMarkAdvances(buffer, this, MarkZeroingMode.PreGPos); + } // TODO: I don't think we should disable kerning here. if (!kerned && kerningMode != KerningMode.None) @@ -499,18 +533,45 @@ internal override void UpdatePositions(GlyphPositioningCollection collection) if (kern?.Count > 0) { // Set max constraints to prevent OutOfMemoryException or infinite loops from attacks. - int maxCount = AdvancedTypographicUtils.GetMaxAllowableShapingCollectionCount(collection.Count); - for (int index = 0; index < collection.Count - 1; index++) + int maxCount = AdvancedTypographicUtils.GetMaxAllowableShapingCollectionCount(buffer.Count); + for (int index = 0; index < buffer.Count - 1; index++) { if (index >= maxCount) { break; } - kern.UpdatePositions(this, collection, index, index + 1); + kern.UpdatePositions(this, buffer, index, index + 1); } } } + + if (gpos is null) + { + ZeroMarkAdvances(buffer, this, MarkZeroingMode.PostGpos); + FallbackMarkPositioner.Apply(this, buffer); + } + } + + /// + /// Applies a script's mark-zeroing stage when the font has no positioning table to host that stage. + /// + /// The positioned glyph buffer. + /// The font metrics supplying glyph classes. + /// The mark-zeroing stage to apply. + private static void ZeroMarkAdvances(ShapingBuffer buffer, FontMetrics fontMetrics, MarkZeroingMode mode) + { + List<(int Index, int Count, ScriptClass Script, ShapePlan Plan)> segments = buffer.SegmentPlans; + for (int i = 0; i < segments.Count; i++) + { + (int index, int count, ScriptClass _, ShapePlan plan) = segments[i]; + if (plan.FontMetrics == fontMetrics && plan.Shaper.MarkZeroingMode == mode) + { + // Without a positioning table, forward text needs the removed + // advance folded into the mark offset so it stays over its base. + AdvancedTypographicUtils.ZeroMarkAdvances(fontMetrics, buffer, index, count, true); + } + } } /// @@ -578,8 +639,8 @@ public StreamFontMetrics CreateVariationInstance(FontVariation[] variations) { if (fvar.Axes[i].Tag == variation.Tag) { + // A malformed but usable font can repeat an axis tag. A tag-value setting applies to every matching axis. userCoordinates[i] = variation.Value; - break; } } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/AdvancedTypographicUtils.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/AdvancedTypographicUtils.cs index 4a3a9c476..2b0e33db9 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/AdvancedTypographicUtils.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/AdvancedTypographicUtils.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Globalization; using SixLabors.Fonts.Tables.AdvancedTypographic.GPos; using SixLabors.Fonts.Unicode; @@ -19,17 +20,24 @@ internal static class AdvancedTypographicUtils public const int MaxContextLength = 64; /// - /// The maximum length factor multiplied by collection count to compute max allowable collection size. + /// The maximum depth of nested lookup application. Contextual lookups may + /// recurse into one another, so a font can describe unbounded nesting; + /// application stops at this depth instead. + /// + public const int MaxNestingLevel = 64; + + /// + /// The maximum length factor multiplied by buffer count to compute max allowable buffer size. /// private const int MaxLengthFactor = 64; /// - /// The minimum value for the max allowable collection size. + /// The minimum value for the max allowable buffer size. /// private const int MaxLengthMinimum = 16384; /// - /// The maximum operations factor multiplied by collection count to compute max allowable operations. + /// The maximum operations factor multiplied by buffer count to compute max allowable operations. /// private const int MaxOperationsFactor = 1024; @@ -59,6 +67,21 @@ internal enum MatchDirection Backward } + /// + /// Supplies a compile-time glyph comparison to the shared sequence walks. + /// + /// The type of sequence element to compare. + private interface ISequenceMatcher + { + /// + /// Determines whether one sequence element matches the current glyph record. + /// + /// The sequence element. + /// The glyph record. + /// when the element matches; otherwise, . + static abstract bool Matches(T element, ref GlyphShapingData data); + } + /// /// Gets a value indicating whether the glyph represented by the codepoint should be interpreted vertically. /// @@ -77,9 +100,9 @@ public static bool IsVerticalGlyph(CodePoint codePoint, LayoutMode layoutMode) } /// - /// Gets the maximum allowable shaping collection count for the given input length. + /// Gets the maximum allowable shaping buffer count for the given input length. /// - /// The input collection length. + /// The input buffer length. /// The maximum allowable count. public static int GetMaxAllowableShapingCollectionCount(int length) => (int)Math.Min(Math.Max((long)length * MaxLengthFactor, MaxLengthMinimum), MaxShapingCharsLength); @@ -87,7 +110,7 @@ public static int GetMaxAllowableShapingCollectionCount(int length) /// /// Gets the maximum allowable shaping operations count for the given input length. /// - /// The input collection length. + /// The input buffer length. /// The maximum allowable operations count. public static int GetMaxAllowableShapingOperationsCount(int length) => (int)Math.Min(Math.Max((long)length * MaxOperationsFactor, MaxOperationsMinimum), MaxShapingCharsLength); @@ -98,233 +121,605 @@ public static int GetMaxAllowableShapingOperationsCount(int length) /// The font metrics. /// The GSUB table. /// The feature tag being applied. - /// The lookup flags for glyph filtering. - /// The mark filtering set index. + /// The applying lookup's combined mask, inherited by the nested lookups. /// The sequence lookup records specifying which lookups to apply at which positions. - /// The glyph substitution collection. - /// The starting index in the collection. - /// The number of glyphs in the input sequence. + /// The glyph substitution buffer. + /// The buffer positions the input sequence matched at, reconciled as nested lookups change the buffer's length. + /// The number of matched positions. + /// The position one past the final matched input record. /// if the lookups were applied. public static bool ApplyLookupList( FontMetrics fontMetrics, GSubTable table, Tag feature, - LookupFlags lookupFlags, - ushort markFilteringSet, + uint lookupMask, SequenceLookupRecord[] records, - GlyphSubstitutionCollection collection, - int index, - int count) + ShapingBuffer buffer, + Span matchPositions, + int matchCount, + int matchEnd) { - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags, markFilteringSet); - int currentCount = collection.Count; + if (buffer.NestingLimitReached) + { + return false; + } + + // Matching walked the records still to be read; applying works against + // the pass position, where the produced side has its own length. A + // sequence index can name a record a nested lookup produced, which no + // input position describes, so the matched positions move into that + // frame once and stay in it. + int shift = buffer.PassBacktrackLength - buffer.ReadIndex; + int end = buffer.PassBacktrackLength + (matchEnd - buffer.ReadIndex); + + // The two sides sit level until a lookup in this pass changes a length, + // and level means the frames coincide. + if (shift != 0) + { + for (int i = 0; i < matchCount; i++) + { + matchPositions[i] += shift; + } + } + + buffer.PushNestedApplication(); foreach (SequenceLookupRecord lookupRecord in records) { - ushort sequenceIndex = lookupRecord.SequenceIndex; - ushort lookupIndex = lookupRecord.LookupListIndex; - iterator.Index = index; - iterator.Increment(sequenceIndex); - GSub.LookupTable lookup = table.LookupList.LookupTables[lookupIndex]; - _ = lookup.TrySubstitution(fontMetrics, table, collection, feature, iterator.Index, count - (iterator.Index - index)); + int sequenceIndex = lookupRecord.SequenceIndex; + if (sequenceIndex >= matchCount) + { + continue; + } + + int total = buffer.PassBacktrackLength + buffer.PassLookaheadLength; + int position = matchPositions[sequenceIndex]; - // Account for substitutions changing the length of the collection. - if (collection.Count != currentCount) + // An earlier nested lookup can consume enough records to strand a + // later sequence position past everything that remains. + if (position >= total) { - count -= currentCount - collection.Count; - currentCount = collection.Count; + continue; } + + // The nested lookup applies at the cursor, so the cursor goes to the + // record: forward over what it passes, back over what an earlier + // nested lookup already produced. + buffer.MoveTo(position); + + GSub.LookupTable lookup = table.LookupList.LookupTables[lookupRecord.LookupListIndex]; + _ = lookup.TrySubstitution(fontMetrics, table, buffer, feature, lookupMask, buffer.ReadIndex, buffer.PassLookaheadLength); + + int delta = buffer.PassBacktrackLength + buffer.PassLookaheadLength - total; + if (delta == 0) + { + continue; + } + + end += delta; + if (end < position) + { + // A nested lookup that consumed a great deal can pull the end + // behind the record it applied at; nothing before that record + // can have been consumed, so the end stops there. + delta += position - end; + end = position; + } + + matchCount = FixupMatchPositions(matchPositions, matchCount, sequenceIndex, delta); } + buffer.PopNestedApplication(); + + // Everything the rule matched is now behind the pass position. + buffer.MoveTo(end); return true; } + /// + /// Reconciles the matched positions after a nested lookup changed the + /// buffer's length. A growth is taken as records inserted directly after the + /// applying position, and a shrink as the positions following it having been + /// consumed; the trailing positions then shift by the same delta. + /// + /// The matched positions to reconcile. + /// The number of matched positions. + /// The position the nested lookup applied at. + /// The buffer's length change. + /// The new number of matched positions. + private static int FixupMatchPositions(Span matchPositions, int matchCount, int sequenceIndex, int delta) + { + int next = sequenceIndex + 1; + if (delta > 0) + { + if (delta + matchCount > MaxContextLength) + { + return matchCount; + } + } + else + { + // A shrink can never consume more positions than the match holds. + delta = Math.Max(delta, next - matchCount); + next -= delta; + } + + int tail = matchCount - next; + if (tail > 0) + { + matchPositions.Slice(next, tail).CopyTo(matchPositions[(next + delta)..]); + } + + next += delta; + matchCount += delta; + + for (int j = sequenceIndex + 1; j < next; j++) + { + matchPositions[j] = matchPositions[j - 1] + 1; + } + + for (; next < matchCount; next++) + { + matchPositions[next] += delta; + } + + return matchCount; + } + /// /// Applies nested lookups from sequence lookup records for GPOS contextual/chaining lookups. /// /// The font metrics. /// The GPOS table. /// The feature tag being applied. - /// The lookup flags for glyph filtering. - /// The mark filtering set index. /// The sequence lookup records specifying which lookups to apply at which positions. - /// The glyph positioning collection. - /// The starting index in the collection. + /// The glyph positioning buffer. + /// The buffer positions the input sequence matched at. + /// The number of matched positions. /// The number of glyphs in the input sequence. /// if the lookups were applied. public static bool ApplyLookupList( FontMetrics fontMetrics, GPosTable table, Tag feature, - LookupFlags lookupFlags, - ushort markFilteringSet, SequenceLookupRecord[] records, - GlyphPositioningCollection collection, - int index, + ShapingBuffer buffer, + ReadOnlySpan matchPositions, + int matchCount, int count) { - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags, markFilteringSet); + if (buffer.NestingLimitReached) + { + return false; + } + + // Positioning never changes the buffer's length, so the matched + // positions stand for the whole record list. + int startIndex = matchPositions[0]; + buffer.PushNestedApplication(); + foreach (SequenceLookupRecord lookupRecord in records) { - ushort sequenceIndex = lookupRecord.SequenceIndex; - ushort lookupIndex = lookupRecord.LookupListIndex; - iterator.Index = index; - iterator.Increment(sequenceIndex); - LookupTable lookup = table.LookupList.LookupTables[lookupIndex]; - _ = lookup.TryUpdatePosition(fontMetrics, table, collection, feature, iterator.Index, count - (iterator.Index - index)); + int sequenceIndex = lookupRecord.SequenceIndex; + if (sequenceIndex >= matchCount) + { + continue; + } + + int position = matchPositions[sequenceIndex]; + LookupTable lookup = table.LookupList.LookupTables[lookupRecord.LookupListIndex]; + _ = lookup.TryUpdatePosition(fontMetrics, table, buffer, feature, position, count - (position - startIndex)); } + buffer.PopNestedApplication(); return true; } /// - /// Matches an input glyph sequence by glyph ID, verifying that each glyph has the specified feature enabled. + /// Matches an input glyph sequence by glyph ID, verifying that each glyph has the applying lookup enabled. /// /// The skipping glyph iterator. - /// The feature tag that must be enabled on matched glyphs. + /// The applying lookup's combined mask; matched glyphs must have it enabled. /// The initial increment from the iterator's current position. /// The array of glyph IDs to match. /// A span to store matched glyph indices, or default if not needed. /// if the entire sequence was matched; otherwise, . - public static bool MatchInputSequence(SkippingGlyphIterator iterator, Tag feature, ushort increment, ushort[] sequence, Span matches) - => Match( - increment, - sequence, - iterator, - (component, data) => - { - if (!ContainsFeatureTag(data.Features, feature)) - { - return false; - } + public static bool MatchInputSequence(SkippingGlyphIterator iterator, uint featureMask, ushort increment, ushort[] sequence, Span matches) + { + iterator.SetMatchContext(featureMask, false); + return Match(increment, sequence, iterator, matches); + } - return component == data.GlyphId; - }, - matches); + /// + /// Matches a glyph sequence by glyph ID under the given matcher context. + /// + /// The skipping glyph iterator. + /// The initial increment from the iterator's current position. + /// The array of glyph IDs to match. + /// The applying lookup's mask; ignored during context matching. + /// Whether this matches backtrack or lookahead context. + /// if the entire sequence was matched; otherwise, . + public static bool MatchSequence(SkippingGlyphIterator iterator, int increment, ushort[] sequence, uint mask, bool contextMatch) + => MatchSequence(iterator, increment, sequence, mask, contextMatch, out _); /// - /// Determines whether the feature list contains the specified feature tag in an enabled state. + /// Matches a glyph sequence by glyph ID under the given matcher context, + /// reporting the position one past the final matched element. /// - /// The list of tag entries to search. - /// The feature tag to find. - /// if the feature is present and enabled; otherwise, . - private static bool ContainsFeatureTag(List featureList, Tag feature) - { - foreach (TagEntry tagEntry in featureList) - { - if (tagEntry.Tag == feature && tagEntry.Enabled) - { - return true; - } - } + /// The skipping glyph iterator. + /// The initial increment from the iterator's current position. + /// The array of glyph IDs to match. + /// The applying lookup's mask; ignored during context matching. + /// Whether this matches backtrack or lookahead context. + /// The position one past the final matched element. + /// if the entire sequence was matched; otherwise, . + public static bool MatchSequence(SkippingGlyphIterator iterator, int increment, ushort[] sequence, uint mask, bool contextMatch, out int matchEnd) + => MatchSequence(iterator, increment, sequence, mask, contextMatch, default, out matchEnd); - return false; + /// + /// Matches a glyph sequence by glyph ID under the given matcher context, + /// recording where each element matched so nested lookups apply to the + /// records the match actually consumed rather than to a re-derived walk. + /// + /// The skipping glyph iterator. + /// The initial increment from the iterator's current position. + /// The array of glyph IDs to match. + /// The applying lookup's mask; ignored during context matching. + /// Whether this matches backtrack or lookahead context. + /// The span receiving the matched positions, or default when they are not needed. + /// The position one past the final matched element. + /// if the entire sequence was matched; otherwise, . + public static bool MatchSequence(SkippingGlyphIterator iterator, int increment, ushort[] sequence, uint mask, bool contextMatch, Span matches, out int matchEnd) + { + iterator.SetMatchContext(mask, contextMatch); + return Match(increment, sequence, iterator, matches, out matchEnd); } /// - /// Matches a glyph sequence by glyph ID. + /// Matches a glyph sequence by class values using a class definition table + /// under the given matcher context. /// /// The skipping glyph iterator. /// The initial increment from the iterator's current position. - /// The array of glyph IDs to match. + /// The array of class values to match. + /// The class definition table used to map glyph IDs to class values. + /// The applying lookup's mask; ignored during context matching. + /// Whether this matches backtrack or lookahead context. /// if the entire sequence was matched; otherwise, . - public static bool MatchSequence(SkippingGlyphIterator iterator, int increment, ushort[] sequence) - => Match( - increment, - sequence, - iterator, - (component, data) => component == data.GlyphId, - default); + public static bool MatchClassSequence( + SkippingGlyphIterator iterator, + int increment, + ushort[] sequence, + ClassDefinitionTable classDefinitionTable, + uint mask, + bool contextMatch) + => MatchClassSequence(iterator, increment, sequence, classDefinitionTable, mask, contextMatch, out _); /// - /// Matches a glyph sequence by class values using a class definition table. + /// Matches a glyph sequence by class values under the given matcher context, + /// reporting the position one past the final matched element. /// /// The skipping glyph iterator. /// The initial increment from the iterator's current position. /// The array of class values to match. /// The class definition table used to map glyph IDs to class values. + /// The applying lookup's mask; ignored during context matching. + /// Whether this matches backtrack or lookahead context. + /// The position one past the final matched element. /// if the entire sequence was matched; otherwise, . public static bool MatchClassSequence( SkippingGlyphIterator iterator, int increment, ushort[] sequence, - ClassDefinitionTable classDefinitionTable) - => Match( - increment, - sequence, - iterator, - (component, data) => component == classDefinitionTable.ClassIndexOf(data.GlyphId), - default); + ClassDefinitionTable classDefinitionTable, + uint mask, + bool contextMatch, + out int matchEnd) + => MatchClassSequence(iterator, increment, sequence, classDefinitionTable, mask, contextMatch, default, out matchEnd); + + /// + /// Matches a glyph sequence by class values under the given matcher context, + /// recording where each element matched so nested lookups apply to the + /// records the match actually consumed. + /// + /// The skipping glyph iterator. + /// The initial increment from the iterator's current position. + /// The array of class values to match. + /// The class definition table used to map glyph IDs to class values. + /// The applying lookup's mask; ignored during context matching. + /// Whether this matches backtrack or lookahead context. + /// The span receiving the matched positions, or default when they are not needed. + /// The position one past the final matched element. + /// if the entire sequence was matched; otherwise, . + public static bool MatchClassSequence( + SkippingGlyphIterator iterator, + int increment, + ushort[] sequence, + ClassDefinitionTable classDefinitionTable, + uint mask, + bool contextMatch, + Span matches, + out int matchEnd) + { + iterator.SetMatchContext(mask, contextMatch); + int position = iterator.Index; + ShapingBuffer buffer = iterator.Collection; + matchEnd = position + 1; + int i = 0; + + // Class-context matching is one of the hottest complex-script paths. + // Keep its class lookup directly in the loop so every matched element + // avoids a delegate dispatch while retaining the same iterator gates. + if (!iterator.MatchTransparencyActive) + { + int solidOffset = iterator.Increment(increment); + if (solidOffset < 0) + { + return false; + } + + while (i < sequence.Length && i < MaxContextLength && solidOffset < buffer.Count) + { + ref GlyphShapingData solidData = ref buffer[solidOffset]; + if (!iterator.MayMatch(ref solidData) || sequence[i] != classDefinitionTable.ClassIndexOf(solidData.GlyphId)) + { + break; + } + + if (!matches.IsEmpty) + { + matches[i] = solidOffset; + } + + i++; + matchEnd = solidOffset + 1; + solidOffset = iterator.Next(); + } + + iterator.Index = position; + return i == sequence.Length; + } + + // A single forward step must test the immediately following transparent + // record against the first class before deciding whether to skip it. + int offset; + if (increment == 1) + { + offset = position + 1; + iterator.Index = offset; + } + else + { + offset = iterator.Increment(increment); + } + + if (offset < 0) + { + return false; + } + + // Property-skipped records never participate. A transparent record can + // match its named class; otherwise it is stepped over, while a solid + // class mismatch refuses the rule. + while (i < sequence.Length && i < MaxContextLength && offset < buffer.Count) + { + if (iterator.IsPropertySkipped(offset)) + { + offset = ++iterator.Index; + continue; + } + + ref GlyphShapingData data = ref buffer[offset]; + if (iterator.MayMatch(ref data) && sequence[i] == classDefinitionTable.ClassIndexOf(data.GlyphId)) + { + if (!matches.IsEmpty) + { + matches[i] = offset; + } + + i++; + matchEnd = offset + 1; + offset = ++iterator.Index; + continue; + } + + if (!iterator.IsTransparent(ref data)) + { + break; + } + + offset = ++iterator.Index; + } + + iterator.Index = position; + return i == sequence.Length; + } /// - /// Matches a forward glyph sequence using coverage tables. + /// Matches a forward glyph sequence using coverage tables under the given + /// matcher context. /// /// The skipping glyph iterator. /// The array of coverage tables to match against. - /// The starting index in the collection. - /// The exclusive end index in the collection. + /// The starting index in the buffer. + /// The exclusive end index in the buffer. + /// The applying lookup's mask; ignored during context matching. + /// Whether this matches lookahead context. /// if all coverage tables matched; otherwise, . public static bool MatchCoverageSequence( SkippingGlyphIterator iterator, CoverageTable[] coverageTable, int startIndex, - int endExclusive) - => Match( - iterator, - startIndex, - coverageTable, - MatchDirection.Forward, - endExclusive, - (component, data) => component.CoverageIndexOf(data.GlyphId) >= 0, - default); + int endExclusive, + uint mask, + bool contextMatch) + => MatchCoverageSequence(iterator, coverageTable, startIndex, endExclusive, mask, contextMatch, out _); /// - /// Matches a backward (backtrack) glyph sequence using coverage tables. - /// Per the spec, backtrack[0] matches i-1, then i-2, and so on. + /// Matches a forward glyph sequence using coverage tables under the given + /// matcher context, reporting the position one past the final matched element. + /// + /// The skipping glyph iterator. + /// The array of coverage tables to match against. + /// The starting index in the buffer. + /// The exclusive end index in the buffer. + /// The applying lookup's mask; ignored during context matching. + /// Whether this matches lookahead context. + /// The position one past the final matched element. + /// if all coverage tables matched; otherwise, . + public static bool MatchCoverageSequence( + SkippingGlyphIterator iterator, + CoverageTable[] coverageTable, + int startIndex, + int endExclusive, + uint mask, + bool contextMatch, + out int matchEnd) + => MatchCoverageSequence(iterator, coverageTable, startIndex, endExclusive, mask, contextMatch, default, out matchEnd); + + /// + /// Matches a forward glyph sequence using coverage tables under the given + /// matcher context, recording where each element matched so nested lookups + /// apply to the records the match actually consumed. /// /// The skipping glyph iterator. + /// The array of coverage tables to match against. + /// The starting index in the buffer. + /// The exclusive end index in the buffer. + /// The applying lookup's mask; ignored during context matching. + /// Whether this matches lookahead context. + /// The span receiving the matched positions, or default when they are not needed. + /// The position one past the final matched element. + /// if all coverage tables matched; otherwise, . + public static bool MatchCoverageSequence( + SkippingGlyphIterator iterator, + CoverageTable[] coverageTable, + int startIndex, + int endExclusive, + uint mask, + bool contextMatch, + Span matches, + out int matchEnd) + { + iterator.SetMatchContext(mask, contextMatch); + return Match(iterator, startIndex, coverageTable, MatchDirection.Forward, endExclusive, matches, out matchEnd); + } + + /// + /// Matches a backward (backtrack) glyph sequence using coverage tables. + /// Per the spec, backtrack[0] matches i-1, then i-2, and so on. Backtrack is + /// always context, so joiners are transparent to it. + /// + /// The skipping glyph iterator, already stamped at the applying glyph for backtrack matching. /// The array of backtrack coverage tables to match against. - /// The starting index in the collection (the first backtrack position). - /// The exclusive end index in the collection. + /// The starting index in the buffer (the first backtrack position). + /// The exclusive end index in the buffer. /// if all backtrack coverage tables matched; otherwise, . public static bool MatchBacktrackCoverageSequence( SkippingGlyphIterator iterator, CoverageTable[] backtrack, int startIndex, int endExclusive) - => Match( - iterator, - startIndex, - backtrack, - MatchDirection.Backward, - endExclusive, - (component, data) => component.CoverageIndexOf(data.GlyphId) >= 0, - default); + { + return Match(iterator, startIndex, backtrack, MatchDirection.Backward, endExclusive, default, out _); + } + + /// + /// Matches a backtrack sequence by class value over the records the pass has + /// produced. The class table travels as match state so the lambda stays + /// static: a capturing lambda here would allocate on every rule attempt. + /// + /// The skipping glyph iterator, already pointed at the produced side. + /// The first backtrack position. + /// The array of class values to match. + /// The class definition table for the backtrack sequence. + /// if the entire sequence was matched; otherwise, . + private static bool MatchBacktrackClassSequence( + SkippingGlyphIterator iterator, + int startIndex, + ushort[] sequence, + ClassDefinitionTable classDefinitionTable) + { + if (sequence.Length == 0) + { + return true; + } + + int offset = startIndex; + int limit = iterator.RecordCount; + int i = 0; + while (i < sequence.Length && i < MaxContextLength) + { + if (offset < 0 || offset >= limit) + { + return false; + } + + if (iterator.IsPropertySkipped(offset)) + { + offset--; + continue; + } + + ref GlyphShapingData data = ref iterator.RecordAt(offset); + if (iterator.MayMatch(ref data) && sequence[i] == classDefinitionTable.ClassIndexOf(data.GlyphId)) + { + i++; + offset--; + continue; + } + + if (!iterator.IsTransparent(ref data)) + { + return false; + } + + offset--; + } + + return true; + } /// /// Applies a chained sequence rule by matching backtrack, input, and lookahead glyph ID sequences. + /// The input matches under the applying lookup's mask and joiner handling; + /// backtrack and lookahead match as context, transparent to joiners. /// /// The skipping glyph iterator. /// The chained sequence rule table to apply. + /// The applying lookup's mask. + /// The span receiving the matched input positions, offset by one for the coverage-matched first glyph. + /// The position one past the final matched input record. /// if all sequences matched; otherwise, . - public static bool ApplyChainedSequenceRule(SkippingGlyphIterator iterator, ChainedSequenceRuleTable rule) + public static bool ApplyChainedSequenceRule(SkippingGlyphIterator iterator, ChainedSequenceRuleTable rule, uint mask, Span matches, out int matchEnd) { - if (rule.BacktrackSequence.Length > 0 - && !MatchSequence(iterator, -rule.BacktrackSequence.Length, rule.BacktrackSequence)) + matchEnd = iterator.Index + 1; + if (rule.InputSequence.Length > 0 + && !MatchSequence(iterator, 1, rule.InputSequence, mask, false, matches, out matchEnd)) { return false; } - if (rule.InputSequence.Length > 0 - && !MatchSequence(iterator, 1, rule.InputSequence)) + if (rule.LookaheadSequence.Length > 0) { - return false; + // Lookahead starts exactly one past the final matched input element, + // not a stepped jump over the input count. + SkippingGlyphIterator lookahead = iterator; + lookahead.Index = matchEnd - 1; + if (!MatchSequence(lookahead, 1, rule.LookaheadSequence, 0, true)) + { + return false; + } } - if (rule.LookaheadSequence.Length > 0 - && !MatchSequence(iterator, 1 + rule.InputSequence.Length, rule.LookaheadSequence)) + if (rule.BacktrackSequence.Length > 0) { - return false; + SkippingGlyphIterator backIt = iterator; + int backtrackStart = backIt.StartBacktrack(); + if (!Match(backIt, backtrackStart, rule.BacktrackSequence, MatchDirection.Backward, int.MaxValue, default, out _)) + { + return false; + } } return true; @@ -332,36 +727,55 @@ public static bool ApplyChainedSequenceRule(SkippingGlyphIterator iterator, Chai /// /// Applies a chained class sequence rule by matching backtrack, input, and lookahead class sequences. + /// The input matches under the applying lookup's mask and joiner handling; + /// backtrack and lookahead match as context, transparent to joiners. /// /// The skipping glyph iterator. /// The chained class sequence rule table to apply. /// The class definition table for the input sequence. /// The class definition table for the backtrack sequence. /// The class definition table for the lookahead sequence. + /// The applying lookup's mask. + /// The span receiving the matched input positions, offset by one for the coverage-matched first glyph. + /// The position one past the final matched input record. /// if all sequences matched; otherwise, . public static bool ApplyChainedClassSequenceRule( SkippingGlyphIterator iterator, ChainedClassSequenceRuleTable rule, ClassDefinitionTable inputClassDefinitionTable, ClassDefinitionTable backtrackClassDefinitionTable, - ClassDefinitionTable lookaheadClassDefinitionTable) + ClassDefinitionTable lookaheadClassDefinitionTable, + uint mask, + Span matches, + out int matchEnd) { - if (rule.BacktrackSequence.Length > 0 - && !MatchClassSequence(iterator, -rule.BacktrackSequence.Length, rule.BacktrackSequence, backtrackClassDefinitionTable)) + matchEnd = iterator.Index + 1; + if (rule.InputSequence.Length > 0 && + !MatchClassSequence(iterator, 1, rule.InputSequence, inputClassDefinitionTable, mask, false, matches, out matchEnd)) { return false; } - if (rule.InputSequence.Length > 0 && - !MatchClassSequence(iterator, 1, rule.InputSequence, inputClassDefinitionTable)) + if (rule.LookaheadSequence.Length > 0) { - return false; + // Lookahead starts exactly one past the final matched input element, + // not a stepped jump over the input count. + SkippingGlyphIterator lookahead = iterator; + lookahead.Index = matchEnd - 1; + if (!MatchClassSequence(lookahead, 1, rule.LookaheadSequence, lookaheadClassDefinitionTable, 0, true)) + { + return false; + } } - if (rule.LookaheadSequence.Length > 0 - && !MatchClassSequence(iterator, 1 + rule.InputSequence.Length, rule.LookaheadSequence, lookaheadClassDefinitionTable)) + if (rule.BacktrackSequence.Length > 0) { - return false; + SkippingGlyphIterator backIt = iterator; + int backtrackStart = backIt.StartBacktrack(); + if (!MatchBacktrackClassSequence(backIt, backtrackStart, rule.BacktrackSequence, backtrackClassDefinitionTable)) + { + return false; + } } return true; @@ -373,60 +787,55 @@ public static bool ApplyChainedClassSequenceRule( /// The font metrics. /// The lookup flags for glyph filtering. /// The mark filtering set index. - /// The glyph shaping collection. + /// The glyph shaping buffer. /// The starting index of the input sequence. /// The number of glyphs available from the starting index. /// The array of input coverage tables. /// The array of backtrack coverage tables. /// The array of lookahead coverage tables. + /// The applying lookup's mask; the input matches under it. + /// The span receiving the matched input positions; the input coverage array covers the whole input including its first glyph. + /// The position one past the final matched input record. /// if all coverages matched; otherwise, . public static bool CheckAllCoverages( FontMetrics fontMetrics, LookupFlags lookupFlags, ushort markFilteringSet, - IGlyphShapingCollection collection, + ShapingBuffer buffer, int index, int count, CoverageTable[] input, CoverageTable[] backtrack, - CoverageTable[] lookahead) + CoverageTable[] lookahead, + uint mask, + Span matches, + out int matchEnd) { int endExclusive = index + count; - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, lookupFlags, markFilteringSet); + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, lookupFlags, markFilteringSet); - // Compute backtrack start using skippy prev(), not index-1. - int backtrackStart = index; + // Backtrack steps back through the records the pass produced, skipping + // as context so joiners are transparent to it. + matchEnd = index; if (backtrack.Length > 0) { SkippingGlyphIterator backIt = iterator; - backIt.Index = index; - backtrackStart = backIt.Prev(); // first backtrack glyph (i-1 in skippy space) - } - - if (!MatchBacktrackCoverageSequence(iterator, backtrack, backtrackStart, endExclusive)) - { - return false; + int backtrackStart = backIt.StartBacktrack(); + if (!MatchBacktrackCoverageSequence(backIt, backtrack, backtrackStart, int.MaxValue)) + { + return false; + } } - // Input starts at the current glyph position. - if (!MatchCoverageSequence(iterator, input, index, endExclusive)) + // Input starts at the current glyph position; lookahead starts exactly + // one past the final matched input element. + if (!MatchCoverageSequence(iterator, input, index, endExclusive, mask, false, matches, out matchEnd)) { return false; } - // Compute lookahead start by advancing through the input sequence using skippy Next(), - // not by raw index arithmetic. - int lookaheadStart = index; - if (lookahead.Length > 0) - { - SkippingGlyphIterator fwdIt = iterator; - fwdIt.Index = index; - fwdIt.Increment(input.Length); // advance input.Length steps in skippy space - lookaheadStart = fwdIt.Index; - } - - if (!MatchCoverageSequence(iterator, lookahead, lookaheadStart, endExclusive)) + if (!MatchCoverageSequence(iterator, lookahead, matchEnd, endExclusive, 0, true)) { return false; } @@ -438,15 +847,15 @@ public static bool CheckAllCoverages( /// Applies anchor-based positioning for mark-to-base, mark-to-ligature, or mark-to-mark attachment. /// /// The font metrics. - /// The glyph positioning collection. - /// The index of the mark glyph in the collection. + /// The glyph positioning buffer. + /// The index of the mark glyph in the buffer. /// The anchor table for the base glyph, or if no anchor is defined. /// The mark record containing the mark anchor table and class. - /// The index of the base glyph in the collection. + /// The index of the base glyph in the buffer. /// The feature tag being applied. public static void ApplyAnchor( FontMetrics fontMetrics, - GlyphPositioningCollection collection, + ShapingBuffer buffer, int index, AnchorTable? baseAnchor, MarkRecord markRecord, @@ -461,84 +870,190 @@ public static void ApplyAnchor( return; } - GlyphShapingData baseData = collection[baseGlyphIndex]; - AnchorXY baseXY = baseAnchor.GetAnchor(fontMetrics, baseData, collection); + ref GlyphShapingData baseData = ref buffer[baseGlyphIndex]; + AnchorXY baseXY = baseAnchor.GetAnchor(fontMetrics, ref baseData, buffer); - GlyphShapingData markData = collection[index]; - AnchorXY markXY = markRecord.MarkAnchorTable.GetAnchor(fontMetrics, markData, collection); + ref GlyphShapingData markData = ref buffer[index]; + AnchorXY markXY = markRecord.MarkAnchorTable.GetAnchor(fontMetrics, ref markData, buffer); - markData.Bounds.X = baseXY.XCoordinate - markXY.XCoordinate; - markData.Bounds.Y = baseXY.YCoordinate - markXY.YCoordinate; - markData.MarkAttachment = baseGlyphIndex; - markData.AppliedFeatures.Add(feature); + ref GlyphShapingPosition markPosition = ref buffer.PositionAt(index); + markPosition.Bounds.X = baseXY.XCoordinate - markXY.XCoordinate; + markPosition.Bounds.Y = baseXY.YCoordinate - markXY.YCoordinate; + markPosition.MarkAttachment = baseGlyphIndex; + markData.AppliedFeatureMask |= ShapePlanFeatures.GetVerticalMask(feature); } /// - /// Applies a value record's positioning adjustments to a glyph in the collection. + /// Applies a value record's positioning adjustments to a glyph in the buffer. /// /// The font metrics. - /// The glyph positioning collection. - /// The index of the glyph in the collection. + /// The glyph positioning buffer. + /// The index of the glyph in the buffer. /// The value record containing positioning adjustments. /// The feature tag being applied. public static void ApplyPosition( FontMetrics fontMetrics, - GlyphPositioningCollection collection, + ShapingBuffer buffer, int index, ValueRecord record, Tag feature) { - GlyphShapingData current = collection[index]; - current.Bounds.Width += record.XAdvance; - current.Bounds.Height += record.YAdvance; - current.Bounds.X += record.XPlacement; - current.Bounds.Y += record.YPlacement; + ref GlyphShapingPosition position = ref buffer.PositionAt(index); + position.Bounds.Width += record.XAdvance; + position.Bounds.Height += record.YAdvance; + position.Bounds.X += record.XPlacement; + position.Bounds.Y += record.YPlacement; // Apply variation deltas from VariationIndex tables (variable fonts). if (record.HasVariation) { - current.Bounds.X += (short)MathF.Round(fontMetrics.GetGDefVariationDelta(record.XPlacementVariation)); - current.Bounds.Y += (short)MathF.Round(fontMetrics.GetGDefVariationDelta(record.YPlacementVariation)); - current.Bounds.Width += (short)MathF.Round(fontMetrics.GetGDefVariationDelta(record.XAdvanceVariation)); - current.Bounds.Height += (short)MathF.Round(fontMetrics.GetGDefVariationDelta(record.YAdvanceVariation)); + position.Bounds.X += (short)MathF.Round(fontMetrics.GetGDefVariationDelta(record.XPlacementVariation)); + position.Bounds.Y += (short)MathF.Round(fontMetrics.GetGDefVariationDelta(record.YPlacementVariation)); + position.Bounds.Width += (short)MathF.Round(fontMetrics.GetGDefVariationDelta(record.XAdvanceVariation)); + position.Bounds.Height += (short)MathF.Round(fontMetrics.GetGDefVariationDelta(record.YAdvanceVariation)); } - current.AppliedFeatures.Add(feature); + ref GlyphShapingData current = ref buffer[index]; + current.AppliedFeatureMask |= ShapePlanFeatures.GetVerticalMask(feature); } /// - /// Determines whether the specified glyph is a mark glyph based on GDEF class or Unicode properties. + /// Replaces a matched glyph sequence with one ligature while preserving component attachment bookkeeping. /// /// The font metrics. - /// The glyph identifier. - /// The glyph shaping data. - /// if the glyph is a mark; otherwise, . - public static bool IsMarkGlyph(FontMetrics fontMetrics, ushort glyphId, GlyphShapingData shapingData) + /// The shaping buffer. + /// The index of the first matched glyph. + /// The indices of the remaining matched glyphs. + /// The ligature glyph identifier. + /// The feature applying the substitution. + /// The number of glyphs in the current shaping segment. + public static void ApplyLigatureSubstitution(FontMetrics fontMetrics, ShapingBuffer buffer, int index, ReadOnlySpan matches, ushort glyphId, Tag feature, int count) { - if (!fontMetrics.TryGetGlyphClass(glyphId, out GlyphClassDef? glyphClass) && - !CodePoint.IsMark(shapingData.CodePoint)) + ref GlyphShapingData data = ref buffer[index]; + GlyphShapingClass shapingClass = GetGlyphShapingClass(fontMetrics, buffer, data.GlyphId, ref data); + bool isBaseLigature = shapingClass.IsBase; + bool isMarkLigature = shapingClass.IsMark; + + // A base followed only by marks remains a base, and marks combined only + // with marks remain attached to their existing base. Any non-mark + // component turns the result into a new ordinary ligature. + for (int i = 0; i < matches.Length; i++) { - return false; + ref GlyphShapingData match = ref buffer[matches[i]]; + if (!IsMarkGlyph(fontMetrics, match.GlyphId, ref match)) + { + isBaseLigature = false; + isMarkLigature = false; + break; + } } - if (glyphClass != GlyphClassDef.MarkGlyph) + bool isLigature = !isBaseLigature && !isMarkLigature; + + // An ordinary ligature needs a fresh identity so later positioning can + // distinguish its components. A mark ligature keeps the identity and + // component of the mark stack it already belongs to. + int ligatureId = isLigature ? buffer.LigatureId++ : data.LigatureId; + int ligatureComponent = isLigature ? -1 : data.LigatureComponent; + int lastLigatureId = data.LigatureId; + int lastComponentCount = data.LigatureComponentCount; + int currentComponentCount = lastComponentCount; + int nextIndex = index + 1; + + // Matching can skip marks between two components. Move those marks to + // the new ligature identity while translating their old component + // numbers past every component already consumed. + foreach (int matchIndex in matches) { - return false; + if (isLigature) + { + while (nextIndex < matchIndex) + { + ref GlyphShapingData current = ref buffer[nextIndex]; + int currentComponent = current.LigatureComponent == -1 ? lastComponentCount : current.LigatureComponent; + current.LigatureId = ligatureId; + current.LigatureComponent = currentComponentCount - lastComponentCount + Math.Min(currentComponent, lastComponentCount); + nextIndex++; + } + } + else + { + nextIndex = matchIndex; + } + + ref GlyphShapingData last = ref buffer[nextIndex]; + lastLigatureId = last.LigatureId; + lastComponentCount = last.LigatureComponentCount; + currentComponentCount += lastComponentCount; + nextIndex++; } - return true; + // A matched component can itself be a ligature whose attached marks + // follow the final matched glyph. Translate only its contiguous component + // records: a shared identity without a component marks the end of that + // attachment tail just as surely as a different identity does. + if (!isMarkLigature && lastLigatureId > 0) + { + int end = index + count; + for (int i = nextIndex; i < end; i++) + { + ref GlyphShapingData current = ref buffer[i]; + if (current.LigatureId != lastLigatureId) + { + break; + } + + int currentComponent = current.LigatureComponent; + if (currentComponent < 0) + { + break; + } + + current.LigatureId = ligatureId; + current.LigatureComponent = currentComponentCount - lastComponentCount + Math.Min(currentComponent, lastComponentCount); + } + } + + // The matched component total is distinct from the text span represented by + // the glyph because marks ignored by the lookup remain separate records. + if (isLigature) + { + data.LigatureComponentCount = currentComponentCount; + } + + buffer.Replace(index, matches, glyphId, ligatureId, ligatureComponent, feature); + } + + /// + /// Determines whether the specified glyph is a mark glyph based on its font-defined class or the fallback Unicode classification. + /// + /// The font metrics. + /// The glyph identifier. + /// The glyph shaping data. + /// if the glyph is a mark; otherwise, . + public static bool IsMarkGlyph(FontMetrics fontMetrics, ushort glyphId, ref GlyphShapingData shapingData) + { + if (fontMetrics.TryGetGlyphClass(glyphId, out GlyphClassDef? glyphClass)) + { + return glyphClass == GlyphClassDef.MarkGlyph; + } + + return IsFallbackMark(ref shapingData); } /// /// Gets the glyph shaping class (mark, base, ligature, mark attachment type) for the specified glyph, /// using GDEF table data if available or falling back to Unicode properties. - /// Results are cached on the instance. + /// Results are cached on the instance, with + /// table-derived classes additionally cached on the buffer so re-classification + /// after a substitution changes a glyph id skips the table walks. /// /// The font metrics. + /// The glyph shaping buffer carrying the class cache. /// The glyph identifier. /// The glyph shaping data, used for caching and Unicode fallback. /// The . - public static GlyphShapingClass GetGlyphShapingClass(FontMetrics fontMetrics, ushort glyphId, GlyphShapingData shapingData) + public static GlyphShapingClass GetGlyphShapingClass(FontMetrics fontMetrics, ShapingBuffer buffer, ushort glyphId, ref GlyphShapingData shapingData) { // Cache the shaping class on the GlyphShapingData to avoid repeated GDEF lookups. // The cache key stores the glyph id; -1 means "not cached". @@ -547,15 +1062,24 @@ public static GlyphShapingClass GetGlyphShapingClass(FontMetrics fontMetrics, us return shapingData.CachedShapingClass; } + if (buffer.TryGetShapingClass(fontMetrics, glyphId, out GlyphShapingClass cached)) + { + shapingData.CachedShapingClass = cached; + shapingData.ShapingClassCacheKey = glyphId; + return cached; + } + bool isMark; bool isBase; bool isLigature; + bool tableDerived = false; ushort markAttachmentType = 0; if (fontMetrics.TryGetGlyphClass(glyphId, out GlyphClassDef? glyphClass)) { isMark = glyphClass == GlyphClassDef.MarkGlyph; isBase = glyphClass == GlyphClassDef.BaseGlyph; isLigature = glyphClass == GlyphClassDef.LigatureGlyph; + tableDerived = true; if (fontMetrics.TryGetMarkAttachmentClass(glyphId, out GlyphClassDef? markAttachmentClass)) { markAttachmentType = (ushort)markAttachmentClass; @@ -564,17 +1088,72 @@ public static GlyphShapingClass GetGlyphShapingClass(FontMetrics fontMetrics, us else { // TODO: We may have to store each codepoint. FontKit checks all. - isMark = CodePoint.IsMark(shapingData.CodePoint); + isMark = IsFallbackMark(ref shapingData); isBase = !isMark; isLigature = shapingData.CodePointCount > 1; } GlyphShapingClass result = new(isMark, isBase, isLigature, markAttachmentType); + if (tableDerived) + { + buffer.SetShapingClass(glyphId, result); + } + shapingData.CachedShapingClass = result; shapingData.ShapingClassCacheKey = glyphId; return result; } + /// + /// Determines whether a glyph without a font-defined class receives the fallback mark class. + /// + /// The Unicode properties carried by the glyph. + /// when the glyph receives the mark class; otherwise, . + private static bool IsFallbackMark(ref GlyphShapingData shapingData) + { + // Spacing and enclosing marks retain their advances when a font supplies no + // glyph classes. Default-ignorables are also bases so they remain visible to + // lookup matching instead of being skipped by mark-specific lookup flags. + return CodePoint.GetGeneralCategory(shapingData.CodePoint) == UnicodeCategory.NonSpacingMark + && !shapingData.IsDefaultIgnorable; + } + + /// + /// Zeros the advances of mark glyphs in a positioned segment. + /// + /// The font metrics supplying glyph classes. + /// The glyph shaping buffer. + /// The zero-based index of the segment. + /// The number of glyphs in the segment. + /// Whether to move each mark back by its original advance before zeroing it. + public static void ZeroMarkAdvances(FontMetrics fontMetrics, ShapingBuffer buffer, int index, int count, bool adjustOffsets) + { + int end = index + count; + for (int i = index; i < end; i++) + { + ref GlyphShapingData data = ref buffer[i]; + if (!IsMarkGlyph(fontMetrics, data.GlyphId, ref data)) + { + continue; + } + + ref GlyphShapingPosition position = ref buffer.PositionAt(i); + if (adjustOffsets + && (data.Direction == TextDirection.LeftToRight || buffer.TextOptions.LayoutMode.IsVertical())) + { + // With no positioning table, a forward-flowing mark hangs over the + // preceding glyph after its advance is removed. Browsers normalize + // bottom-to-top runs to forward top-to-bottom shaping, so pure + // vertical layout follows the same offset adjustment. + position.Bounds.X -= position.Bounds.Width; + position.Bounds.Y -= position.Bounds.Height; + } + + position.Bounds.Width = 0; + position.Bounds.Height = 0; + } + } + /// /// Determines whether the specified glyph is in the given mark filtering set. /// @@ -589,43 +1168,119 @@ public static bool IsInMarkFilteringSet(FontMetrics fontMetrics, ushort markFilt /// Matches a sequence of elements against glyphs using an increment-based approach. /// /// The type of sequence elements to match. + /// The compile-time comparison used for each sequence element. /// The initial increment from the iterator's current position. /// The array of elements to match. /// The skipping glyph iterator. - /// The condition function to test each element against glyph data. /// A span to store matched glyph indices, or default if not needed. /// if all elements in the sequence were matched; otherwise, . - private static bool Match( - int increment, - T[] sequence, - SkippingGlyphIterator iterator, - Func condition, - Span matches) + private static bool Match(int increment, T[] sequence, SkippingGlyphIterator iterator, Span matches) + where TMatcher : ISequenceMatcher + => Match(increment, sequence, iterator, matches, out _); + + /// + /// Matches a sequence of elements against glyphs using an increment-based + /// approach, reporting the position one past the final matched element so + /// lookahead can start exactly where the input ended. + /// + /// The type of sequence elements to match. + /// The compile-time comparison used for each sequence element. + /// The initial increment from the iterator's current position. + /// The array of elements to match. + /// The skipping glyph iterator. + /// A span to store matched glyph indices, or default if not needed. + /// The position one past the final matched element. + /// if all elements in the sequence were matched; otherwise, . + private static bool Match(int increment, T[] sequence, SkippingGlyphIterator iterator, Span matches, out int matchEnd) + where TMatcher : ISequenceMatcher { int position = iterator.Index; - int offset = iterator.Increment(increment); - IGlyphShapingCollection collection = iterator.Collection; + ShapingBuffer buffer = iterator.Collection; + matchEnd = position + 1; + int i = 0; + + // A buffer without a transparent record in reach keeps the solid-glyph + // walk: every stepped-to record must match or the rule is refused. + if (!iterator.MatchTransparencyActive) + { + int solidOffset = iterator.Increment(increment); + if (solidOffset < 0) + { + return false; + } + + while (i < sequence.Length && i < MaxContextLength && solidOffset < buffer.Count) + { + ref GlyphShapingData solidData = ref buffer[solidOffset]; + if (!iterator.MayMatch(ref solidData) || !TMatcher.Matches(sequence[i], ref solidData)) + { + break; + } + + if (matches.Length == MaxContextLength) + { + matches[i] = solidOffset; + } + + i++; + matchEnd = solidOffset + 1; + solidOffset = iterator.Next(); + } + + iterator.Index = position; + return i == sequence.Length; + } + + // A single forward step enters the walk directly so a transparent record + // at the next position still gets its chance to match the first sequence + // element; larger jumps step over positions other matchers consumed. + int offset; + if (increment == 1) + { + offset = position + 1; + iterator.Index = offset; + } + else + { + offset = iterator.Increment(increment); + } if (offset < 0) { return false; } - int i = 0; - while (i < sequence.Length && i < MaxContextLength && offset < collection.Count) + // A transparent record is stepped over unless it matches the sequence + // position itself; a solid record that fails the shape test refuses the + // whole match. + while (i < sequence.Length && i < MaxContextLength && offset < buffer.Count) { - if (!condition(sequence[i], collection[offset])) + if (iterator.IsPropertySkipped(offset)) { - break; + offset = ++iterator.Index; + continue; } - if (matches.Length == MaxContextLength) + ref GlyphShapingData data = ref buffer[offset]; + if (iterator.MayMatch(ref data) && TMatcher.Matches(sequence[i], ref data)) { - matches[i] = iterator.Index; + if (matches.Length == MaxContextLength) + { + matches[i] = offset; + } + + i++; + matchEnd = offset + 1; + offset = ++iterator.Index; + continue; } - i++; - offset = iterator.Next(); + if (!iterator.IsTransparent(ref data)) + { + break; + } + + offset = ++iterator.Index; } iterator.Index = position; @@ -636,63 +1291,91 @@ private static bool Match( /// Matches a sequence of elements against glyphs using a directional (forward/backward) approach. /// /// The type of sequence elements to match. + /// The compile-time comparison used for each sequence element. /// The skipping glyph iterator. - /// The starting index in the collection. + /// The starting index in the buffer. /// The array of elements to match. /// The direction to iterate (forward or backward). - /// The exclusive end index in the collection. - /// The condition function to test each element against glyph data. + /// The exclusive end index in the buffer. /// A span to store matched glyph indices, or default if not needed. + /// The position one past the final matched element; meaningful for forward matching. /// if all elements in the sequence were matched; otherwise, . - private static bool Match( - SkippingGlyphIterator iterator, - int startIndex, - T[] sequence, - MatchDirection direction, - int endExclusive, - Func condition, - Span matches) + private static bool Match(SkippingGlyphIterator iterator, int startIndex, T[] sequence, MatchDirection direction, int endExclusive, Span matches, out int matchEnd) + where TMatcher : ISequenceMatcher { + matchEnd = startIndex; if (sequence.Length == 0) { return true; } int saved = iterator.Index; - iterator.Index = startIndex; + int offset = startIndex; + int step = direction == MatchDirection.Forward ? 1 : -1; - IGlyphShapingCollection collection = iterator.Collection; - int limit = Math.Min(endExclusive, collection.Count); + // Backtrack reads the records the pass produced, so that side bounds it. + int limit = Math.Min(endExclusive, iterator.RecordCount); - for (int i = 0; i < sequence.Length && i < MaxContextLength; i++) + // A transparent record is stepped over unless it matches the sequence + // position itself; a solid record that fails the shape test refuses the + // whole match. + int i = 0; + while (i < sequence.Length && i < MaxContextLength) { - if (iterator.Index < 0 || iterator.Index >= limit) + if (offset < 0 || offset >= limit) { iterator.Index = saved; return false; } - GlyphShapingData data = collection[iterator.Index]; - if (!condition(sequence[i], data)) + if (iterator.IsPropertySkipped(offset)) { - iterator.Index = saved; - return false; + offset += step; + continue; } - if (matches.Length == MaxContextLength) + ref GlyphShapingData data = ref iterator.RecordAt(offset); + if (iterator.MayMatch(ref data) && TMatcher.Matches(sequence[i], ref data)) { - matches[i] = iterator.Index; + if (matches.Length == MaxContextLength) + { + matches[i] = offset; + } + + i++; + matchEnd = offset + 1; + offset += step; + continue; } - if (i + 1 < sequence.Length) + if (!iterator.IsTransparent(ref data)) { - iterator.Index = direction == MatchDirection.Forward - ? iterator.Next() - : iterator.Prev(); + iterator.Index = saved; + return false; } + + offset += step; } iterator.Index = saved; return true; } + + /// + /// Compares a sequence glyph identifier with the current glyph record. + /// + private readonly struct GlyphIdSequenceMatcher : ISequenceMatcher + { + /// + public static bool Matches(ushort element, ref GlyphShapingData data) => element == data.GlyphId; + } + + /// + /// Tests the current glyph record against a sequence coverage table. + /// + private readonly struct CoverageSequenceMatcher : ISequenceMatcher + { + /// + public static bool Matches(CoverageTable element, ref GlyphShapingData data) => element.CoverageIndexOf(data.GlyphId) >= 0; + } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/BaseTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/BaseTable.cs index 3a41ba6f0..1e0a63ea3 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/BaseTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/BaseTable.cs @@ -257,7 +257,7 @@ public static BaseAxisTable Load(BigEndianBinaryReader reader, long offset) // +--------+----------------------------+--------------------------------------------------------------------+ // | Tag | baselineTags[baseTagCount] | Array of 4-byte baseline identification tags, in alphabetical order | // +--------+----------------------------+--------------------------------------------------------------------+ - Tag[] baselineTags = Array.Empty(); + Tag[] baselineTags = []; if (baseTagListOffset != 0) { reader.Seek(offset + baseTagListOffset, SeekOrigin.Begin); @@ -292,7 +292,7 @@ public static BaseAxisTable Load(BigEndianBinaryReader reader, long offset) reader.Seek(scriptListStart, SeekOrigin.Begin); ushort baseScriptCount = reader.ReadUInt16(); - var scriptTags = new Tag[baseScriptCount]; + Tag[] scriptTags = new Tag[baseScriptCount]; ushort[] scriptOffsets = new ushort[baseScriptCount]; for (int i = 0; i < scriptTags.Length; i++) { @@ -300,7 +300,7 @@ public static BaseAxisTable Load(BigEndianBinaryReader reader, long offset) scriptOffsets[i] = reader.ReadOffset16(); } - var scripts = new BaseScriptEntry[baseScriptCount]; + BaseScriptEntry[] scripts = new BaseScriptEntry[baseScriptCount]; for (int i = 0; i < scripts.Length; i++) { scripts[i] = BaseScriptEntry.Load(scriptTags[i], reader, scriptListStart + scriptOffsets[i]); diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/ClassDefinitionTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ClassDefinitionTable.cs index 1d5399ce4..4c4b94623 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/ClassDefinitionTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ClassDefinitionTable.cs @@ -134,14 +134,56 @@ public override int ClassIndexOf(ushort glyphId) /// internal sealed class ClassDefinitionFormat2Table : ClassDefinitionTable { + /// + /// The number of direct-mapped cache slots; must be a power of two so the slot + /// mask below selects one. + /// + private const int ClassCacheSlots = 128; + + /// + /// The mask selecting a cache slot from a glyph id. + /// + private const int ClassCacheSlotMask = ClassCacheSlots - 1; + + /// + /// The bit offset of the class value in a cache entry; the glyph id key occupies + /// the low half. + /// + private const int ClassCacheClassShift = 16; + + /// + /// The mask extracting the glyph id key from a cache entry. + /// + private const uint ClassCacheGlyphMask = 0xFFFF; + + /// + /// The one glyph id excluded from caching: the empty-slot sentinel carries this + /// id, so allowing it to cache could serve the sentinel as a real entry. + /// + private const ushort UncacheableGlyphId = 0xFFFF; + private readonly ClassRangeRecord[] records; + /// + /// Direct-mapped glyph-to-class cache in front of the range binary search: one + /// word per slot packing the glyph id key and the class value. Entries start at + /// the all-ones sentinel, which can never match because the sentinel glyph id is + /// excluded from caching. Aligned 32-bit writes are atomic, so concurrent + /// shaping passes can race on a slot and only ever lose an update, never read a + /// torn entry. + /// + private readonly uint[] classCache; + /// /// Initializes a new instance of the class. /// /// The array of class range records. private ClassDefinitionFormat2Table(ClassRangeRecord[] records) - => this.records = records; + { + this.records = records; + this.classCache = new uint[ClassCacheSlots]; + Array.Fill(this.classCache, uint.MaxValue); + } /// /// Loads a from the binary reader. @@ -186,9 +228,22 @@ public static ClassDefinitionFormat2Table Load(BigEndianBinaryReader reader) /// public override int ClassIndexOf(ushort glyphId) { + uint[] cache = this.classCache; + int slot = glyphId & ClassCacheSlotMask; + if (glyphId != UncacheableGlyphId) + { + uint entry = cache[slot]; + if ((entry & ClassCacheGlyphMask) == glyphId) + { + return (int)(entry >> ClassCacheClassShift); + } + } + // Records are ordered by StartGlyphId, so use binary search to find the - // candidate range whose StartGlyphId is <= glyphId. + // candidate range whose StartGlyphId is <= glyphId. Any glyph not included + // in the range of covered glyph IDs automatically belongs to Class 0. ClassRangeRecord[] records = this.records; + int result = 0; int lo = 0; int hi = records.Length - 1; while (lo <= hi) @@ -205,11 +260,16 @@ public override int ClassIndexOf(ushort glyphId) } else { - return rec.Class; + result = rec.Class; + break; } } - // Any glyph not included in the range of covered glyph IDs automatically belongs to Class 0. - return 0; + if (glyphId != UncacheableGlyphId) + { + cache[slot] = (uint)(glyphId | ((uint)result << ClassCacheClassShift)); + } + + return result; } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/CoverageTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/CoverageTable.cs index 2b83c6a35..8ef7808c9 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/CoverageTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/CoverageTable.cs @@ -22,6 +22,12 @@ internal abstract class CoverageTable /// The zero-based coverage index, or -1 if not found. public abstract int CoverageIndexOf(ushort glyphId); + /// + /// Adds every glyph this table covers to the digest. + /// + /// The digest to add to. + public abstract void CollectDigest(ref GlyphSetDigest digest); + /// /// Loads a from the binary reader at the specified offset. /// @@ -83,6 +89,15 @@ public override int CoverageIndexOf(ushort glyphId) return n < 0 ? -1 : n; } + /// + public override void CollectDigest(ref GlyphSetDigest digest) + { + foreach (ushort glyphId in this.glyphArray) + { + digest.Add(glyphId); + } + } + /// /// Loads a from the binary reader. /// The format identifier has already been read. @@ -150,6 +165,15 @@ public override int CoverageIndexOf(ushort glyphId) return -1; } + /// + public override void CollectDigest(ref GlyphSetDigest digest) + { + foreach (CoverageRangeRecord record in this.records) + { + digest.AddRange(record.StartGlyphId, record.EndGlyphId); + } + } + /// /// Loads a from the binary reader. /// The format identifier has already been read. @@ -209,5 +233,11 @@ private EmptyCoverageTable() /// public override int CoverageIndexOf(ushort glyphId) => -1; + + /// + public override void CollectDigest(ref GlyphSetDigest digest) + { + // This table never matches a glyph, so contributing nothing is exact. + } } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/FallbackMarkPositioner.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/FallbackMarkPositioner.cs new file mode 100644 index 000000000..37c6aa567 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/FallbackMarkPositioner.cs @@ -0,0 +1,547 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// Positions combining marks from glyph extents when a script permits fallback positioning and the font has no positioning table. +/// +internal static class FallbackMarkPositioner +{ + /// + /// The first canonical combining class whose value directly describes an attachment position. + /// + private const int FirstPositioningClass = 200; + + /// + /// The attached-below-left combining class. + /// + private const int AttachedBelowLeft = 200; + + /// + /// The attached-below combining class. + /// + private const int AttachedBelow = 202; + + /// + /// The attached-above combining class. + /// + private const int AttachedAbove = 214; + + /// + /// The attached-above-right combining class. + /// + private const int AttachedAboveRight = 216; + + /// + /// The below-left combining class. + /// + private const int BelowLeft = 218; + + /// + /// The below combining class. + /// + private const int Below = 220; + + /// + /// The below-right combining class. + /// + private const int BelowRight = 222; + + /// + /// The above-left combining class. + /// + private const int AboveLeft = 228; + + /// + /// The above combining class. + /// + private const int Above = 230; + + /// + /// The above-right combining class. + /// + private const int AboveRight = 232; + + /// + /// The double-below combining class. + /// + private const int DoubleBelow = 233; + + /// + /// The double-above combining class. + /// + private const int DoubleAbove = 234; + + /// + /// The divisor defining the vertical gap between a base and an unattached mark. + /// + private const int VerticalGapDivisor = 16; + + /// + /// The shared high-byte prefix of the Thai and Lao blocks. + /// + private const int ThaiLaoBlockPrefix = 0x0E00; + + /// + /// The Thai character MAI HAN-AKAT. + /// + private const int ThaiMaiHanAkat = 0x0E31; + + /// + /// The Thai character PHINTHU. + /// + private const int ThaiPhinthu = 0x0E3A; + + /// + /// The Thai character SARA I. + /// + private const int ThaiSaraI = 0x0E34; + + /// + /// The Thai character SARA II. + /// + private const int ThaiSaraIi = 0x0E35; + + /// + /// The Thai character SARA UE. + /// + private const int ThaiSaraUe = 0x0E36; + + /// + /// The Thai character SARA UEE. + /// + private const int ThaiSaraUee = 0x0E37; + + /// + /// The Thai character MAITAIKHU. + /// + private const int ThaiMaiTaikhu = 0x0E47; + + /// + /// The Thai character THANTHAKHAT. + /// + private const int ThaiThanthakhat = 0x0E4C; + + /// + /// The Thai character NIKHAHIT. + /// + private const int ThaiNikhahit = 0x0E4D; + + /// + /// The Thai character YAMAKKAN. + /// + private const int ThaiYamakkan = 0x0E4E; + + /// + /// The Lao vowel sign MAI KAN. + /// + private const int LaoMaiKan = 0x0EB1; + + /// + /// The Lao vowel sign I. + /// + private const int LaoVowelSignI = 0x0EB4; + + /// + /// The Lao vowel sign II. + /// + private const int LaoVowelSignIi = 0x0EB5; + + /// + /// The Lao vowel sign Y. + /// + private const int LaoVowelSignY = 0x0EB6; + + /// + /// The Lao vowel sign YY. + /// + private const int LaoVowelSignYy = 0x0EB7; + + /// + /// The Lao vowel sign MAI KON. + /// + private const int LaoMaiKon = 0x0EBB; + + /// + /// The Lao semivowel sign LO. + /// + private const int LaoSemivowelLo = 0x0EBC; + + /// + /// The Lao cancellation mark. + /// + private const int LaoCancellationMark = 0x0ECC; + + /// + /// The Lao NIGGAHITA. + /// + private const int LaoNiggahita = 0x0ECD; + + /// + /// Applies fallback mark positioning to every eligible segment belonging to the font. + /// + /// The font metrics supplying extents and advances. + /// The positioned shaping buffer. + public static void Apply(FontMetrics fontMetrics, ShapingBuffer buffer) + { + List<(int Index, int Count, ScriptClass Script, ShapePlan Plan)> segments = buffer.SegmentPlans; + for (int i = 0; i < segments.Count; i++) + { + (int index, int count, ScriptClass _, ShapePlan plan) = segments[i]; + if (plan.FontMetrics == fontMetrics && plan.Shaper.FallbackMarkPositioning) + { + PositionSegment(buffer, index, count, fontMetrics.UnitsPerEm); + } + } + } + + /// + /// Divides a segment at each visible non-mark and positions the marks following each base. + /// + /// The positioned shaping buffer. + /// The zero-based index of the first record. + /// The number of records in the segment. + /// The font's units per em. + private static void PositionSegment(ShapingBuffer buffer, int index, int count, int unitsPerEm) + { + int start = index; + int end = index + count; + for (int i = index + 1; i < end; i++) + { + ref GlyphShapingData data = ref buffer[i]; + if (!CodePoint.IsMark(data.CodePoint) && !data.IsHidden && !data.IsDefaultIgnorable) + { + PositionCluster(buffer, start, i, unitsPerEm); + start = i; + } + } + + PositionCluster(buffer, start, end, unitsPerEm); + } + + /// + /// Finds each base in a character cluster and positions its following marks. + /// + /// The positioned shaping buffer. + /// The zero-based cluster start. + /// The exclusive cluster end. + /// The font's units per em. + private static void PositionCluster(ShapingBuffer buffer, int start, int end, int unitsPerEm) + { + if (end - start < 2) + { + return; + } + + for (int i = start; i < end; i++) + { + if (CodePoint.IsMark(buffer[i].CodePoint)) + { + continue; + } + + int markEnd = i + 1; + while (markEnd < end) + { + ref GlyphShapingData data = ref buffer[markEnd]; + if (!data.IsHidden && !data.IsDefaultIgnorable && !CodePoint.IsMark(data.CodePoint)) + { + break; + } + + markEnd++; + } + + PositionAroundBase(buffer, i, markEnd, unitsPerEm); + i = markEnd - 1; + } + } + + /// + /// Positions marks around one base, stacking marks with the same recategorized combining class. + /// + /// The positioned shaping buffer. + /// The zero-based base record index. + /// The exclusive end of the marks belonging to the base. + /// The font's units per em. + private static void PositionAroundBase(ShapingBuffer buffer, int baseIndex, int end, int unitsPerEm) + { + ref ShapingBuffer.GlyphMetricsEntry baseEntry = ref buffer.MetricsAt(baseIndex); + ref GlyphShapingPosition basePosition = ref buffer.PositionAt(baseIndex); + GlyphExtents baseExtents = GetExtents(baseEntry.Metrics); + baseExtents.YBearing += basePosition.Bounds.Y; + + // Horizontal advance gives stable component widths even for a zero-ink glyph. + baseExtents.XBearing = 0; + baseExtents.Width = baseEntry.Metrics.AdvanceWidth; + + ref GlyphShapingData baseData = ref buffer[baseIndex]; + int ligatureId = baseData.LigatureId; + int componentCount = baseData.LigatureComponentCount; + int xOffset = 0; + int yOffset = 0; + bool isForward = baseData.Direction == TextDirection.LeftToRight + || buffer.TextOptions.LayoutMode.IsVertical(); + if (isForward) + { + xOffset -= basePosition.Bounds.Width; + yOffset -= basePosition.Bounds.Height; + } + + GlyphExtents componentExtents = baseExtents; + GlyphExtents clusterExtents = baseExtents; + int lastLigatureComponent = -1; + int lastCombiningClass = byte.MaxValue; + for (int i = baseIndex + 1; i < end; i++) + { + ref GlyphShapingData data = ref buffer[i]; + int combiningClass = RecategorizeCombiningClass(data.CodePoint, data.MarkOrderingClass); + if (combiningClass == 0) + { + ref GlyphShapingPosition ordinaryPosition = ref buffer.PositionAt(i); + if (isForward) + { + xOffset -= ordinaryPosition.Bounds.Width; + yOffset -= ordinaryPosition.Bounds.Height; + } + else + { + xOffset += ordinaryPosition.Bounds.Width; + yOffset += ordinaryPosition.Bounds.Height; + } + + continue; + } + + if (componentCount > 1) + { + int component = data.LigatureComponent - 1; + if (ligatureId == 0 || data.LigatureId != ligatureId || component < 0 || component >= componentCount) + { + component = componentCount - 1; + } + + if (lastLigatureComponent != component) + { + lastLigatureComponent = component; + lastCombiningClass = byte.MaxValue; + componentExtents = baseExtents; + if (baseData.Direction == TextDirection.LeftToRight) + { + componentExtents.XBearing += (component * componentExtents.Width) / componentCount; + } + else + { + componentExtents.XBearing += ((componentCount - 1 - component) * componentExtents.Width) / componentCount; + } + + componentExtents.Width /= componentCount; + } + } + + if (lastCombiningClass != combiningClass) + { + lastCombiningClass = combiningClass; + clusterExtents = componentExtents; + } + + PositionMark(buffer, i, combiningClass, unitsPerEm, ref clusterExtents); + + ref GlyphShapingPosition markPosition = ref buffer.PositionAt(i); + markPosition.Bounds.Width = 0; + markPosition.Bounds.Height = 0; + markPosition.Bounds.X += xOffset; + markPosition.Bounds.Y += yOffset; + buffer.UpdatePosition(i); + } + } + + /// + /// Positions one mark against the current stack extents. + /// + /// The positioned shaping buffer. + /// The zero-based mark index. + /// The recategorized combining class. + /// The font's units per em. + /// The extents of the base or current mark stack. + private static void PositionMark(ShapingBuffer buffer, int index, int combiningClass, int unitsPerEm, ref GlyphExtents baseExtents) + { + GlyphExtents markExtents = GetExtents(buffer.MetricsAt(index).Metrics); + int verticalGap = unitsPerEm / VerticalGapDivisor; + ref GlyphShapingPosition position = ref buffer.PositionAt(index); + position.Bounds.X = 0; + position.Bounds.Y = 0; + + switch (combiningClass) + { + case DoubleBelow: + case DoubleAbove: + if (buffer[index].Direction == TextDirection.LeftToRight) + { + position.Bounds.X += baseExtents.XBearing + baseExtents.Width - (markExtents.Width / 2) - markExtents.XBearing; + break; + } + + position.Bounds.X += baseExtents.XBearing - (markExtents.Width / 2) - markExtents.XBearing; + break; + case AttachedBelowLeft: + case BelowLeft: + case AboveLeft: + position.Bounds.X += baseExtents.XBearing - markExtents.XBearing; + break; + case AttachedAboveRight: + case BelowRight: + case AboveRight: + position.Bounds.X += baseExtents.XBearing + baseExtents.Width - markExtents.Width - markExtents.XBearing; + break; + default: + position.Bounds.X += baseExtents.XBearing + ((baseExtents.Width - markExtents.Width) / 2) - markExtents.XBearing; + break; + } + + switch (combiningClass) + { + case DoubleBelow: + case BelowLeft: + case Below: + case BelowRight: + baseExtents.Height -= verticalGap; + goto case AttachedBelow; + case AttachedBelowLeft: + case AttachedBelow: + position.Bounds.Y = baseExtents.YBearing + baseExtents.Height - markExtents.YBearing; + if ((verticalGap > 0) == (position.Bounds.Y > 0)) + { + baseExtents.Height -= position.Bounds.Y; + position.Bounds.Y = 0; + } + + baseExtents.Height += markExtents.Height; + break; + case DoubleAbove: + case AboveLeft: + case Above: + case AboveRight: + baseExtents.YBearing += verticalGap; + baseExtents.Height -= verticalGap; + goto case AttachedAbove; + case AttachedAbove: + case AttachedAboveRight: + position.Bounds.Y = baseExtents.YBearing - (markExtents.YBearing + markExtents.Height); + if ((verticalGap > 0) != (position.Bounds.Y > 0)) + { + int correction = -position.Bounds.Y / 2; + baseExtents.YBearing += correction; + baseExtents.Height -= correction; + position.Bounds.Y += correction; + } + + baseExtents.YBearing -= markExtents.Height; + baseExtents.Height += markExtents.Height; + break; + } + } + + /// + /// Recategorizes script-specific mark-ordering classes into geometric attachment classes. + /// + /// The mark character. + /// The mark-ordering class. + /// The geometric attachment class. + private static int RecategorizeCombiningClass(CodePoint codePoint, int combiningClass) + { + if (combiningClass >= FirstPositioningClass) + { + return combiningClass; + } + + if ((codePoint.Value & ~byte.MaxValue) == ThaiLaoBlockPrefix) + { + if (combiningClass == 0) + { + combiningClass = codePoint.Value switch + { + ThaiMaiHanAkat or ThaiSaraI or ThaiSaraIi or ThaiSaraUe or ThaiSaraUee or ThaiMaiTaikhu or ThaiThanthakhat or ThaiNikhahit or ThaiYamakkan => AboveRight, + LaoMaiKan or LaoVowelSignI or LaoVowelSignIi or LaoVowelSignY or LaoVowelSignYy or LaoMaiKon or LaoCancellationMark or LaoNiggahita => Above, + LaoSemivowelLo => Below, + _ => 0 + }; + } + else if (codePoint.Value == ThaiPhinthu) + { + combiningClass = BelowRight; + } + } + + return combiningClass switch + { + 22 or 15 or 16 or 17 or 23 or 18 or 19 or 20 or 21 or 24 or 25 => Below, + 13 => AttachedAbove, + 10 => AboveRight, + 11 or 14 => AboveLeft, + 26 => Above, + 28 or 29 or 31 or 32 or 27 or 34 or 35 or 36 => Above, + 30 or 33 => Below, + 3 => BelowRight, + 107 => AboveRight, + 118 or 131 or 129 => Below, + 122 or 132 => Above, + _ => combiningClass + }; + } + + /// + /// Converts glyph bounds into bearing, width, and downward-height extents. + /// + /// The glyph metrics. + /// The glyph extents. + private static GlyphExtents GetExtents(FontGlyphMetrics metrics) + { + Bounds bounds = metrics.Bounds; + return new GlyphExtents((int)bounds.Min.X, (int)bounds.Max.Y, (int)(bounds.Max.X - bounds.Min.X), (int)(bounds.Min.Y - bounds.Max.Y)); + } + + /// + /// Stores glyph extents in font coordinates. + /// + private struct GlyphExtents + { + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal bearing. + /// The vertical bearing. + /// The glyph width. + /// The downward glyph height. + public GlyphExtents(int xBearing, int yBearing, int width, int height) + { + this.XBearing = xBearing; + this.YBearing = yBearing; + this.Width = width; + this.Height = height; + } + + /// + /// Gets or sets the horizontal bearing. + /// + public int XBearing { get; set; } + + /// + /// Gets or sets the vertical bearing. + /// + public int YBearing { get; set; } + + /// + /// Gets or sets the glyph width. + /// + public int Width { get; set; } + + /// + /// Gets or sets the downward glyph height. + /// + public int Height { get; set; } + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/FeatureLookupsKey.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/FeatureLookupsKey.cs new file mode 100644 index 000000000..2818e1984 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/FeatureLookupsKey.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// Cache key for resolved feature lookups: the stage feature, the script, and the +/// language system candidates the resolution ladder selected against. Language tags +/// compare by sequence because each shaping pass resolves a fresh array from the +/// culture. +/// +internal readonly struct FeatureLookupsKey : IEquatable +{ + private readonly uint feature; + private readonly Unicode.ScriptClass script; + private readonly Tag[] languageTags; + + /// + /// Initializes a new instance of the struct. + /// + /// The stage feature tag. + /// The script class. + /// The candidate language system tags, most specific first. + public FeatureLookupsKey(Tag feature, Unicode.ScriptClass script, Tag[] languageTags) + { + this.feature = feature.Value; + this.script = script; + this.languageTags = languageTags; + } + + /// + public bool Equals(FeatureLookupsKey other) + { + if (this.feature != other.feature || this.script != other.script) + { + return false; + } + + Tag[] tags = this.languageTags; + Tag[] otherTags = other.languageTags; + if (ReferenceEquals(tags, otherTags)) + { + return true; + } + + if (tags.Length != otherTags.Length) + { + return false; + } + + for (int i = 0; i < tags.Length; i++) + { + if (tags[i] != otherTags[i]) + { + return false; + } + } + + return true; + } + + /// + public override bool Equals(object? obj) => obj is FeatureLookupsKey key && this.Equals(key); + + /// + public override int GetHashCode() + { + HashCode hash = default; + hash.Add(this.feature); + hash.Add(this.script); + for (int i = 0; i < this.languageTags.Length; i++) + { + hash.Add(this.languageTags[i].Value); + } + + return hash.ToHashCode(); + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/AnchorTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/AnchorTable.cs index d10b49f45..88d96a97e 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/AnchorTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/AnchorTable.cs @@ -42,9 +42,9 @@ protected AnchorTable(short xCoordinate, short yCoordinate) /// /// The font metrics. /// The glyph shaping data. - /// The glyph positioning collection. + /// The glyph positioning buffer. /// The resolved anchor coordinates. - public abstract AnchorXY GetAnchor(FontMetrics fontMetrics, GlyphShapingData data, GlyphPositioningCollection collection); + public abstract AnchorXY GetAnchor(FontMetrics fontMetrics, ref GlyphShapingData data, ShapingBuffer buffer); /// /// Loads the anchor table. @@ -106,7 +106,7 @@ public static AnchorFormat1 Load(BigEndianBinaryReader reader) } /// - public override AnchorXY GetAnchor(FontMetrics fontMetrics, GlyphShapingData data, GlyphPositioningCollection collection) + public override AnchorXY GetAnchor(FontMetrics fontMetrics, ref GlyphShapingData data, ShapingBuffer buffer) => new(this.XCoordinate, this.YCoordinate); } @@ -152,14 +152,15 @@ public static AnchorFormat2 Load(BigEndianBinaryReader reader) } /// - public override AnchorXY GetAnchor(FontMetrics fontMetrics, GlyphShapingData data, GlyphPositioningCollection collection) + public override AnchorXY GetAnchor(FontMetrics fontMetrics, ref GlyphShapingData data, ShapingBuffer buffer) { - if (collection.TextOptions.HintingMode != HintingMode.None) + if (buffer.TextOptions.HintingMode != HintingMode.None) { - TextAttributes textAttributes = data.TextRun.TextAttributes; - TextDecorations textDecorations = data.TextRun.TextDecorations; - LayoutMode layoutMode = collection.TextOptions.LayoutMode; - ColorFontSupport colorFontSupport = collection.TextOptions.ColorFontSupport; + TextRun textRun = buffer.TextRuns[data.TextRunIndex]; + TextAttributes textAttributes = textRun.TextAttributes; + TextDecorations textDecorations = textRun.TextDecorations; + LayoutMode layoutMode = buffer.TextOptions.LayoutMode; + ColorFontSupport colorFontSupport = buffer.TextOptions.ColorFontSupport; if (fontMetrics.TryGetGlyphMetrics(data.CodePoint, textAttributes, textDecorations, layoutMode, colorFontSupport, out FontGlyphMetrics? metrics)) { if (metrics is TrueTypeGlyphMetrics ttmetric) @@ -247,7 +248,7 @@ public static AnchorFormat3 LoadFormat3(BigEndianBinaryReader reader, long ancho } /// - public override AnchorXY GetAnchor(FontMetrics fontMetrics, GlyphShapingData data, GlyphPositioningCollection collection) + public override AnchorXY GetAnchor(FontMetrics fontMetrics, ref GlyphShapingData data, ShapingBuffer buffer) { short x = this.XCoordinate; short y = this.YCoordinate; @@ -320,8 +321,8 @@ private EmptyAnchorTable() /// public override AnchorXY GetAnchor( FontMetrics fontMetrics, - GlyphShapingData data, - GlyphPositioningCollection collection) + ref GlyphShapingData data, + ShapingBuffer buffer) => new(0, 0); } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupListTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupListTable.cs index fc0f6a60d..cc1676a0f 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupListTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupListTable.cs @@ -92,6 +92,24 @@ private LookupTable( this.LookupFlags = lookupFlags; this.MarkFilteringSet = markFilteringSet; this.LookupSubTables = lookupSubTables; + + // The union of every subtable's gating coverage: a glyph outside the digest + // cannot be affected by any subtable of this lookup, so application skips it + // without touching the subtables. See GlyphSetDigest for the accuracy contract. + GlyphSetDigest digest = default; + for (int i = 0; i < lookupSubTables.Length; i++) + { + // Each subtable also carries its own digest so application can skip + // subtables whose gating coverage cannot contain the current glyph + // without paying the virtual probe. Contextual formats that expose no + // leading coverage flood their digest and therefore always pass. + GlyphSetDigest subTableDigest = default; + lookupSubTables[i].CollectDigest(ref subTableDigest); + lookupSubTables[i].Digest = subTableDigest; + lookupSubTables[i].CollectDigest(ref digest); + } + + this.Digest = digest; } /// @@ -114,6 +132,11 @@ private LookupTable( /// public LookupSubTable[] LookupSubTables { get; } + /// + /// Gets the approximate membership filter for the glyphs this lookup can affect. + /// + public GlyphSetDigest Digest { get; } + /// /// Loads the from the specified reader at the given offset. /// @@ -188,11 +211,11 @@ private static LookupSubTable LoadLookupSubTable(ushort lookupType, LookupFlags }; /// - /// Attempts to update the position of glyphs in the collection at the specified index. + /// Attempts to update the position of glyphs in the buffer at the specified index. /// /// The font metrics. /// The GPOS table. - /// The glyph positioning collection. + /// The glyph positioning buffer. /// The feature tag. /// The zero-based index of the glyph to position. /// The number of glyphs remaining in the sequence. @@ -200,16 +223,25 @@ private static LookupSubTable LoadLookupSubTable(ushort lookupType, LookupFlags public bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) { + ushort glyphId = buffer[index].GlyphId; foreach (LookupSubTable subTable in this.LookupSubTables) { + // A glyph outside the subtable's digest cannot match its coverage, so the + // probe (a virtual call, coverage search, or full context-match attempt) + // is skipped entirely. + if (!subTable.Digest.MightContain(glyphId)) + { + continue; + } + // A lookup is finished for a glyph after the client locates the target // glyph or glyph context and performs a positioning action, if specified. - if (subTable.TryUpdatePosition(fontMetrics, table, collection, feature, index, count)) + if (subTable.TryUpdatePosition(fontMetrics, table, buffer, feature, index, count)) { return true; } @@ -246,11 +278,25 @@ protected LookupSubTable(LookupFlags lookupFlags, ushort markFilteringSet) public ushort MarkFilteringSet { get; } /// - /// Attempts to update the position of glyphs in the collection at the specified index. + /// Gets or sets the approximate membership filter for the glyphs this subtable can + /// affect. Assigned once by the owning during construction. + /// + public GlyphSetDigest Digest { get; internal set; } + + /// + /// Adds the coverage that gates this subtable's applicability to the digest. + /// The default adds every glyph so the lookup is always attempted, the correct + /// conservative behavior for subtables whose gating coverage is unknown. + /// + /// The digest to add to. + public virtual void CollectDigest(ref GlyphSetDigest digest) => digest.AddAll(); + + /// + /// Attempts to update the position of glyphs in the buffer at the specified index. /// /// The font metrics. /// The GPOS table. - /// The glyph positioning collection. + /// The glyph positioning buffer. /// The feature tag. /// The zero-based index of the glyph to position. /// The number of glyphs remaining in the sequence. @@ -258,7 +304,7 @@ protected LookupSubTable(LookupFlags lookupFlags, ushort markFilteringSet) public abstract bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count); diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType1SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType1SubTable.cs index b60e4c1aa..1f51db55c 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType1SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType1SubTable.cs @@ -90,16 +90,19 @@ public static LookupType1Format1SubTable Load(BigEndianBinaryReader reader, long return new LookupType1Format1SubTable(valueRecord, coverageTable, lookupFlags, markFilteringSet); } + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -109,7 +112,7 @@ public override bool TryUpdatePosition( if (coverage > -1) { ValueRecord record = this.valueRecord; - AdvancedTypographicUtils.ApplyPosition(fontMetrics, collection, index, record, feature); + AdvancedTypographicUtils.ApplyPosition(fontMetrics, buffer, index, record, feature); return true; } @@ -181,16 +184,19 @@ public static LookupType1Format2SubTable Load(BigEndianBinaryReader reader, long return new LookupType1Format2SubTable(valueRecords, coverageTable, lookupFlags, markFilteringSet); } + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -200,7 +206,7 @@ public override bool TryUpdatePosition( if (coverage > -1 && coverage < this.valueRecords.Length) { ValueRecord record = this.valueRecords[coverage]; - AdvancedTypographicUtils.ApplyPosition(fontMetrics, collection, index, record, feature); + AdvancedTypographicUtils.ApplyPosition(fontMetrics, buffer, index, record, feature); return true; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType2SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType2SubTable.cs index f67b69b4f..b2df7ae14 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType2SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType2SubTable.cs @@ -113,11 +113,14 @@ public static LookupType2Format1SubTable Load(BigEndianBinaryReader reader, long return new LookupType2Format1SubTable(coverageTable, pairSets, lookupFlags, markFilteringSet); } + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) @@ -127,7 +130,7 @@ public override bool TryUpdatePosition( return false; } - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -137,7 +140,18 @@ public override bool TryUpdatePosition( if (coverage > -1 && coverage < this.pairSets.Length) { PairSetTable pairSet = this.pairSets[coverage]; - ushort glyphId2 = collection[index + 1].GlyphId; + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + iterator.SetMatchContext(uint.MaxValue, false); + + // Lookup flags and positioning transparency determine the second + // member of the pair; ignored glyphs may remain between both members. + int secondIndex = iterator.Next(); + if (secondIndex >= index + count) + { + return false; + } + + ushort glyphId2 = buffer[secondIndex].GlyphId; if (glyphId2 == 0) { return false; @@ -146,10 +160,10 @@ public override bool TryUpdatePosition( if (pairSet.TryGetPairValueRecord(glyphId2, out PairValueRecord pairValueRecord)) { ValueRecord record1 = pairValueRecord.ValueRecord1; - AdvancedTypographicUtils.ApplyPosition(fontMetrics, collection, index, record1, feature); + AdvancedTypographicUtils.ApplyPosition(fontMetrics, buffer, index, record1, feature); ValueRecord record2 = pairValueRecord.ValueRecord2; - AdvancedTypographicUtils.ApplyPosition(fontMetrics, collection, index + 1, record2, feature); + AdvancedTypographicUtils.ApplyPosition(fontMetrics, buffer, secondIndex, record2, feature); return true; } @@ -336,11 +350,14 @@ public static LookupType2Format2SubTable Load(BigEndianBinaryReader reader, long return new LookupType2Format2SubTable(coverageTable, class1Records, classDefTable1, classDefTable2, lookupFlags, markFilteringSet); } + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) @@ -350,7 +367,7 @@ public override bool TryUpdatePosition( return false; } - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -360,7 +377,18 @@ public override bool TryUpdatePosition( if (coverage > -1) { int classDef1 = this.classDefinitionTable1.ClassIndexOf(glyphId); - ushort glyphId2 = collection[index + 1].GlyphId; + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + iterator.SetMatchContext(uint.MaxValue, false); + + // Class-pair matching observes the same lookup filtering as + // individual-pair matching, including transparent intervening glyphs. + int secondIndex = iterator.Next(); + if (secondIndex >= index + count) + { + return false; + } + + ushort glyphId2 = buffer[secondIndex].GlyphId; if (glyphId2 == 0) { return false; @@ -372,10 +400,10 @@ public override bool TryUpdatePosition( Class2Record class2Record = class1Record.Class2Records[classDef2]; ValueRecord record1 = class2Record.ValueRecord1; - AdvancedTypographicUtils.ApplyPosition(fontMetrics, collection, index, record1, feature); + AdvancedTypographicUtils.ApplyPosition(fontMetrics, buffer, index, record1, feature); ValueRecord record2 = class2Record.ValueRecord2; - AdvancedTypographicUtils.ApplyPosition(fontMetrics, collection, index + 1, record2, feature); + AdvancedTypographicUtils.ApplyPosition(fontMetrics, buffer, secondIndex, record2, feature); return true; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType3SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType3SubTable.cs index 38f02f164..e6d8eda1a 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType3SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType3SubTable.cs @@ -102,99 +102,108 @@ public static LookupType3Format1SubTable Load(BigEndianBinaryReader reader, long } /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + + /// + /// + /// The entry anchor connects to the closest preceding glyph visible under + /// the lookup flags. Default-ignorables remain in the buffer but are + /// transparent to positioning lookups. + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int 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].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; } - int nextIndex = index + 1; - ushort nextGlyphId = collection[nextIndex].GlyphId; - if (nextGlyphId == 0) + int coverage = this.coverageTable.CoverageIndexOf(glyphId); + if (coverage < 0 || coverage >= this.entryExitAnchors.Length) { return false; } - int coverageNext = this.coverageTable.CoverageIndexOf(nextGlyphId); - if (coverageNext < 0 || coverageNext >= this.entryExitAnchors.Length) + EntryExitAnchors currentRecord = this.entryExitAnchors[coverage]; + AnchorTable? entry = currentRecord.EntryAnchor; + if (entry is null) { return false; } - EntryExitAnchors nextRecord = this.entryExitAnchors[coverageNext]; - AnchorTable? entry = nextRecord.EntryAnchor; - if (entry is null) + // Cursive attachment joins the current glyph to the closest preceding + // glyph that the lookup can see. In particular, marks excluded by the + // lookup's filtering set and default-ignorables are transparent. + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + iterator.SetMatchContext(uint.MaxValue, false); + int previousIndex = iterator.Prev(); + if (previousIndex < 0) { return false; } - int coverage = this.coverageTable.CoverageIndexOf(glyphId); - if (coverage < 0 || coverage >= this.entryExitAnchors.Length) + ushort previousGlyphId = buffer[previousIndex].GlyphId; + int previousCoverage = this.coverageTable.CoverageIndexOf(previousGlyphId); + if (previousCoverage < 0 || previousCoverage >= this.entryExitAnchors.Length) { return false; } - EntryExitAnchors curRecord = this.entryExitAnchors[coverage]; - AnchorTable? exit = curRecord.ExitAnchor; + EntryExitAnchors previousRecord = this.entryExitAnchors[previousCoverage]; + AnchorTable? exit = previousRecord.ExitAnchor; if (exit is null) { return false; } - GlyphShapingData current = collection[index]; - GlyphShapingData next = collection[nextIndex]; + ref GlyphShapingData previous = ref buffer[previousIndex]; + ref GlyphShapingData current = ref buffer[index]; + + AnchorXY exitXY = exit.GetAnchor(fontMetrics, ref previous, buffer); + AnchorXY entryXY = entry.GetAnchor(fontMetrics, ref current, buffer); - AnchorXY exitXY = exit.GetAnchor(fontMetrics, current, collection); - AnchorXY entryXY = entry.GetAnchor(fontMetrics, next, collection); + ref GlyphShapingPosition previousPosition = ref buffer.PositionAt(previousIndex); + ref GlyphShapingPosition currentPosition = ref buffer.PositionAt(index); - bool isVerticalLayout = AdvancedTypographicUtils.IsVerticalGlyph(current.CodePoint, collection.TextOptions.LayoutMode); + bool isVerticalLayout = AdvancedTypographicUtils.IsVerticalGlyph(current.CodePoint, buffer.TextOptions.LayoutMode); if (!isVerticalLayout) { // Horizontal if (current.Direction == TextDirection.LeftToRight) { - current.Bounds.Width = exitXY.XCoordinate + current.Bounds.X; + previousPosition.Bounds.Width = exitXY.XCoordinate + previousPosition.Bounds.X; - int delta = entryXY.XCoordinate + next.Bounds.X; - next.Bounds.Width -= delta; - next.Bounds.X -= delta; + int delta = entryXY.XCoordinate + currentPosition.Bounds.X; + currentPosition.Bounds.Width -= delta; + currentPosition.Bounds.X -= delta; } else { - int delta = exitXY.XCoordinate + current.Bounds.X; - current.Bounds.Width -= delta; - current.Bounds.X -= delta; + int delta = exitXY.XCoordinate + previousPosition.Bounds.X; + previousPosition.Bounds.Width -= delta; + previousPosition.Bounds.X -= delta; - next.Bounds.Width = entryXY.XCoordinate + next.Bounds.X; + currentPosition.Bounds.Width = entryXY.XCoordinate + currentPosition.Bounds.X; } } else { // Vertical layout modes advance top-to-bottom; column progression is handled by layout. - current.Bounds.Height = exitXY.YCoordinate + current.Bounds.Y; + previousPosition.Bounds.Height = exitXY.YCoordinate + previousPosition.Bounds.Y; - int delta = entryXY.YCoordinate + next.Bounds.Y; - next.Bounds.Height -= delta; - next.Bounds.Y -= delta; + int delta = entryXY.YCoordinate + currentPosition.Bounds.Y; + currentPosition.Bounds.Height -= delta; + currentPosition.Bounds.Y -= delta; } - int child = index; - int parent = nextIndex; + int child = previousIndex; + int parent = index; int xOffset = entryXY.XCoordinate - exitXY.XCoordinate; int yOffset = entryXY.YCoordinate - exitXY.YCoordinate; if ((this.LookupFlags & LookupFlags.RightToLeft) != LookupFlags.RightToLeft) @@ -210,9 +219,9 @@ public override bool TryUpdatePosition( // previous connection now attaches to new parent.Watch out for case // where new parent is on the path from old chain... bool horizontal = !isVerticalLayout; - ReverseCursiveMinorOffset(collection, index, child, horizontal, parent); + ReverseCursiveMinorOffset(buffer, child, horizontal, parent); - GlyphShapingData c = collection[child]; + ref GlyphShapingPosition c = ref buffer.PositionAt(child); c.CursiveAttachment = parent - child; if (horizontal) { @@ -223,15 +232,15 @@ public override bool TryUpdatePosition( c.Bounds.X = xOffset; } - // If parent was attached to child, separate them. - // https://github.com/harfbuzz/harfbuzz/issues/2469 - GlyphShapingData p = collection[parent]; + // If parent was attached to child, separate them so the attachment + // graph stays acyclic. + ref GlyphShapingPosition p = ref buffer.PositionAt(parent); if (p.CursiveAttachment == -c.CursiveAttachment) { - p.CursiveAttachment = 0; + p.CursiveAttachment = GlyphShapingPosition.NoCursiveAttachment; - // Bounds.X/Y carry shaping placement offsets here, matching - // HarfBuzz x_offset/y_offset. Clear only the detached parent's minor axis. + // Bounds.X/Y carry shaping placement offsets here. Clear only the + // detached parent's minor axis. if (horizontal) { p.Bounds.Y = 0; @@ -249,26 +258,20 @@ public override bool TryUpdatePosition( /// Recursively reverses the cursive minor offset chain so that the entire tree /// of a previous connection attaches to the new parent. /// - /// The glyph positioning collection. - /// The original glyph position that initiated the chain reversal. + /// The glyph positioning buffer. /// The current index in the chain being reversed. /// Whether the layout is horizontal. /// The new parent index to stop at. - private static void ReverseCursiveMinorOffset( - GlyphPositioningCollection collection, - int position, - int i, - bool horizontal, - int parent) + private static void ReverseCursiveMinorOffset(ShapingBuffer buffer, int i, bool horizontal, int parent) { - GlyphShapingData c = collection[i]; + ref GlyphShapingPosition c = ref buffer.PositionAt(i); int chain = c.CursiveAttachment; - if (chain <= 0) + if (chain == GlyphShapingPosition.NoCursiveAttachment) { return; } - c.CursiveAttachment = 0; + c.CursiveAttachment = GlyphShapingPosition.NoCursiveAttachment; int j = i + chain; @@ -278,9 +281,9 @@ private static void ReverseCursiveMinorOffset( return; } - ReverseCursiveMinorOffset(collection, position, j, horizontal, parent); + ReverseCursiveMinorOffset(buffer, j, horizontal, parent); - GlyphShapingData p = collection[j]; + ref GlyphShapingPosition p = ref buffer.PositionAt(j); if (horizontal) { p.Bounds.Y = -c.Bounds.Y; diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType4SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType4SubTable.cs index 5e32c5ff4..f3792b843 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType4SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType4SubTable.cs @@ -110,17 +110,23 @@ public static LookupType4Format1SubTable Load(BigEndianBinaryReader reader, long } /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.markCoverage.CollectDigest(ref digest); + + /// + /// + /// The backward search and multiple-substitution acceptance rule are transcribed from HarfBuzz 14.2.1, src/OT/Layout/GPOS/MarkBasePosFormat1.hh, symbol MarkBasePosFormat1_2::apply. Default-ignorable transparency is defined by matcher_t::may_skip in src/hb-ot-layout-gsubgpos.hh. These rules are shaping behavior and are not derivable from the Unicode Character Database. + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int 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].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -132,15 +138,29 @@ public override bool TryUpdatePosition( return false; } - // Search backward for a base glyph. - int baseGlyphIndex = index; - while (--baseGlyphIndex >= 0) + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, LookupFlags.IgnoreMarks, 0); + iterator.SetMatchContext(buffer.LookupMask, false); + + int baseGlyphIndex = iterator.Prev(); + while (baseGlyphIndex >= 0) { - GlyphShapingData data = collection[baseGlyphIndex]; - if (!AdvancedTypographicUtils.IsMarkGlyph(fontMetrics, data.GlyphId, data) && data.LigatureComponent <= 0) + ref GlyphShapingData candidate = ref buffer[baseGlyphIndex]; + bool acceptsCandidate = !candidate.IsDecomposed + || candidate.LigatureComponent == 0 + || baseGlyphIndex == 0 + || AdvancedTypographicUtils.IsMarkGlyph(fontMetrics, buffer[baseGlyphIndex - 1].GlyphId, ref buffer[baseGlyphIndex - 1]) + || !buffer[baseGlyphIndex - 1].IsDecomposed + || candidate.LigatureId != buffer[baseGlyphIndex - 1].LigatureId + || candidate.LigatureComponent != buffer[baseGlyphIndex - 1].LigatureComponent + 1; + + // A later component may still be accepted when the font explicitly + // covers it; otherwise continue to the first component. + if (acceptsCandidate || this.baseCoverage.CoverageIndexOf(candidate.GlyphId) >= 0) { break; } + + baseGlyphIndex = iterator.Prev(); } if (baseGlyphIndex < 0) @@ -148,7 +168,7 @@ public override bool TryUpdatePosition( return false; } - ushort baseGlyphId = collection[baseGlyphIndex].GlyphId; + ushort baseGlyphId = buffer[baseGlyphIndex].GlyphId; int baseIndex = this.baseCoverage.CoverageIndexOf(baseGlyphId); if (baseIndex < 0 || baseIndex >= this.baseArrayTable.BaseRecords.Length) { @@ -157,7 +177,7 @@ public override bool TryUpdatePosition( MarkRecord markRecord = this.markArrayTable.MarkRecords[markIndex]; AnchorTable baseAnchor = this.baseArrayTable.BaseRecords[baseIndex].BaseAnchorTables[markRecord.MarkClass]; - AdvancedTypographicUtils.ApplyAnchor(fontMetrics, collection, index, baseAnchor, markRecord, baseGlyphIndex, feature); + AdvancedTypographicUtils.ApplyAnchor(fontMetrics, buffer, index, baseAnchor, markRecord, baseGlyphIndex, feature); return true; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType5SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType5SubTable.cs index 3ccd01b93..3a7d98a0d 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType5SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType5SubTable.cs @@ -112,18 +112,21 @@ public static LookupType5Format1SubTable Load(BigEndianBinaryReader reader, long return new LookupType5Format1SubTable(markCoverage, ligatureCoverage, markArrayTable, ligatureArrayTable, lookupFlags, markFilteringSet); } + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.markCoverage.CollectDigest(ref digest); + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int 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].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -135,23 +138,17 @@ public override bool TryUpdatePosition( return false; } - // Search backward for a base glyph. - int baseGlyphIndex = index; - while (--baseGlyphIndex >= 0) - { - GlyphShapingData data = collection[baseGlyphIndex]; - if (!AdvancedTypographicUtils.IsMarkGlyph(fontMetrics, data.GlyphId, data)) - { - break; - } - } + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, LookupFlags.IgnoreMarks, 0); + iterator.SetMatchContext(buffer.LookupMask, false); + + int baseGlyphIndex = iterator.Prev(); if (baseGlyphIndex < 0) { return false; } - ushort baseGlyphId = collection[baseGlyphIndex].GlyphId; + ushort baseGlyphId = buffer[baseGlyphIndex].GlyphId; int ligatureIndex = this.ligatureCoverage.CoverageIndexOf(baseGlyphId); if (ligatureIndex < 0 || ligatureIndex >= this.ligatureArrayTable.LigatureAttachTables.Length) { @@ -163,15 +160,21 @@ public override bool TryUpdatePosition( // 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[index]; - GlyphShapingData ligGlyph = collection[baseGlyphIndex]; + int componentCount = ligatureAttach.ComponentRecords.Length; + if (componentCount == 0) + { + return false; + } + + ref GlyphShapingData markGlyph = ref buffer[index]; + ref GlyphShapingData ligGlyph = ref buffer[baseGlyphIndex]; int compIndex = ligGlyph.LigatureId > 0 && ligGlyph.LigatureId == markGlyph.LigatureId && markGlyph.LigatureComponent > 0 - ? Math.Min(markGlyph.LigatureComponent, ligGlyph.CodePointCount) - 1 - : ligGlyph.CodePointCount - 1; + ? Math.Min(markGlyph.LigatureComponent, componentCount) - 1 + : componentCount - 1; MarkRecord markRecord = this.markArrayTable.MarkRecords[markIndex]; AnchorTable baseAnchor = ligatureAttach.ComponentRecords[compIndex].LigatureAnchorTables[markRecord.MarkClass]; - AdvancedTypographicUtils.ApplyAnchor(fontMetrics, collection, index, baseAnchor, markRecord, baseGlyphIndex, feature); + AdvancedTypographicUtils.ApplyAnchor(fontMetrics, buffer, index, baseAnchor, markRecord, baseGlyphIndex, feature); return true; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType6SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType6SubTable.cs index 92a7110df..e04f6e0ff 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType6SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType6SubTable.cs @@ -112,17 +112,23 @@ public static LookupType6Format1SubTable Load(BigEndianBinaryReader reader, long } /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.mark1Coverage.CollectDigest(ref digest); + + /// + /// + /// The backward search is transcribed from HarfBuzz 14.2.1, src/OT/Layout/GPOS/MarkMarkPosFormat1.hh, symbol MarkMarkPosFormat1_2::apply. Default-ignorable transparency is defined by matcher_t::may_skip in src/hb-ot-layout-gsubgpos.hh. These rules are shaping behavior and are not derivable from the Unicode Character Database. + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int 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].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -134,12 +140,12 @@ public override bool TryUpdatePosition( return false; } - // Get the previous mark to attach to. - // HarfBuzz: search backwards for a suitable mark glyph until a non-mark glyph. - // It clears ignore flags when searching, but keeps mark attachment / filtering behavior. + // Clear the class-ignore flags while retaining mark attachment and + // filtering behavior for the preceding-mark search. LookupFlags searchFlags = this.LookupFlags & ~(LookupFlags.IgnoreMarks | LookupFlags.IgnoreBaseGlyphs | LookupFlags.IgnoreLigatures); - SkippingGlyphIterator it = new(fontMetrics, collection, index, searchFlags, this.MarkFilteringSet); + SkippingGlyphIterator it = new(fontMetrics, buffer, index, searchFlags, this.MarkFilteringSet); + it.SetMatchContext(buffer.LookupMask, false); int j = it.Prev(); if (j < 0) @@ -147,13 +153,13 @@ public override bool TryUpdatePosition( return false; } - GlyphShapingData prevGlyph = collection[j]; - if (!AdvancedTypographicUtils.IsMarkGlyph(fontMetrics, prevGlyph.GlyphId, prevGlyph)) + ref GlyphShapingData prevGlyph = ref buffer[j]; + if (!AdvancedTypographicUtils.IsMarkGlyph(fontMetrics, prevGlyph.GlyphId, ref prevGlyph)) { return false; } - GlyphShapingData curGlyph = collection[index]; + ref GlyphShapingData curGlyph = ref buffer[index]; bool good; int id1 = curGlyph.LigatureId; @@ -193,7 +199,7 @@ public override bool TryUpdatePosition( MarkRecord markRecord = this.mark1ArrayTable.MarkRecords[mark1Index]; AnchorTable? baseAnchor = this.mark2ArrayTable.Mark2Records[mark2Index].MarkAnchorTable[markRecord.MarkClass]; - AdvancedTypographicUtils.ApplyAnchor(fontMetrics, collection, index, baseAnchor, markRecord, j, feature); + AdvancedTypographicUtils.ApplyAnchor(fontMetrics, buffer, index, baseAnchor, markRecord, j, feature); return true; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType7SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType7SubTable.cs index c92ce602e..6560bbb0c 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType7SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType7SubTable.cs @@ -74,16 +74,19 @@ public static LookupType7Format1SubTable Load(BigEndianBinaryReader reader, long return new LookupType7Format1SubTable(coverageTable, seqRuleSets, lookupFlags, markFilteringSet); } + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -98,7 +101,12 @@ public override bool TryUpdatePosition( // TODO: Check this. // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#example-7-contextual-substitution-format-1 SequenceRuleSetTable ruleSetTable = this.seqRuleSetTables[offset]; - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, this.LookupFlags, this.MarkFilteringSet); + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + + // Slot zero holds the coverage-matched glyph; the input match fills the + // rest, so nested lookups address the records the match consumed. + Span matchPositions = buffer.GetContextMatchPositions(); + matchPositions[0] = index; foreach (SequenceRuleTable ruleTable in ruleSetTable.SequenceRuleTables) { int remaining = count - 1; @@ -108,7 +116,7 @@ public override bool TryUpdatePosition( continue; } - if (!AdvancedTypographicUtils.MatchSequence(iterator, 1, ruleTable.InputSequence)) + if (!AdvancedTypographicUtils.MatchSequence(iterator, 1, ruleTable.InputSequence, buffer.LookupMask, false, matchPositions[1..], out _)) { continue; } @@ -118,11 +126,10 @@ public override bool TryUpdatePosition( fontMetrics, table, feature, - this.LookupFlags, - this.MarkFilteringSet, ruleTable.SequenceLookupRecords, - collection, - index, + buffer, + matchPositions, + ruleTable.InputSequence.Length + 1, count); } @@ -180,16 +187,19 @@ public static LookupType7Format2SubTable Load(BigEndianBinaryReader reader, long return new LookupType7Format2SubTable(coverageTable, classDefTable, classSeqRuleSets, lookupFlags, markFilteringSet); } + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -207,7 +217,12 @@ public override bool TryUpdatePosition( } ClassSequenceRuleSetTable ruleSetTable = this.sequenceRuleSetTables[offset]; - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, this.LookupFlags, this.MarkFilteringSet); + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + + // Slot zero holds the coverage-matched glyph; the input match fills the + // rest, so nested lookups address the records the match consumed. + Span matchPositions = buffer.GetContextMatchPositions(); + matchPositions[0] = index; foreach (ClassSequenceRuleTable ruleTable in ruleSetTable.SequenceRuleTables) { int remaining = count - 1; @@ -217,7 +232,7 @@ public override bool TryUpdatePosition( continue; } - if (!AdvancedTypographicUtils.MatchClassSequence(iterator, 1, ruleTable.InputSequence, this.classDefinitionTable)) + if (!AdvancedTypographicUtils.MatchClassSequence(iterator, 1, ruleTable.InputSequence, this.classDefinitionTable, buffer.LookupMask, false, matchPositions[1..], out _)) { continue; } @@ -227,11 +242,10 @@ public override bool TryUpdatePosition( fontMetrics, table, feature, - this.LookupFlags, - this.MarkFilteringSet, ruleTable.SequenceLookupRecords, - collection, - index, + buffer, + matchPositions, + ruleTable.InputSequence.Length + 1, count); } @@ -281,23 +295,41 @@ public static LookupType7Format3SubTable Load(BigEndianBinaryReader reader, long return new LookupType7Format3SubTable(coverageTables, seqLookupRecords, lookupFlags, markFilteringSet); } + /// + public override void CollectDigest(ref GlyphSetDigest digest) + { + if (this.coverageTables.Length == 0) + { + // Degenerate table data: without a first position coverage the gate + // cannot be known, so the lookup is always attempted. + digest.AddAll(); + return; + } + + this.coverageTables[0].CollectDigest(ref digest); + } + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; } - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, this.LookupFlags, this.MarkFilteringSet); - if (!AdvancedTypographicUtils.MatchCoverageSequence(iterator, this.coverageTables, index, index + count)) + // The input coverage array covers the whole input including its + // first glyph, so the match fills every position nested lookups + // address. + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + Span matchPositions = buffer.GetContextMatchPositions()[..AdvancedTypographicUtils.MaxContextLength]; + if (!AdvancedTypographicUtils.MatchCoverageSequence(iterator, this.coverageTables, index, index + count, buffer.LookupMask, false, matchPositions, out _)) { return false; } @@ -306,11 +338,10 @@ public override bool TryUpdatePosition( fontMetrics, table, feature, - this.LookupFlags, - this.MarkFilteringSet, this.sequenceLookupRecords, - collection, - index, + buffer, + matchPositions, + this.coverageTables.Length, count); } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType8SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType8SubTable.cs index bac0b6d63..e003d9640 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType8SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/LookupType8SubTable.cs @@ -76,18 +76,21 @@ public static LookupType8Format1SubTable Load(BigEndianBinaryReader reader, long return new LookupType8Format1SubTable(coverageTable, seqRuleSets, lookupFlags, markFilteringSet); } + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) { // Implements Chained Contexts Substitution, Format 1: // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#61-chained-contexts-substitution-format-1-simple-glyph-contexts - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -113,27 +116,46 @@ public override bool TryUpdatePosition( // Apply ruleset for the given glyph id. ChainedSequenceRuleTable[] rules = seqRuleSet.SequenceRuleTables; - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, this.LookupFlags, this.MarkFilteringSet); + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + + // Slot zero holds the coverage-matched glyph; the input match fills + // the rest, so nested lookups address the records the match + // consumed rather than a raw offset from the first. + Span matchPositions = buffer.GetContextMatchPositions(); + matchPositions[0] = index; for (int lookupIndex = 0; lookupIndex < rules.Length; lookupIndex++) { ChainedSequenceRuleTable rule = rules[lookupIndex]; - if (!AdvancedTypographicUtils.ApplyChainedSequenceRule(iterator, rule)) + if (!AdvancedTypographicUtils.ApplyChainedSequenceRule(iterator, rule, buffer.LookupMask, matchPositions[1..], out _)) { continue; } + if (buffer.NestingLimitReached) + { + return false; + } + bool hasChanged = false; + int matchCount = rule.InputSequence.Length + 1; + buffer.PushNestedApplication(); for (int j = 0; j < rule.SequenceLookupRecords.Length; j++) { SequenceLookupRecord sequenceLookupRecord = rule.SequenceLookupRecords[j]; LookupTable lookup = table.LookupList.LookupTables[sequenceLookupRecord.LookupListIndex]; - ushort sequenceIndex = sequenceLookupRecord.SequenceIndex; - if (lookup.TryUpdatePosition(fontMetrics, table, collection, feature, index + sequenceIndex, 1)) + int sequenceIndex = sequenceLookupRecord.SequenceIndex; + if (sequenceIndex >= matchCount) + { + continue; + } + + if (lookup.TryUpdatePosition(fontMetrics, table, buffer, feature, matchPositions[sequenceIndex], 1)) { hasChanged = true; } } + buffer.PopNestedApplication(); return hasChanged; } @@ -207,18 +229,21 @@ public static LookupType8Format2SubTable Load(BigEndianBinaryReader reader, long markFilteringSet); } + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) { // Implements Chained Contexts Substitution for Format 2: // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#62-chained-contexts-substitution-format-2-class-based-glyph-contexts - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -233,39 +258,62 @@ public override bool TryUpdatePosition( // Search in the class definition table to find the class value assigned to the currently glyph. int classId = this.inputClassDefinitionTable.ClassIndexOf(glyphId); - ChainedClassSequenceRuleTable[]? rules = classId >= 0 && classId < this.sequenceRuleSetTables.Length - ? this.sequenceRuleSetTables[classId].SubRules + ChainedClassSequenceRuleSetTable? ruleSet = classId >= 0 && classId < this.sequenceRuleSetTables.Length + ? this.sequenceRuleSetTables[classId] : null; - if (rules is null) + if (ruleSet is null) { return false; } + ChainedClassSequenceRuleTable[] rules = ruleSet.SubRules; + // Apply ruleset for the given glyph class id. - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, this.LookupFlags, this.MarkFilteringSet); + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + + // Slot zero holds the coverage-matched glyph; the input match fills + // the rest, so nested lookups address the records the match + // consumed rather than a raw offset from the first. Match storage is + // retained by the buffer because this method is entered from the + // per-glyph lookup loop. + Span matchPositions = buffer.GetContextMatchPositions(); + matchPositions[0] = index; for (int lookupIndex = 0; lookupIndex < rules.Length; lookupIndex++) { ChainedClassSequenceRuleTable rule = rules[lookupIndex]; - if (!AdvancedTypographicUtils.ApplyChainedClassSequenceRule(iterator, rule, this.inputClassDefinitionTable, this.backtrackClassDefinitionTable, this.lookaheadClassDefinitionTable)) + if (!AdvancedTypographicUtils.ApplyChainedClassSequenceRule(iterator, rule, this.inputClassDefinitionTable, this.backtrackClassDefinitionTable, this.lookaheadClassDefinitionTable, buffer.LookupMask, matchPositions[1..], out _)) { continue; } // It's a match. Perform position update and return true if anything changed. + if (buffer.NestingLimitReached) + { + return false; + } + bool hasChanged = false; + int matchCount = rule.InputSequence.Length + 1; + buffer.PushNestedApplication(); for (int j = 0; j < rule.SequenceLookupRecords.Length; j++) { SequenceLookupRecord sequenceLookupRecord = rule.SequenceLookupRecords[j]; LookupTable lookup = table.LookupList.LookupTables[sequenceLookupRecord.LookupListIndex]; - ushort sequenceIndex = sequenceLookupRecord.SequenceIndex; - if (lookup.TryUpdatePosition(fontMetrics, table, collection, feature, index + sequenceIndex, 1)) + int sequenceIndex = sequenceLookupRecord.SequenceIndex; + if (sequenceIndex >= matchCount) + { + continue; + } + + if (lookup.TryUpdatePosition(fontMetrics, table, buffer, feature, matchPositions[sequenceIndex], 1)) { hasChanged = true; } } + buffer.PopNestedApplication(); return hasChanged; } @@ -333,40 +381,73 @@ public static LookupType8Format3SubTable Load(BigEndianBinaryReader reader, long markFilteringSet); } + /// + public override void CollectDigest(ref GlyphSetDigest digest) + { + if (this.inputCoverageTables.Length == 0) + { + // Degenerate table data: without a first position coverage the gate + // cannot be known, so the lookup is always attempted. + digest.AddAll(); + return; + } + + this.inputCoverageTables[0].CollectDigest(ref digest); + } + /// public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; } - if (!AdvancedTypographicUtils.CheckAllCoverages(fontMetrics, this.LookupFlags, this.MarkFilteringSet, collection, index, count, this.inputCoverageTables, this.backtrackCoverageTables, this.lookaheadCoverageTables)) + // The input coverage array covers the whole input including its + // first glyph, so the match fills every position nested lookups + // address. + Span matchPositions = buffer.GetContextMatchPositions()[..AdvancedTypographicUtils.MaxContextLength]; + if (!AdvancedTypographicUtils.CheckAllCoverages(fontMetrics, this.LookupFlags, this.MarkFilteringSet, buffer, index, count, this.inputCoverageTables, this.backtrackCoverageTables, this.lookaheadCoverageTables, buffer.LookupMask, matchPositions, out _)) + { + return false; + } + + if (buffer.NestingLimitReached) { return false; } // It's a match. Perform position update and return true if anything changed. bool hasChanged = false; + int matchCount = this.inputCoverageTables.Length; + buffer.PushNestedApplication(); + foreach (SequenceLookupRecord lookupRecord in this.seqLookupRecords) { - ushort sequenceIndex = lookupRecord.SequenceIndex; + int sequenceIndex = lookupRecord.SequenceIndex; + if (sequenceIndex >= matchCount) + { + continue; + } + ushort lookupIndex = lookupRecord.LookupListIndex; + int position = matchPositions[sequenceIndex]; LookupTable lookup = table.LookupList.LookupTables[lookupIndex]; - if (lookup.TryUpdatePosition(fontMetrics, table, collection, feature, index + sequenceIndex, count - sequenceIndex)) + if (lookup.TryUpdatePosition(fontMetrics, table, buffer, feature, position, count - (position - index))) { hasChanged = true; } } + buffer.PopNestedApplication(); return hasChanged; } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/NotImplementedSubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/NotImplementedSubTable.cs index c3656b607..70a8edf1c 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/NotImplementedSubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/NotImplementedSubTable.cs @@ -21,7 +21,7 @@ public NotImplementedSubTable() public override bool TryUpdatePosition( FontMetrics fontMetrics, GPosTable table, - GlyphPositioningCollection collection, + ShapingBuffer buffer, Tag feature, int index, int count) diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPosTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPosTable.cs index d2241c8d5..3459a58f1 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPosTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GPosTable.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using SixLabors.Fonts.Tables.AdvancedTypographic.GPos; using SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; @@ -15,6 +16,13 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic; /// internal class GPosTable : Table { + /// + /// Caches resolved feature lookups per stage feature, script, and language + /// candidates. See for the variable-font bypass + /// and the read-only contract on cached lists. + /// + private readonly ConcurrentDictionary> featureLookupsCache = new(); + /// /// The tag for the horizontal kerning feature ('kern'). /// @@ -25,10 +33,15 @@ internal class GPosTable : Table /// private static readonly Tag VKernTag = Tag.Parse("vkrn"); + /// + /// The invalid but widely shipped language system record tag 'dflt'. + /// + private static readonly Tag DefaultLangSysTag = Tag.Parse("dflt"); + /// /// The OpenType table tag for the GPOS table. /// - internal const string TableName = "GPOS"; + public const string TableName = "GPOS"; /// /// Initializes a new instance of the class. @@ -88,7 +101,7 @@ public GPosTable(ScriptList? scriptList, FeatureListTable featureList, LookupLis /// /// The big endian binary reader. /// The . - internal static GPosTable Load(BigEndianBinaryReader reader) + public static GPosTable Load(BigEndianBinaryReader reader) { // GPOS Header, Version 1.0 // +----------+-------------------+-----------------------------------------------------------+ @@ -144,58 +157,87 @@ internal static GPosTable Load(BigEndianBinaryReader reader) } /// - /// Tries to update the positions of glyphs in the collection using GPOS lookup rules. + /// Tries to update the positions of glyphs in the buffer using GPOS lookup rules. /// /// The font metrics. - /// The glyph positioning collection. + /// The glyph positioning buffer. /// When this method returns, indicates whether kerning was applied. /// if any positioning was updated; otherwise, . - public bool TryUpdatePositions(FontMetrics fontMetrics, GlyphPositioningCollection collection, out bool kerned) + public bool TryUpdatePositions(FontMetrics fontMetrics, ShapingBuffer buffer, out bool kerned) { // Set max constraints to prevent OutOfMemoryException or infinite loops from attacks. - int maxCount = AdvancedTypographicUtils.GetMaxAllowableShapingCollectionCount(collection.Count); - int maxOperationsCount = AdvancedTypographicUtils.GetMaxAllowableShapingOperationsCount(collection.Count); + int maxCount = AdvancedTypographicUtils.GetMaxAllowableShapingCollectionCount(buffer.Count); + int maxOperationsCount = AdvancedTypographicUtils.GetMaxAllowableShapingOperationsCount(buffer.Count); int currentOperations = 0; bool maxOperationsReached = false; kerned = false; bool updated = false; - for (int i = 0; i < collection.Count; i++) + + // Segments recorded during an in-place substitution pass carry their plan; + // reuse them so one plan drives both tables and positioning never + // re-segments, re-creates, or re-plans. An empty list means records were + // seeded across buffers and positioning must segment for itself below. + List<(int Index, int Count, ScriptClass Script, ShapePlan Plan)> segments = buffer.SegmentPlans; + if (segments.Count > 0) + { + for (int s = 0; s < segments.Count; s++) + { + (int index, int count, ScriptClass script, ShapePlan shapePlan) = segments[s]; + if (shapePlan.FontMetrics != fontMetrics) + { + // Glyph ids and lookup indices are local to the font bound to + // the plan, even though all fonts share one positioning buffer. + continue; + } + + updated |= this.PositionSegment( + fontMetrics, + buffer, + shapePlan, + index, + count, + maxOperationsCount, + ref currentOperations, + ref kerned, + ref maxOperationsReached); + + if (maxOperationsReached) + { + break; + } + } + + return updated; + } + + for (int i = 0; i < buffer.Count; i++) { - if (!collection.ShouldProcess(fontMetrics, i)) + if (!buffer.ShouldProcess(fontMetrics, i)) { continue; } - ScriptClass current = this.GetScriptClass(CodePoint.GetScriptClass(collection[i].CodePoint)); + ScriptItemizer.ShapingRun run = new(buffer, i); int index = i; int count = 1; - while (i < collection.Count - 1) + while (i < buffer.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. int ni = i + 1; - GlyphShapingData nextData = collection[ni]; - if (!collection.ShouldProcess(fontMetrics, ni)) + if (!buffer.ShouldProcess(fontMetrics, ni)) { break; } - ScriptClass next = this.GetScriptClass(CodePoint.GetScriptClass(nextData.CodePoint)); - if (next != current && - current is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited && - next is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited) + if (!run.TryInclude(buffer, ni)) { break; } - if (current is ScriptClass.Common or ScriptClass.Unknown or ScriptClass.Inherited) - { - current = next; - } - i++; count++; @@ -205,88 +247,164 @@ current is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClas } } - Tag unicodeScriptTag = this.GetUnicodeScriptTag(current); - BaseShaper shaper = ShaperFactory.Create(current, unicodeScriptTag, fontMetrics, collection.TextOptions); + Tag unicodeScriptTag = this.GetUnicodeScriptTag(run.Script); + ShapePlan shapePlan = buffer.GetOrCreatePlan(run.Script, unicodeScriptTag, fontMetrics, run.Culture, run.FeatureTags); + + // Plan positioning features for each glyph. Records seeded across buffers + // had their feature registrations cleared, so this pass re-plans. + shapePlan.Shaper.Plan(fontMetrics, buffer, index, count); + + updated |= this.PositionSegment( + fontMetrics, + buffer, + shapePlan, + index, + count, + maxOperationsCount, + ref currentOperations, + ref kerned, + ref maxOperationsReached); - if (shaper.MarkZeroingMode == MarkZeroingMode.PreGPos) + if (i >= maxCount || maxOperationsReached) { - ZeroMarkAdvances(fontMetrics, collection, index, count); + return updated; } + } + + return updated; + } + + /// + /// Applies the positioning stages of a planned segment: mark zeroing, the + /// pause-delimited stage groups in lookup-index order, attachment resolution, and + /// position materialization. The caller supplies the plan that covers the + /// segment, either freshly re-planned or reused from the substitution pass. + /// + /// The font metrics. + /// The glyph positioning buffer. + /// The plan covering the segment. + /// The starting index of the segment. + /// The number of glyphs in the segment. + /// The maximum allowable operations count. + /// The current operations counter. + /// Set when a kerning feature applied. + /// Set when the operations budget ran out. + /// if any positioning was updated. + private bool PositionSegment( + FontMetrics fontMetrics, + ShapingBuffer buffer, + ShapePlan shapePlan, + int index, + int count, + int maxOperationsCount, + ref int currentOperations, + ref bool kerned, + ref bool maxOperationsReached) + { + bool updated = false; + + if (shapePlan.Shaper.MarkZeroingMode == MarkZeroingMode.PreGPos) + { + AdvancedTypographicUtils.ZeroMarkAdvances(fontMetrics, buffer, index, count, false); + } + + // Stages are applied in pause-delimited groups: a stage action is a + // synchronization point, and between two actions every registered + // feature's lookups apply together in lookup-list order, the order the + // specification defines for lookups within a single application pass. A + // lookup registered by several of the group's features applies once with + // their glyph masks combined. Group boundaries, merged lookup lists, and + // entry masks are all prebuilt on the plan. + List> groups = shapePlan.GetOrBuildGPosStageGroups(this); + List shapingStages = shapePlan.Stages; + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, default, 0); + int segmentEnd = index + count; + for (int g = 0; g < groups.Count; g++) + { + ShapePlanStageGroup group = groups[g]; + List<(Tag Feature, ushort Index, LookupTable LookupTable, uint Mask, bool AutoZwnj, bool AutoZwj, bool Random, bool PerSyllable)> merged = group.Lookups; + + shapingStages[group.Start].PreProcessFeature(shapePlan, buffer, index, count); - // Plan positioning features for each glyph. - shaper.Plan(collection, index, count); - IEnumerable shapingStages = shaper.GetShapingStages(); - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, default, 0); - foreach (ShapingStage stage in shapingStages) + for (int m = 0; m < merged.Count; m++) { - stage.PreProcessFeature(collection, index, count); + (Tag feature, ushort _, LookupTable featureLookupTable, uint featureMask, bool autoZwnj, bool autoZwj, bool random, bool perSyllable) = merged[m]; + + // Skip the whole lookup when its mask reaches no record, or when + // its coverage cannot intersect any glyph id the buffer has ever + // contained; most fonts carry many lookups for glyphs a given + // text never produces. + if ((featureMask & buffer.EnabledFeatureMaskUnion) == 0 + || !featureLookupTable.Digest.MightIntersect(buffer.GlyphDigest)) + { + continue; + } - Tag featureTag = stage.FeatureTag; - if (this.TryGetFeatureLookups(fontMetrics, in featureTag, current, out List<(Tag Feature, ushort Index, LookupTable LookupTable)>? lookups)) + // Matcher state is observable only while this lookup is attempted. + // Stamp it after the whole-lookup gates so rejected entries do not + // rewrite the buffer's state. + buffer.SetLookupMatchState(featureMask, autoZwnj, autoZwj, random, perSyllable); + iterator.Reset(index, featureLookupTable.LookupFlags, featureLookupTable.MarkFilteringSet); + + while (iterator.Index < segmentEnd) { - // Apply features in order. - foreach ((Tag Feature, ushort Index, LookupTable LookupTable) featureLookup in lookups) + if (currentOperations++ >= maxOperationsCount) { - Tag feature = featureLookup.Feature; - LookupTable featureLookupTable = featureLookup.LookupTable; - iterator.Reset(index, featureLookupTable.LookupFlags, featureLookupTable.MarkFilteringSet); - - while (iterator.Index < index + count) - { - if (currentOperations++ >= maxOperationsCount) - { - maxOperationsReached = true; - goto EndLookups; - } - - if (!collection[iterator.Index].EnabledFeatureTags.Contains(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(); - } + maxOperationsReached = true; + goto EndLookups; } - } - stage.PostProcessFeature(collection, index, count); - } + // The digest cheaply rejects glyphs no subtable of this + // lookup can affect; a maybe falls through to the exact + // coverage test inside. + ref GlyphShapingData glyphData = ref buffer[iterator.Index]; + if ((glyphData.FeatureMask & featureMask) == 0 || !featureLookupTable.Digest.MightContain(glyphData.GlyphId)) + { + iterator.Next(); + continue; + } - EndLookups: - if (shaper.MarkZeroingMode == MarkZeroingMode.PostGpos) - { - ZeroMarkAdvances(fontMetrics, collection, index, count); + bool success = featureLookupTable.TryUpdatePosition(fontMetrics, this, buffer, feature, iterator.Index, segmentEnd - iterator.Index); + kerned |= success && (feature == KernTag || feature == VKernTag); + updated |= success; + iterator.Next(); + } } - FixCursiveAttachment(collection, index, count); - FixMarkAttachment(collection, index, count); - UpdatePositions(fontMetrics, collection, index, count); + shapingStages[group.End - 1].PostProcessFeature(shapePlan, buffer, index, count); + } - if (i >= maxCount || maxOperationsReached) - { - return updated; - } + EndLookups: + if (shapePlan.Shaper.MarkZeroingMode == MarkZeroingMode.PostGpos) + { + AdvancedTypographicUtils.ZeroMarkAdvances(fontMetrics, buffer, index, count, false); } + ZeroDefaultIgnorableAdvances(buffer, index, count); + FixCursiveAttachment(buffer, index, count); + FixMarkAttachment(buffer, index, count); + UpdatePositions(buffer, index, count); + return updated; } /// - /// Tries to get the feature lookups for the given stage feature and script. + /// Tries to get the feature lookups for the given stage feature, script, and language. /// /// The font metrics. /// The feature tag for the current shaping stage. /// The script class. + /// + /// The candidate OpenType language system tags, most specific first. An empty array + /// selects the default language system. + /// /// When this method returns, contains the list of feature lookups if found. /// if lookups were found; otherwise, . - private bool TryGetFeatureLookups( + public bool TryGetFeatureLookups( FontMetrics fontMetrics, in Tag stageFeature, ScriptClass script, + Tag[] languageTags, [NotNullWhen(true)] out List<(Tag Feature, ushort Index, LookupTable LookupTable)>? value) { if (this.ScriptList is null) @@ -295,11 +413,52 @@ private bool TryGetFeatureLookups( return false; } + // Feature variations resolve against the font's live variation coordinates, so + // caching would mix results across differently configured variable fonts. + if (this.FeatureVariations is not null) + { + value = this.ResolveFeatureLookups(fontMetrics, stageFeature, script, languageTags); + return value.Count > 0; + } + + // Resolution depends only on this table's data for a given feature, script, + // and language candidates, so results, including empty ones, are cached for + // the table's lifetime. The cached list is shared: consumers must not mutate + // it. A concurrent first-resolution race only duplicates deterministic work. + FeatureLookupsKey key = new(stageFeature, script, languageTags); + if (!this.featureLookupsCache.TryGetValue(key, out value)) + { + value = this.ResolveFeatureLookups(fontMetrics, stageFeature, script, languageTags); + this.featureLookupsCache.TryAdd(key, value); + } + + return value.Count > 0; + } + + /// + /// Resolves the feature lookups for the given stage feature, script, and language + /// through the selection ladder documented inline. + /// + /// The font metrics. + /// The feature tag for the current shaping stage. + /// The script class. + /// The candidate OpenType language system tags, most specific first. + /// The resolved lookups; empty when the feature yields none. + private List<(Tag Feature, ushort Index, LookupTable LookupTable)> ResolveFeatureLookups( + FontMetrics fontMetrics, + Tag stageFeature, + ScriptClass script, + Tag[] languageTags) + { // Resolve feature substitutions from FeatureVariations (variable fonts). FeatureTableSubstitutionRecord[]? substitutions = this.FeatureVariations ?.FindMatchingSubstitutions(fontMetrics.GetNormalizedCoordinates()); - ScriptListTable scriptListTable = this.ScriptList.Default(); + // Step 1: script selection. Map the Unicode script class onto the font's + // script table, falling back to the font's first script when the font does not + // declare the script. + // The caching entry point rejects a null script list before dispatching here. + ScriptListTable scriptListTable = this.ScriptList!.Default(); Tag[] tags = UnicodeScriptTagMap.Instance[script]; for (int i = 0; i < tags.Length; i++) { @@ -310,15 +469,52 @@ private bool TryGetFeatureLookups( } } + // Step 2: language selection. Walk the culture's candidate tags in priority + // order, most specific first, scanning the script's named language systems for + // a tag match; the first candidate the font declares wins, so a zh-HK run + // selects ZHH before ZHT. A match commits even when the selected language + // system lacks this stage feature: per the specification a LangSys table is the + // complete feature set for its language, so falling through to the default + // below would merge two languages' features, exactly the output language + // systems exist to prevent. An empty candidate array skips this step entirely. + LangSysTable[] langSysTables = scriptListTable.LangSysTables; + for (int i = 0; i < languageTags.Length; i++) + { + uint language = languageTags[i].Value; + for (int j = 0; j < langSysTables.Length; j++) + { + if (langSysTables[j].LangSysTag == language) + { + return this.GetFeatureLookups(stageFeature, substitutions, langSysTables[j]); + } + } + } + + // Step 3: no culture, or no candidate the font declares. A language system + // record explicitly tagged dflt is preferred over the true default: the tag is + // invalid per the specification, but fonts built from old documentation typos + // carry one, and the reference engines honor it. + LangSysTable[] langSysRecords = scriptListTable.LangSysTables; + for (int i = 0; i < langSysRecords.Length; i++) + { + if (langSysRecords[i].LangSysTag == DefaultLangSysTag.Value) + { + return this.GetFeatureLookups(stageFeature, substitutions, langSysRecords[i]); + } + } + LangSysTable? defaultLangSysTable = scriptListTable.DefaultLangSysTable; if (defaultLangSysTable != null) { - value = this.GetFeatureLookups(stageFeature, substitutions, defaultLangSysTable); - return value.Count > 0; + return this.GetFeatureLookups(stageFeature, substitutions, defaultLangSysTable); } - value = this.GetFeatureLookups(stageFeature, substitutions, scriptListTable.LangSysTables); - return value.Count > 0; + // Step 4: no default language system either. Nothing applies: the font scoped + // every feature to specific languages, and the reference engines agree that no + // language system means no lookups. Features such as SimSun's vertical + // alternates, which live only under its Chinese language systems, are reached by + // setting TextOptions.Culture to a Chinese culture. + return []; } /// @@ -413,106 +609,178 @@ private static FeatureTable ResolveFeatureTable( } /// - /// Maps a script class to an effective script class, checking whether the font supports it. - /// Falls back to if the script is not present in the font. + /// Zeros the movement-axis geometry of default ignorables before attachment offsets are resolved. /// - /// The script class to check. - /// The effective script class. - private ScriptClass GetScriptClass(ScriptClass current) + /// The glyph positioning buffer. + /// The starting index. + /// The number of glyphs to process. + private static void ZeroDefaultIgnorableAdvances(ShapingBuffer buffer, int index, int count) { - if (current is ScriptClass.Common or ScriptClass.Unknown or ScriptClass.Inherited) + if (!buffer.HasDefaultIgnorables) { - return current; + return; } - if (this.ScriptList is null) + bool isVertical = buffer.TextOptions.LayoutMode.IsVertical(); + int end = index + count; + for (int i = index; i < end; i++) { - return ScriptClass.Default; - } - - Tag[] tags = UnicodeScriptTagMap.Instance[current]; + ref GlyphShapingData data = ref buffer[i]; + if (!data.IsDefaultIgnorable || data.IsSubstituted) + { + continue; + } - for (int i = 0; i < tags.Length; i++) - { - if (this.ScriptList.TryGetValue(tags[i].Value, out ScriptListTable? _)) + // Attachment propagation sums every intervening advance. The invisible + // record therefore has to become zero-width before that sum is evaluated, + // while retaining its cross-axis adjustment until final hiding. + ref GlyphShapingPosition position = ref buffer.PositionAt(i); + position.Bounds.Width = 0; + position.Bounds.Height = 0; + if (isVertical) + { + position.Bounds.Y = 0; + } + else { - return current; + position.Bounds.X = 0; } } - - // Script for `current` not present in the font: use default shaper. - return ScriptClass.Default; } /// /// Fixes cursive attachment positioning by propagating Y (or X for vertical) offsets. /// - /// The glyph positioning collection. + /// + /// The parent chain is resolved before its minor-axis offset is added to a + /// cursively attached child. Direction controls the outer traversal order, + /// while recursion completes every parent before its child. + /// + /// The glyph positioning buffer. /// The starting index. /// The number of glyphs to process. - private static void FixCursiveAttachment(GlyphPositioningCollection collection, int index, int count) + private static void FixCursiveAttachment(ShapingBuffer buffer, int index, int count) { - LayoutMode layoutMode = collection.TextOptions.LayoutMode; - for (int i = 0; i < count; i++) + int end = index + count; + int currentIndex = index; + int increment = 1; + if (!buffer.TextOptions.LayoutMode.IsVertical() + && buffer[index].Direction == TextDirection.RightToLeft) { - int currentIndex = i + index; - GlyphShapingData data = collection[currentIndex]; - if (data.CursiveAttachment != -1) - { - int j = data.CursiveAttachment + currentIndex; - if (j < index || j >= index + count) - { - return; - } + currentIndex = end - 1; + end = index - 1; + increment = -1; + } - GlyphShapingData cursiveData = collection[j]; - if (!AdvancedTypographicUtils.IsVerticalGlyph(data.CodePoint, layoutMode)) - { - data.Bounds.Y += cursiveData.Bounds.Y; - } - else - { - data.Bounds.X += cursiveData.Bounds.X; - } + while (currentIndex != end) + { + ref GlyphShapingPosition position = ref buffer.PositionAt(currentIndex); + if (position.CursiveAttachment != GlyphShapingPosition.NoCursiveAttachment) + { + PropagateCursiveAttachment(buffer, index, index + count, currentIndex, AdvancedTypographicUtils.MaxNestingLevel); } + + currentIndex += increment; + } + } + + /// + /// Resolves one cursive attachment chain and accumulates its parent's minor-axis offset into the child. + /// + /// The glyph positioning buffer. + /// The first index in the positioned segment. + /// The index immediately after the positioned segment. + /// The child glyph whose attachment is being resolved. + /// The number of parent links that may still be followed. + private static void PropagateCursiveAttachment(ShapingBuffer buffer, int start, int end, int currentIndex, int nestingLevel) + { + ref GlyphShapingPosition position = ref buffer.PositionAt(currentIndex); + int chain = position.CursiveAttachment; + position.CursiveAttachment = GlyphShapingPosition.NoCursiveAttachment; + + int parentIndex = currentIndex + chain; + if (parentIndex < start || parentIndex >= end || nestingLevel == 0) + { + return; + } + + ref GlyphShapingPosition parent = ref buffer.PositionAt(parentIndex); + if (parent.CursiveAttachment != GlyphShapingPosition.NoCursiveAttachment) + { + PropagateCursiveAttachment(buffer, start, end, parentIndex, nestingLevel - 1); + } + + // Cursive attachment only accumulates the cross-run axis. Main-axis + // advances were resolved by the lookup itself. + if (!AdvancedTypographicUtils.IsVerticalGlyph(buffer[currentIndex].CodePoint, buffer.TextOptions.LayoutMode)) + { + position.Bounds.Y += parent.Bounds.Y; + } + else + { + position.Bounds.X += parent.Bounds.X; } } /// /// Fixes mark attachment positioning by propagating offsets from base glyphs. /// - /// The glyph positioning collection. + /// The glyph positioning buffer. /// The starting index. /// The number of glyphs to process. - private static void FixMarkAttachment(GlyphPositioningCollection collection, int index, int count) + private static void FixMarkAttachment(ShapingBuffer buffer, int index, int count) { for (int i = 0; i < count; i++) { int currentIndex = i + index; - GlyphShapingData data = collection[currentIndex]; - if (data.MarkAttachment != -1) + ref GlyphShapingPosition position = ref buffer.PositionAt(currentIndex); + if (position.MarkAttachment != -1) { - int j = data.MarkAttachment; - GlyphShapingData markData = collection[j]; - data.Bounds.X += markData.Bounds.X; - data.Bounds.Y += markData.Bounds.Y; + int j = position.MarkAttachment; + bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(buffer[currentIndex].CodePoint, buffer.TextOptions.LayoutMode); + if (isVertical) + { + GetVerticalOrigin(buffer.MetricsAt(currentIndex).Metrics, buffer.UseShapingVerticalOrigin, out int markOriginX, out int markOriginY); + GetVerticalOrigin(buffer.MetricsAt(j).Metrics, buffer.UseShapingVerticalOrigin, out int baseOriginX, out int baseOriginY); + + // OpenType mark attachment replaces the mark offset with its + // anchor delta. Origins are applied only when the result is + // consumed, so exchange the child origin for the parent's here. + // The pass policy keeps public shaping aligned with shaping + // engines while layout retains the browser fallback. + position.Bounds.X += markOriginX - baseOriginX; + position.Bounds.Y += markOriginY - baseOriginY; + } + + position.Bounds.X += buffer.PositionAt(j).Bounds.X; + position.Bounds.Y += buffer.PositionAt(j).Bounds.Y; - if (data.Direction == TextDirection.LeftToRight) + if (isVertical) + { + for (int k = j; k < currentIndex; k++) + { + // Vertical positions store the magnitude of the downward + // advance, while the shaping coordinate system publishes its + // Y-up equivalent as a negative value. Subtracting that signed + // advance therefore adds the stored height. + position.Bounds.X -= buffer.PositionAt(k).Bounds.Width; + position.Bounds.Y += buffer.PositionAt(k).Bounds.Height; + } + } + else if (buffer[currentIndex].Direction == TextDirection.LeftToRight) { for (int k = j; k < currentIndex; k++) { - markData = collection[k]; - data.Bounds.X -= markData.Bounds.Width; - data.Bounds.Y -= markData.Bounds.Height; + position.Bounds.X -= buffer.PositionAt(k).Bounds.Width; + position.Bounds.Y -= buffer.PositionAt(k).Bounds.Height; } } else { for (int k = j + 1; k < currentIndex + 1; k++) { - markData = collection[k]; - data.Bounds.X += markData.Bounds.Width; - data.Bounds.Y += markData.Bounds.Height; + position.Bounds.X += buffer.PositionAt(k).Bounds.Width; + position.Bounds.Y += buffer.PositionAt(k).Bounds.Height; } } } @@ -520,39 +788,45 @@ private static void FixMarkAttachment(GlyphPositioningCollection collection, int } /// - /// Zeros the advance widths and heights for mark glyphs within the specified range. + /// Gets the vertical origin for a glyph in font design units. /// - /// The font metrics. - /// The glyph positioning collection. - /// The starting index. - /// The number of glyphs to process. - private static void ZeroMarkAdvances(FontMetrics fontMetrics, GlyphPositioningCollection collection, int index, int count) + /// The glyph metrics. + /// Whether to use the public shaping fallback when the font has no authored vertical origin. + /// The horizontal origin. + /// The vertical origin. + private static void GetVerticalOrigin(FontGlyphMetrics metrics, bool useShapingVerticalOrigin, out int x, out int y) { - for (int i = 0; i < count; i++) + // OpenType vertical origins are horizontally centered on the glyph's + // nominal horizontal advance, using integer division in font units. + x = metrics.AdvanceWidth / 2; + + VerticalMetrics verticalMetrics = metrics.FontMetrics.VerticalMetrics; + if (verticalMetrics.Synthesized && useShapingVerticalOrigin) { - int currentIndex = i + index; - GlyphShapingData data = collection[currentIndex]; - if (AdvancedTypographicUtils.IsMarkGlyph(fontMetrics, data.GlyphId, data)) - { - data.Bounds.Width = 0; - data.Bounds.Height = 0; - } + // Public shaping follows the shaping-engine fallback by centering each + // glyph's extents within the font's ascender-to-descender height. + float fontAdvance = verticalMetrics.Ascender - verticalMetrics.Descender; + y = (int)(metrics.Bounds.Max.Y + MathF.Floor((fontAdvance - metrics.Height) * .5F)); + return; } + + // Authored origins are shared by both contracts. When tables are absent, + // synthesized TopSideBearing resolves to the font-ascent origin used by + // browsers for layout and painting. + y = (int)(metrics.Bounds.Max.Y + metrics.TopSideBearing); } /// - /// Updates glyph positions in the collection for the specified range. + /// Updates glyph positions in the buffer for the specified range. /// - /// The font metrics. - /// The glyph positioning collection. + /// The glyph positioning buffer. /// The starting index. /// The number of glyphs to process. - private static void UpdatePositions(FontMetrics fontMetrics, GlyphPositioningCollection collection, int index, int count) + private static void UpdatePositions(ShapingBuffer buffer, int index, int count) { for (int i = 0; i < count; i++) { - int currentIndex = i + index; - collection.UpdatePosition(fontMetrics, currentIndex); + buffer.UpdatePosition(i + index); } } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupListTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupListTable.cs index b221bc9aa..d98908b7c 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupListTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupListTable.cs @@ -92,6 +92,25 @@ private LookupTable( this.LookupFlags = lookupFlags; this.MarkFilteringSet = markFilteringSet; this.LookupSubTables = lookupSubTables; + this.IsReverse = lookupType == 8 || (lookupType == 7 && lookupSubTables.Length > 0 && lookupSubTables[0].IsReverse); + + // The union of every subtable's gating coverage: a glyph outside the digest + // cannot be affected by any subtable of this lookup, so application skips it + // without touching the subtables. See GlyphSetDigest for the accuracy contract. + GlyphSetDigest digest = default; + for (int i = 0; i < lookupSubTables.Length; i++) + { + // Each subtable also carries its own digest so application can skip + // subtables whose gating coverage cannot contain the current glyph + // without paying the virtual probe. Contextual formats that expose no + // leading coverage flood their digest and therefore always pass. + GlyphSetDigest subTableDigest = default; + lookupSubTables[i].CollectDigest(ref subTableDigest); + lookupSubTables[i].Digest = subTableDigest; + lookupSubTables[i].CollectDigest(ref digest); + } + + this.Digest = digest; } /// @@ -115,6 +134,16 @@ private LookupTable( /// public LookupSubTable[] LookupSubTables { get; } + /// + /// Gets the approximate membership filter for the glyphs this lookup can affect. + /// + public GlyphSetDigest Digest { get; } + + /// + /// Gets a value indicating whether this lookup applies from the end of the glyph sequence to the beginning. + /// + public bool IsReverse { get; } + /// /// Loads the from the binary reader at the given offset. /// @@ -165,29 +194,77 @@ public static LookupTable Load(BigEndianBinaryReader reader, long offset) } /// - /// Attempts to perform a glyph substitution at the specified index in the collection. + /// Attempts to perform a glyph substitution at the specified index in the buffer. /// /// The font metrics. /// The GSUB table. - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The feature tag to apply. - /// The index in the collection at which to attempt substitution. + /// The applying lookup's combined mask. + /// The index in the buffer at which to attempt substitution. /// The number of glyphs in the input sequence to consider. /// if a substitution was performed; otherwise, . public bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { + ushort glyphId = buffer[index].GlyphId; foreach (LookupSubTable subTable in this.LookupSubTables) { - if (subTable.TrySubstitution(fontMetrics, table, collection, feature, index, count)) + // A glyph outside the subtable's digest cannot match its coverage, so the + // probe (a virtual call, coverage search, or full context-match attempt) + // is skipped entirely. + if (!subTable.Digest.MightContain(glyphId)) + { + continue; + } + + // Nested applications consume through the pass cursor exactly as + // top-level ones do; the contextual match brings the cursor to each + // record before recursing. + buffer.DirectConsume = subTable.ConsumesDirectly; + if (subTable.TrySubstitution(fontMetrics, table, buffer, feature, lookupMask, index, count)) { // A lookup is finished for a glyph after the client locates the target // glyph or glyph context and performs a substitution, if specified. + buffer.DirectConsume = false; + return true; + } + } + + buffer.DirectConsume = false; + return false; + } + + /// + /// Tests whether this lookup would trigger a substitution on the given glyph + /// sequence without applying it. Shapers use this to probe a feature's effect + /// while classifying glyphs, before any real substitution runs. + /// + /// The glyph id sequence to query. + /// Whether matching against context outside the sequence is disallowed. + /// if a substitution would be triggered. + public bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + { + if (glyphs.Length == 0 || !this.Digest.MightContain(glyphs[0])) + { + return false; + } + + foreach (LookupSubTable subTable in this.LookupSubTables) + { + if (!subTable.Digest.MightContain(glyphs[0])) + { + continue; + } + + if (subTable.WouldApply(glyphs, zeroContext)) + { return true; } } @@ -253,20 +330,60 @@ protected LookupSubTable(LookupFlags lookupFlags, ushort markFilteringSet) public ushort MarkFilteringSet { get; } /// - /// Attempts to perform a glyph substitution at the specified index in the collection. + /// Gets a value indicating whether the subtable's replacements consume exactly + /// the records they match, so top-level application may produce them through + /// the pass cursor. Contextual and reverse subtables leave this false: their + /// replacements run in place and the driver alone advances the cursor. + /// + public virtual bool ConsumesDirectly => false; + + /// + /// Gets a value indicating whether the owning lookup applies from the end of the glyph sequence to the beginning. + /// + public virtual bool IsReverse => false; + + /// + /// Gets or sets the approximate membership filter for the glyphs this subtable can + /// affect. Assigned once by the owning during construction. + /// + public GlyphSetDigest Digest { get; internal set; } + + /// + /// Adds the coverage that gates this subtable's applicability to the digest. + /// The default adds every glyph so the lookup is always attempted, the correct + /// conservative behavior for subtables whose gating coverage is unknown. + /// + /// The digest to add to. + public virtual void CollectDigest(ref GlyphSetDigest digest) => digest.AddAll(); + + /// + /// Attempts to perform a glyph substitution at the specified index in the buffer. /// /// The font metrics. /// The GSUB table. - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The feature tag to apply. - /// The index in the collection at which to attempt substitution. + /// The applying lookup's combined mask. + /// The index in the buffer at which to attempt substitution. /// The number of glyphs in the input sequence to consider. /// if a substitution was performed; otherwise, . public abstract bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count); + + /// + /// Tests whether this subtable would trigger a substitution on the given glyph + /// sequence without applying it. The first glyph anchors the match and the + /// sequence length must account for every matched glyph; context outside the + /// sequence is consulted only when permits it. + /// + /// The glyph id sequence to query. + /// Whether matching against context outside the sequence is disallowed. + /// if a substitution would be triggered. + public abstract bool WouldApply(ReadOnlySpan glyphs, bool zeroContext); } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType1SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType1SubTable.cs index 8b83cac8f..2f3d54878 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType1SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType1SubTable.cs @@ -65,6 +65,9 @@ private LookupType1Format1SubTable(ushort deltaGlyphId, CoverageTable coverageTa this.coverageTable = coverageTable; } + /// + public override bool ConsumesDirectly => true; + /// /// Loads the single substitution format 1 subtable from the given offset. /// @@ -93,16 +96,20 @@ public static LookupType1Format1SubTable Load(BigEndianBinaryReader reader, long return new LookupType1Format1SubTable(deltaGlyphId, coverageTable, lookupFlags, markFilteringSet); } - /// + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + + /// public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -110,12 +117,16 @@ public override bool TrySubstitution( if (this.coverageTable.CoverageIndexOf(glyphId) > -1) { - collection.Replace(index, (ushort)(glyphId + this.deltaGlyphId), feature); + buffer.Replace(index, (ushort)(glyphId + this.deltaGlyphId), feature); return true; } return false; } + + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + => glyphs.Length == 1 && this.coverageTable.CoverageIndexOf(glyphs[0]) > -1; } /// @@ -149,6 +160,9 @@ private LookupType1Format2SubTable(ushort[] substituteGlyphs, CoverageTable cove this.coverageTable = coverageTable; } + /// + public override bool ConsumesDirectly => true; + /// /// Loads the single substitution format 2 subtable from the given offset. /// @@ -180,16 +194,20 @@ public static LookupType1Format2SubTable Load(BigEndianBinaryReader reader, long return new LookupType1Format2SubTable(substituteGlyphIds, coverageTable, lookupFlags, markFilteringSet); } - /// + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + + /// public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -199,10 +217,14 @@ public override bool TrySubstitution( if (offset > -1 && offset < this.substituteGlyphs.Length) { - collection.Replace(index, this.substituteGlyphs[offset], feature); + buffer.Replace(index, this.substituteGlyphs[offset], feature); return true; } return false; } + + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + => glyphs.Length == 1 && this.coverageTable.CoverageIndexOf(glyphs[0]) > -1; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType2SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType2SubTable.cs index 28b04e07d..47de5c001 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType2SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType2SubTable.cs @@ -62,6 +62,9 @@ private LookupType2Format1SubTable(SequenceTable[] sequenceTables, CoverageTable this.coverageTable = coverageTable; } + /// + public override bool ConsumesDirectly => true; + /// /// Loads the multiple substitution format 1 subtable from the given offset. /// @@ -115,16 +118,20 @@ public static LookupType2Format1SubTable Load(BigEndianBinaryReader reader, long return new LookupType2Format1SubTable(sequenceTables, coverageTable, lookupFlags, markFilteringSet); } - /// + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + + /// public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -134,13 +141,17 @@ public override bool TrySubstitution( if (offset > -1 && offset < this.sequenceTables.Length) { - collection.Replace(index, this.sequenceTables[offset].SubstituteGlyphs, feature); + buffer.Replace(index, this.sequenceTables[offset].SubstituteGlyphs, feature); return true; } return false; } + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + => glyphs.Length == 1 && this.coverageTable.CoverageIndexOf(glyphs[0]) > -1; + /// /// Represents a sequence table containing an ordered list of substitute glyph IDs /// that replace a single input glyph. diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType3SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType3SubTable.cs index 8cae811f1..c98b1bd3a 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType3SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType3SubTable.cs @@ -62,6 +62,9 @@ private LookupType3Format1SubTable(AlternateSetTable[] alternateSetTables, Cover this.coverageTable = coverageTable; } + /// + public override bool ConsumesDirectly => true; + /// /// Loads the alternate substitution format 1 subtable from the given offset. /// @@ -114,16 +117,20 @@ public static LookupType3Format1SubTable Load(BigEndianBinaryReader reader, long return new LookupType3Format1SubTable(alternateTables, coverageTable, lookupFlags, markFilteringSet); } - /// + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + + /// public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -133,16 +140,30 @@ public override bool TrySubstitution( if (offset > -1 && offset < this.alternateSetTables.Length) { - // TODO: We're just choosing the first alternative here. - // It looks like the choice is arbitrary and should be determined by - // the client. - collection.Replace(index, this.alternateSetTables[offset].AlternateGlyphs[0], feature); + ushort[] alternates = this.alternateSetTables[offset].AlternateGlyphs; + if (alternates.Length == 0) + { + return false; + } + + // Ordinary alternate features select their first requested value. The + // random feature advances once for each replaceable glyph and maps that + // value onto the font's alternate set. + int alternateIndex = buffer.LookupRandom + ? (int)(buffer.NextRandomNumber() % (uint)alternates.Length) + : 0; + + buffer.Replace(index, alternates[alternateIndex], feature); return true; } return false; } + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + => glyphs.Length == 1 && this.coverageTable.CoverageIndexOf(glyphs[0]) > -1; + /// /// Represents an alternate set table containing an array of alternate glyph IDs /// for a single input glyph. diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType4SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType4SubTable.cs index 94dee6362..e9f377bc7 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType4SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType4SubTable.cs @@ -64,6 +64,9 @@ private LookupType4Format1SubTable(LigatureSetTable[] ligatureSetTables, Coverag this.coverageTable = coverageTable; } + /// + public override bool ConsumesDirectly => true; + /// /// Loads the ligature substitution format 1 subtable from the given offset. /// @@ -145,16 +148,20 @@ public static LookupType4Format1SubTable Load(BigEndianBinaryReader reader, long return new LookupType4Format1SubTable(ligatureSetTables, coverageTable, lookupFlags, markFilteringSet); } - /// + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + + /// public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -167,8 +174,8 @@ public override bool TrySubstitution( } LigatureSetTable ligatureSetTable = this.ligatureSetTables[offset]; - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, this.LookupFlags, this.MarkFilteringSet); - Span matchBuffer = stackalloc int[AdvancedTypographicUtils.MaxContextLength]; + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + Span matchBuffer = buffer.GetContextMatchPositions()[..AdvancedTypographicUtils.MaxContextLength]; for (int i = 0; i < ligatureSetTable.Ligatures.Length; i++) { LigatureTable ligatureTable = ligatureSetTable.Ligatures[i]; @@ -179,115 +186,50 @@ public override bool TrySubstitution( continue; } - if (!AdvancedTypographicUtils.MatchInputSequence(iterator, feature, 1, ligatureTable.ComponentGlyphs, matchBuffer)) + if (!AdvancedTypographicUtils.MatchInputSequence(iterator, lookupMask, 1, ligatureTable.ComponentGlyphs, matchBuffer)) { continue; } - // From Harfbuzz: - // - If it *is* a mark ligature, we don't allocate a new ligature id, and leave - // the ligature to keep its old ligature id. This will allow it to attach to - // a base ligature in GPOS. Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH, - // and LAM,LAM,HEH for a ligature, they will leave SHADDA and FATHA with a - // ligature id and component value of 2. Then if SHADDA,FATHA form a ligature - // later, we don't want them to lose their ligature id/component, otherwise - // GPOS will fail to correctly position the mark ligature on top of the - // LAM,LAM,HEH ligature. See https://bugzilla.gnome.org/show_bug.cgi?id=676343 - // - // - If a ligature is formed of components that some of which are also ligatures - // themselves, and those ligature components had marks attached to *their* - // components, we have to attach the marks to the new ligature component - // positions! Now *that*'s tricky! And these marks may be following the - // last component of the whole sequence, so we should loop forward looking - // for them and update them. - // - // Eg. the sequence is LAM,LAM,SHADDA,FATHA,HEH, and the font first forms a - // 'calt' ligature of LAM,HEH, leaving the SHADDA and FATHA with a ligature - // id and component == 1. Now, during 'liga', the LAM and the LAM-HEH ligature - // form a LAM-LAM-HEH ligature. We need to reassign the SHADDA and FATHA to - // the new ligature with a component value of 2. - // - // This in fact happened to a font... See https://bugzilla.gnome.org/show_bug.cgi?id=437633 - GlyphShapingData data = collection[index]; - GlyphShapingClass shapingClass = AdvancedTypographicUtils.GetGlyphShapingClass(fontMetrics, glyphId, data); - bool isBaseLigature = shapingClass.IsBase; - bool isMarkLigature = shapingClass.IsMark; - Span matches = matchBuffer[..Math.Min(ligatureTable.ComponentGlyphs.Length, matchBuffer.Length)]; - for (int j = 0; j < matches.Length && isMarkLigature; j++) - { - GlyphShapingData match = collection[matches[j]]; - if (!AdvancedTypographicUtils.IsMarkGlyph(fontMetrics, match.GlyphId, match)) - { - isBaseLigature = false; - isMarkLigature = false; - break; - } - } + AdvancedTypographicUtils.ApplyLigatureSubstitution(fontMetrics, buffer, index, matches, ligatureTable.GlyphId, feature, count); + return true; + } - bool isLigature = !isBaseLigature && !isMarkLigature; + return false; + } - int ligatureId = isLigature ? 0 : collection.LigatureId++; - int lastLigatureId = data.LigatureId; - int lastComponentCount = data.CodePointCount; - int currentComponentCount = lastComponentCount; - int idx = index + 1; + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + { + int offset = this.coverageTable.CoverageIndexOf(glyphs[0]); + if (offset < 0 || offset >= this.ligatureSetTables.Length) + { + return false; + } - // Set ligatureID and ligatureComponent on glyphs that were skipped in the matched sequence. - // This allows GPOS to attach marks to the correct ligature components. - foreach (int matchIndex in matches) + foreach (LigatureTable ligature in this.ligatureSetTables[offset].Ligatures) + { + ushort[] components = ligature.ComponentGlyphs; + if (components.Length + 1 != glyphs.Length) { - // Don't assign new ligature components for mark ligatures (see above). - if (isLigature) - { - idx = matchIndex; - } - else - { - while (idx < matchIndex) - { - GlyphShapingData current = collection[idx]; - int currentLC = current.LigatureComponent == -1 ? 1 : current.LigatureComponent; - int ligatureComponent = currentComponentCount - lastComponentCount + Math.Min(currentLC, lastComponentCount); - current.LigatureId = ligatureId; - current.LigatureComponent = ligatureComponent; - - idx++; - } - } - - GlyphShapingData last = collection[idx]; - lastLigatureId = last.LigatureId; - lastComponentCount = last.CodePointCount; - currentComponentCount += lastComponentCount; - idx++; // Skip base glyph + continue; } - // Adjust ligature components for any marks following - if (lastLigatureId > 0 && !isLigature) + bool matched = true; + for (int i = 0; i < components.Length; i++) { - // Only check glyphs managed by current shaper. - int followingCount = count - (idx - index); - for (int j = idx; j < followingCount; j++) + if (glyphs[i + 1] != components[i]) { - GlyphShapingData current = collection[j]; - if (current.LigatureId == lastLigatureId) - { - int currentLC = current.LigatureComponent == -1 ? 1 : current.LigatureComponent; - int ligatureComponent = currentComponentCount - lastComponentCount + Math.Min(currentLC, lastComponentCount); - current.LigatureId = ligatureId; - current.LigatureComponent = ligatureComponent; - } - else - { - break; - } + matched = false; + break; } } - // Delete the matched glyphs, and replace the current glyph with the ligature glyph - collection.Replace(index, matches, ligatureTable.GlyphId, ligatureId, feature); - return true; + if (matched) + { + return true; + } } return false; diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType5SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType5SubTable.cs index ef772d12f..f68da05ef 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType5SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType5SubTable.cs @@ -79,16 +79,20 @@ public static LookupType5Format1SubTable Load(BigEndianBinaryReader reader, long return new LookupType5Format1SubTable(coverageTable, seqRuleSets, lookupFlags, markFilteringSet); } - /// + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + + /// public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -103,7 +107,12 @@ public override bool TrySubstitution( // TODO: Check this. // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#example-7-contextual-substitution-format-1 SequenceRuleSetTable ruleSetTable = this.seqRuleSetTables[offset]; - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, this.LookupFlags, this.MarkFilteringSet); + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + + // Slot zero holds the coverage-matched glyph; the sequence match fills + // the rest, so nested lookups address the records the match consumed. + Span matchPositions = buffer.GetContextMatchPositions(); + matchPositions[0] = index; foreach (SequenceRuleTable ruleTable in ruleSetTable.SequenceRuleTables) { int remaining = count - 1; @@ -113,7 +122,7 @@ public override bool TrySubstitution( continue; } - if (!AdvancedTypographicUtils.MatchSequence(iterator, 1, ruleTable.InputSequence)) + if (!AdvancedTypographicUtils.MatchSequence(iterator, 1, ruleTable.InputSequence, lookupMask, false, matchPositions[1..], out int matchEnd)) { continue; } @@ -123,12 +132,48 @@ public override bool TrySubstitution( fontMetrics, table, feature, - this.LookupFlags, - this.MarkFilteringSet, + lookupMask, ruleTable.SequenceLookupRecords, - collection, - index, - count); + buffer, + matchPositions, + seqLength + 1, + matchEnd); + } + + return false; + } + + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + { + int offset = this.coverageTable.CoverageIndexOf(glyphs[0]); + if (offset < 0 || offset >= this.seqRuleSetTables.Length) + { + return false; + } + + foreach (SequenceRuleTable rule in this.seqRuleSetTables[offset].SequenceRuleTables) + { + ushort[] input = rule.InputSequence; + if (input.Length + 1 != glyphs.Length) + { + continue; + } + + bool matched = true; + for (int i = 0; i < input.Length; i++) + { + if (glyphs[i + 1] != input[i]) + { + matched = false; + break; + } + } + + if (matched) + { + return true; + } } return false; @@ -193,16 +238,20 @@ public static LookupType5Format2SubTable Load(BigEndianBinaryReader reader, long return new LookupType5Format2SubTable(classSeqRuleSets, classDefTable, coverageTable, lookupFlags, markFilteringSet); } - /// + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + + /// public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -213,11 +262,11 @@ public override bool TrySubstitution( return false; } - // TODO: Check this. - // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#52-context-substitution-format-2-class-based-glyph-contexts int offset = this.classDefinitionTable.ClassIndexOf(glyphId); - if (offset < 0) + if (offset < 0 || offset >= this.sequenceRuleSetTables.Length) { + // A font may assign a class without supplying a corresponding rule set. + // The absent set has no substitutions rather than indexing past the table. return false; } @@ -227,7 +276,12 @@ public override bool TrySubstitution( return false; } - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, this.LookupFlags, this.MarkFilteringSet); + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + + // Slot zero holds the coverage-matched glyph; the sequence match fills + // the rest, so nested lookups address the records the match consumed. + Span matchPositions = buffer.GetContextMatchPositions(); + matchPositions[0] = index; foreach (ClassSequenceRuleTable ruleTable in ruleSetTable.SequenceRuleTables) { int remaining = count - 1; @@ -237,7 +291,7 @@ public override bool TrySubstitution( continue; } - if (!AdvancedTypographicUtils.MatchClassSequence(iterator, 1, ruleTable.InputSequence, this.classDefinitionTable)) + if (!AdvancedTypographicUtils.MatchClassSequence(iterator, 1, ruleTable.InputSequence, this.classDefinitionTable, lookupMask, false, matchPositions[1..], out int matchEnd)) { continue; } @@ -247,12 +301,52 @@ public override bool TrySubstitution( fontMetrics, table, feature, - this.LookupFlags, - this.MarkFilteringSet, + lookupMask, ruleTable.SequenceLookupRecords, - collection, - index, - count); + buffer, + matchPositions, + seqLength + 1, + matchEnd); + } + + return false; + } + + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + { + int classId = this.classDefinitionTable.ClassIndexOf(glyphs[0]); + ClassSequenceRuleTable[]? rules = classId >= 0 && classId < this.sequenceRuleSetTables.Length + ? this.sequenceRuleSetTables[classId]?.SequenceRuleTables + : null; + + if (rules is null) + { + return false; + } + + foreach (ClassSequenceRuleTable rule in rules) + { + ushort[] input = rule.InputSequence; + if (input.Length + 1 != glyphs.Length) + { + continue; + } + + bool matched = true; + for (int i = 0; i < input.Length; i++) + { + if (this.classDefinitionTable.ClassIndexOf(glyphs[i + 1]) != input[i]) + { + matched = false; + break; + } + } + + if (matched) + { + return true; + } } return false; @@ -309,24 +403,42 @@ public static LookupType5Format3SubTable Load(BigEndianBinaryReader reader, long return new LookupType5Format3SubTable(coverageTables, seqLookupRecords, lookupFlags, markFilteringSet); } - /// + /// + public override void CollectDigest(ref GlyphSetDigest digest) + { + if (this.coverageTables.Length == 0) + { + // Degenerate table data: without a first position coverage the gate + // cannot be known, so the lookup is always attempted. + digest.AddAll(); + return; + } + + this.coverageTables[0].CollectDigest(ref digest); + } + + /// public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; } // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#53-context-substitution-format-3-coverage-based-glyph-contexts - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, this.LookupFlags, this.MarkFilteringSet); - if (!AdvancedTypographicUtils.MatchCoverageSequence(iterator, this.coverageTables, index, index + count)) + // The input coverage array covers the whole input including its first + // glyph, so the match fills every position nested lookups address. + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + Span matchPositions = buffer.GetContextMatchPositions()[..AdvancedTypographicUtils.MaxContextLength]; + if (!AdvancedTypographicUtils.MatchCoverageSequence(iterator, this.coverageTables, index, index + count, lookupMask, false, matchPositions, out int matchEnd)) { return false; } @@ -336,11 +448,31 @@ public override bool TrySubstitution( fontMetrics, table, feature, - this.LookupFlags, - this.MarkFilteringSet, + lookupMask, this.sequenceLookupRecords, - collection, - index, - count); + buffer, + matchPositions, + this.coverageTables.Length, + matchEnd); + } + + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + { + CoverageTable[] coverages = this.coverageTables; + if (coverages.Length != glyphs.Length) + { + return false; + } + + for (int i = 1; i < glyphs.Length; i++) + { + if (coverages[i].CoverageIndexOf(glyphs[i]) < 0) + { + return false; + } + } + + return true; } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType6SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType6SubTable.cs index 8b377b959..3d7aedf29 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType6SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType6SubTable.cs @@ -82,18 +82,22 @@ public static LookupType6Format1SubTable Load(BigEndianBinaryReader reader, long return new LookupType6Format1SubTable(coverageTable, seqRuleSets, lookupFlags, markFilteringSet); } - /// + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + + /// public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { // Implements Chained Contexts Substitution, Format 1: // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#61-chained-contexts-substitution-format-1-simple-glyph-contexts - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -112,13 +116,18 @@ public override bool TrySubstitution( } // Apply ruleset for the given glyph id. - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, this.LookupFlags, this.MarkFilteringSet); + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); ChainedSequenceRuleSetTable seqRuleSet = this.seqRuleSetTables[offset]; ChainedSequenceRuleTable[] rules = seqRuleSet.SequenceRuleTables; + + // Slot zero holds the coverage-matched glyph; the input match fills the + // rest, so nested lookups address the records the match consumed. + Span matchPositions = buffer.GetContextMatchPositions(); + matchPositions[0] = index; for (int i = 0; i < rules.Length; i++) { ChainedSequenceRuleTable ruleTable = rules[i]; - if (!AdvancedTypographicUtils.ApplyChainedSequenceRule(iterator, ruleTable)) + if (!AdvancedTypographicUtils.ApplyChainedSequenceRule(iterator, ruleTable, lookupMask, matchPositions[1..], out int matchEnd)) { continue; } @@ -127,12 +136,53 @@ public override bool TrySubstitution( fontMetrics, table, feature, - this.LookupFlags, - this.MarkFilteringSet, + lookupMask, ruleTable.SequenceLookupRecords, - collection, - index, - count); + buffer, + matchPositions, + ruleTable.InputSequence.Length + 1, + matchEnd); + } + + return false; + } + + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + { + int offset = this.coverageTable.CoverageIndexOf(glyphs[0]); + if (offset < 0 || offset >= this.seqRuleSetTables.Length) + { + return false; + } + + foreach (ChainedSequenceRuleTable rule in this.seqRuleSetTables[offset].SequenceRuleTables) + { + if (zeroContext && (rule.BacktrackSequence.Length != 0 || rule.LookaheadSequence.Length != 0)) + { + continue; + } + + ushort[] input = rule.InputSequence; + if (input.Length + 1 != glyphs.Length) + { + continue; + } + + bool matched = true; + for (int i = 0; i < input.Length; i++) + { + if (glyphs[i + 1] != input[i]) + { + matched = false; + break; + } + } + + if (matched) + { + return true; + } } return false; @@ -226,18 +276,22 @@ public static LookupType6Format2SubTable Load(BigEndianBinaryReader reader, long markFilteringSet); } - /// + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + + /// public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { // Implements Chained Contexts Substitution for Format 2: // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#62-chained-contexts-substitution-format-2-class-based-glyph-contexts - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; @@ -252,19 +306,28 @@ public override bool TrySubstitution( // Search in the class definition table to find the class value assigned to the currently glyph. int classId = this.inputClassDefinitionTable.ClassIndexOf(glyphId); - ChainedClassSequenceRuleTable[]? rules = classId >= 0 && classId < this.sequenceRuleSetTables.Length ? this.sequenceRuleSetTables[classId]?.SubRules : null; - if (rules is null) + ChainedClassSequenceRuleSetTable? ruleSet = classId >= 0 && classId < this.sequenceRuleSetTables.Length ? this.sequenceRuleSetTables[classId] : null; + if (ruleSet is null) { return false; } + ChainedClassSequenceRuleTable[] rules = ruleSet.SubRules; + // Apply ruleset for the given glyph class id. - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, this.LookupFlags, this.MarkFilteringSet); + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + + // Slot zero holds the coverage-matched glyph; the input match fills the + // rest, so nested lookups address the records the match consumed. Match + // storage is retained by the buffer because this method is entered from + // the per-glyph lookup loop. + Span matchPositions = buffer.GetContextMatchPositions(); + matchPositions[0] = index; for (int lookupIndex = 0; lookupIndex < rules.Length; lookupIndex++) { ChainedClassSequenceRuleTable ruleTable = rules[lookupIndex]; - if (!AdvancedTypographicUtils.ApplyChainedClassSequenceRule(iterator, ruleTable, this.inputClassDefinitionTable, this.backtrackClassDefinitionTable, this.lookaheadClassDefinitionTable)) + if (!AdvancedTypographicUtils.ApplyChainedClassSequenceRule(iterator, ruleTable, this.inputClassDefinitionTable, this.backtrackClassDefinitionTable, this.lookaheadClassDefinitionTable, lookupMask, matchPositions[1..], out int matchEnd)) { continue; } @@ -273,12 +336,57 @@ public override bool TrySubstitution( fontMetrics, table, feature, - this.LookupFlags, - this.MarkFilteringSet, + lookupMask, ruleTable.SequenceLookupRecords, - collection, - index, - count); + buffer, + matchPositions, + ruleTable.InputSequence.Length + 1, + matchEnd); + } + + return false; + } + + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + { + int classId = this.inputClassDefinitionTable.ClassIndexOf(glyphs[0]); + ChainedClassSequenceRuleTable[]? rules = classId >= 0 && classId < this.sequenceRuleSetTables.Length + ? this.sequenceRuleSetTables[classId]?.SubRules + : null; + + if (rules is null) + { + return false; + } + + foreach (ChainedClassSequenceRuleTable rule in rules) + { + if (zeroContext && (rule.BacktrackSequence.Length != 0 || rule.LookaheadSequence.Length != 0)) + { + continue; + } + + ushort[] input = rule.InputSequence; + if (input.Length + 1 != glyphs.Length) + { + continue; + } + + bool matched = true; + for (int i = 0; i < input.Length; i++) + { + if (this.inputClassDefinitionTable.ClassIndexOf(glyphs[i + 1]) != input[i]) + { + matched = false; + break; + } + } + + if (matched) + { + return true; + } } return false; @@ -362,31 +470,52 @@ public static LookupType6Format3SubTable Load(BigEndianBinaryReader reader, long markFilteringSet); } - /// + /// + public override void CollectDigest(ref GlyphSetDigest digest) + { + if (this.inputCoverageTables.Length == 0) + { + // Degenerate table data: without a first position coverage the gate + // cannot be known, so the lookup is always attempted. + digest.AddAll(); + return; + } + + this.inputCoverageTables[0].CollectDigest(ref digest); + } + + /// public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) { - ushort glyphId = collection[index].GlyphId; + ushort glyphId = buffer[index].GlyphId; if (glyphId == 0) { return false; } + // The input coverage array covers the whole input including its first + // glyph, so the match fills every position nested lookups address. + Span matchPositions = buffer.GetContextMatchPositions()[..AdvancedTypographicUtils.MaxContextLength]; if (!AdvancedTypographicUtils.CheckAllCoverages( fontMetrics, this.LookupFlags, this.MarkFilteringSet, - collection, + buffer, index, count, this.inputCoverageTables, this.backtrackCoverageTables, - this.lookaheadCoverageTables)) + this.lookaheadCoverageTables, + lookupMask, + matchPositions, + out int matchEnd)) { return false; } @@ -396,11 +525,36 @@ public override bool TrySubstitution( fontMetrics, table, feature, - this.LookupFlags, - this.MarkFilteringSet, + lookupMask, this.sequenceLookupRecords, - collection, - index, - count); + buffer, + matchPositions, + this.inputCoverageTables.Length, + matchEnd); + } + + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + { + if (zeroContext && (this.backtrackCoverageTables.Length != 0 || this.lookaheadCoverageTables.Length != 0)) + { + return false; + } + + CoverageTable[] coverages = this.inputCoverageTables; + if (coverages.Length != glyphs.Length) + { + return false; + } + + for (int i = 1; i < glyphs.Length; i++) + { + if (coverages[i].CoverageIndexOf(glyphs[i]) < 0) + { + return false; + } + } + + return true; } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType8SubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType8SubTable.cs index e2e42fe84..e4c6a1608 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType8SubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/LookupType8SubTable.cs @@ -83,6 +83,9 @@ private LookupType8Format1SubTable( this.lookaheadCoverageTables = lookaheadCoverageTables; } + /// + public override bool IsReverse => true; + /// /// Loads the reverse chaining contextual single substitution format 1 subtable from the given offset. /// @@ -148,54 +151,40 @@ public static LookupType8Format1SubTable Load(BigEndianBinaryReader reader, long markFilteringSet); } - /// - public override bool TrySubstitution( - FontMetrics fontMetrics, - GSubTable table, - GlyphSubstitutionCollection collection, - Tag feature, - int index, - int count) - { - // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#81-reverse-chaining-contextual-single-substitution-format-1-coverage-based-glyph-contexts - ushort glyphId = collection[index].GlyphId; - if (glyphId == 0) - { - return false; - } + /// + public override void CollectDigest(ref GlyphSetDigest digest) => this.coverageTable.CollectDigest(ref digest); + /// + public override bool TrySubstitution(FontMetrics fontMetrics, GSubTable table, ShapingBuffer buffer, Tag feature, uint lookupMask, int index, int count) + { + ushort glyphId = buffer[index].GlyphId; int offset = this.coverageTable.CoverageIndexOf(glyphId); - if (offset <= -1) + if ((uint)offset >= (uint)this.substituteGlyphIds.Length || buffer.IsNestedApplication) { return false; } - for (int i = 0; i < this.backtrackCoverageTables.Length; ++i) + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, this.LookupFlags, this.MarkFilteringSet); + SkippingGlyphIterator backtrack = iterator; + int backtrackStart = backtrack.StartBacktrack(); + if (!AdvancedTypographicUtils.MatchBacktrackCoverageSequence(backtrack, this.backtrackCoverageTables, backtrackStart, buffer.Count)) { - ushort id = collection[index - 1 - i].GlyphId; - if (id == 0 || this.backtrackCoverageTables[i].CoverageIndexOf(id) < 0) - { - return false; - } - } - - for (int i = 0; i < this.lookaheadCoverageTables.Length; ++i) - { - ushort id = collection[index + i].GlyphId; - if (id == 0 || this.lookaheadCoverageTables[i].CoverageIndexOf(id) < 0) - { - return false; - } + return false; } - // It's a match. Perform substitutions and return true if anything changed. - bool hasChanged = false; - for (int i = 0; i < this.substituteGlyphIds.Length; i++) + // Lookahead starts after the covered glyph. Lookup filtering and transparent + // formatting controls are handled identically on both sides of the context. + int end = index + count; + if (!AdvancedTypographicUtils.MatchCoverageSequence(iterator, this.lookaheadCoverageTables, index + 1, end, 0, true)) { - collection.Replace(index + i, this.substituteGlyphIds[i], feature); - hasChanged = true; + return false; } - return hasChanged; + buffer.ReplaceInPlace(index, this.substituteGlyphIds[offset], feature); + return true; } + + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + => glyphs.Length == 1 && this.coverageTable.CoverageIndexOf(glyphs[0]) > -1; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/NotImplementedSubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/NotImplementedSubTable.cs index 0265292fd..c0ae15fdb 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/NotImplementedSubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSub/NotImplementedSubTable.cs @@ -21,9 +21,14 @@ public NotImplementedSubTable() public override bool TrySubstitution( FontMetrics fontMetrics, GSubTable table, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, Tag feature, + uint lookupMask, int index, int count) => false; + + /// + public override bool WouldApply(ReadOnlySpan glyphs, bool zeroContext) + => false; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSubTable.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSubTable.cs index 2bc7d3aa3..dd2801203 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSubTable.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GSubTable.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using SixLabors.Fonts.Tables.AdvancedTypographic.GSub; using SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; @@ -15,10 +16,22 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic; /// internal class GSubTable : Table { + /// + /// Caches resolved feature lookups per stage feature, script, and language + /// candidates. See for the variable-font bypass + /// and the read-only contract on cached lists. + /// + private readonly ConcurrentDictionary> featureLookupsCache = new(); + /// /// The OpenType table tag for the GSUB table. /// - internal const string TableName = "GSUB"; + public const string TableName = "GSUB"; + + /// + /// The invalid but widely shipped language system record tag 'dflt'. + /// + private static readonly Tag DefaultLangSysTag = Tag.Parse("dflt"); /// /// Initializes a new instance of the class. @@ -78,7 +91,7 @@ public GSubTable(ScriptList? scriptList, FeatureListTable featureList, LookupLis /// /// The big endian binary reader. /// The . - internal static GSubTable Load(BigEndianBinaryReader reader) + public static GSubTable Load(BigEndianBinaryReader reader) { // GSUB Header, Version 1.0 // +----------+-------------------+-----------------------------------------------------------+ @@ -134,84 +147,65 @@ internal static GSubTable Load(BigEndianBinaryReader reader) } /// - /// Applies glyph substitution to the collection using GSUB lookup rules. + /// Applies glyph substitution to the buffer using GSUB lookup rules. /// /// The font metrics. - /// The glyph substitution collection. - public void ApplySubstitution(FontMetrics fontMetrics, GlyphSubstitutionCollection collection) + /// The glyph substitution buffer. + public void ApplySubstitution(FontMetrics fontMetrics, ShapingBuffer buffer) { // Set max constraints to prevent OutOfMemoryException or infinite loops from attacks. - int maxCount = AdvancedTypographicUtils.GetMaxAllowableShapingCollectionCount(collection.Count); - int maxOperationsCount = AdvancedTypographicUtils.GetMaxAllowableShapingOperationsCount(collection.Count); + int maxCount = AdvancedTypographicUtils.GetMaxAllowableShapingCollectionCount(buffer.Count); + int maxOperationsCount = AdvancedTypographicUtils.GetMaxAllowableShapingOperationsCount(buffer.Count); int currentOperations = 0; - for (int i = 0; i < collection.Count; i++) + for (int i = 0; i < buffer.Count; i++) { // Choose a shaper based on the script. // This determines which features to apply to which glyphs. - ScriptClass current = this.GetScriptClass(CodePoint.GetScriptClass(collection[i].CodePoint)); - int index = i; - int 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. - ScriptClass next = this.GetScriptClass(CodePoint.GetScriptClass(collection[i + 1].CodePoint)); - if (next != current && - current is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited && - next is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited) - { - break; - } - - if (current is ScriptClass.Common or ScriptClass.Unknown or ScriptClass.Inherited) - { - current = next; - } + ScriptItemizer.ShapingRun run = ScriptItemizer.ReadRun(buffer, ref i, maxCount, out int count); - i++; - count++; + Tag unicodeScriptTag = this.GetUnicodeScriptTag(run.Script); + ShapePlan shapePlan = buffer.GetOrCreatePlan(run.Script, unicodeScriptTag, fontMetrics, run.Culture, run.FeatureTags); - if (i >= maxCount) - { - break; - } - } - - Tag unicodeScriptTag = this.GetUnicodeScriptTag(current); - BaseShaper shaper = ShaperFactory.Create(current, unicodeScriptTag, fontMetrics, collection.TextOptions); + BaseShaper shaper = shapePlan.Shaper; // Plan substitution features for each glyph. // Shapers can adjust the count during initialization and feature processing so we must capture // the current count to allow resetting indexes and processing counts. - int collectionCount = collection.Count; - shaper.Plan(collection, index, count); - int delta = collection.Count - collectionCount; + int collectionCount = buffer.Count; + shaper.Plan(fontMetrics, buffer, index, count); + int delta = buffer.Count - collectionCount; i += delta; count += delta; - IEnumerable stages = shaper.GetShapingStages(); - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, default, 0); - foreach (ShapingStage stage in stages) + // Stages are applied in pause-delimited groups: a stage action is a + // synchronization point, and between two actions every registered + // feature's lookups apply together in lookup-list order, the order the + // specification defines for lookups within a single application pass. A + // lookup registered by several of the group's features applies once with + // their glyph masks combined. Group boundaries, merged lookup lists, and + // entry masks are all prebuilt on the plan. + List> groups = shapePlan.GetOrBuildGSubStageGroups(); + List stages = shapePlan.Stages; + SkippingGlyphIterator iterator = new(fontMetrics, buffer, index, default, 0); + for (int g = 0; g < groups.Count; g++) { - collectionCount = collection.Count; - stage.PreProcessFeature(collection, index, count); + ShapePlanStageGroup group = groups[g]; + + collectionCount = buffer.Count; + stages[group.Start].PreProcessFeature(shapePlan, buffer, index, count); - // Account for substitutions changing the length of the collection. - delta = collection.Count - collectionCount; + // Account for substitutions changing the length of the buffer. + delta = buffer.Count - collectionCount; count += delta; i += delta; - Tag featureTag = stage.FeatureTag; - - this.ApplyFeature( + this.ApplyMergedLookups( fontMetrics, - collection, + buffer, ref iterator, - in featureTag, - current, + group.Lookups, index, ref count, ref i, @@ -220,38 +214,45 @@ current is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClas maxOperationsCount, ref currentOperations); - collectionCount = collection.Count; - stage.PostProcessFeature(collection, index, count); + collectionCount = buffer.Count; + stages[group.End - 1].PostProcessFeature(shapePlan, buffer, index, count); - // Account for substitutions changing the length of the collection. - delta = collection.Count - collectionCount; + // Account for substitutions changing the length of the buffer. + delta = buffer.Count - collectionCount; count += delta; i += delta; } + + // Record the segment with its post-substitution range so the in-place + // positioning pass can reuse the plan; one plan then drives both tables. + // Mark after GSUB because substitutions can change the number and order + // of glyph records to which layout will later apply tracking. + ScriptItemizer.MarkCursiveTrackingRun(buffer, index, count, run.Script); + buffer.SegmentPlans.Add((index, count, run.Script, shapePlan)); } } /// - /// Applies a specific feature's lookups to the glyph substitution collection. + /// Applies a stage group's merged lookups to the glyph substitution buffer in + /// lookup-index order. Each entry's mask combines every group feature that + /// registered the lookup, so the per-glyph gate stays a single bitwise AND. /// /// The font metrics. - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The skipping glyph iterator. - /// The feature tag to apply. - /// The current script class. - /// The starting index in the collection. + /// The group's lookups, sorted by lookup index. + /// The starting index in the buffer. /// The number of glyphs to process (updated by substitutions). /// The outer loop index (updated by substitutions). - /// The tracked collection count (updated by substitutions). - /// The maximum allowable collection count. + /// The tracked buffer count (updated by substitutions). + /// The maximum allowable buffer count. /// The maximum allowable operations count. /// The current operations counter. - internal void ApplyFeature( + private void ApplyMergedLookups( FontMetrics fontMetrics, - GlyphSubstitutionCollection collection, + ShapingBuffer buffer, ref SkippingGlyphIterator iterator, - in Tag featureTag, - ScriptClass current, + List<(Tag Feature, ushort Index, LookupTable LookupTable, uint Mask, bool AutoZwnj, bool AutoZwj, bool Random, bool PerSyllable)> merged, int index, ref int count, ref int i, @@ -260,53 +261,158 @@ internal void ApplyFeature( int maxOperationsCount, ref int currentOperations) { - if (this.TryGetFeatureLookups(fontMetrics, in featureTag, current, out List<(Tag Feature, ushort Index, LookupTable LookupTable)>? lookups)) + for (int m = 0; m < merged.Count; m++) { - // Apply features in order. - foreach ((Tag Feature, ushort Index, LookupTable LookupTable) featureLookup in lookups) + (Tag feature, ushort _, LookupTable featureLookupTable, uint featureMask, bool autoZwnj, bool autoZwj, bool random, bool perSyllable) = merged[m]; + + // Skip the whole lookup when its coverage cannot intersect any glyph id + // the buffer has ever contained; most fonts carry many lookups for + // glyphs a given text never produces. + // A lookup whose mask no record carries cannot match anything, so + // the whole pass is skipped rather than walked. Features that are + // registered for every plan but enabled only by particular text, + // such as the fraction trio, cost nothing elsewhere. + if ((featureMask & buffer.EnabledFeatureMaskUnion) == 0 + || !featureLookupTable.Digest.MightIntersect(buffer.GlyphDigest)) { - Tag feature = featureLookup.Feature; - LookupTable featureLookupTable = featureLookup.LookupTable; - iterator.Reset(index, featureLookupTable.LookupFlags, featureLookupTable.MarkFilteringSet); + continue; + } - while (iterator.Index < index + count) + buffer.SetLookupMatchState(featureMask, autoZwnj, autoZwj, random, perSyllable); + iterator.Reset(index, featureLookupTable.LookupFlags, featureLookupTable.MarkFilteringSet); + + if (featureLookupTable.IsReverse) + { + // Each replacement may create the context needed by a glyph to its + // left, so reverse lookups walk the segment end-to-start in place. + int reverseSegmentEnd = index + count; + for (int position = reverseSegmentEnd - 1; position >= index; position--) { - if (collection.Count >= maxCount || currentOperations++ >= maxOperationsCount) + if (buffer.Count >= maxCount || currentOperations++ >= maxOperationsCount) { + collectionCount = buffer.Count; return; } - if (!collection[iterator.Index].EnabledFeatureTags.Contains(feature)) + ref GlyphShapingData glyphData = ref buffer[position]; + if ((glyphData.FeatureMask & featureMask) == 0 + || !featureLookupTable.Digest.MightContain(glyphData.GlyphId) + || iterator.IsIgnored(position)) { - iterator.Next(); continue; } - collectionCount = collection.Count; - featureLookup.LookupTable.TrySubstitution(fontMetrics, this, collection, featureLookup.Feature, iterator.Index, count - (iterator.Index - index)); - iterator.Next(); + featureLookupTable.TrySubstitution(fontMetrics, this, buffer, feature, featureMask, position, reverseSegmentEnd - position); + } + + collectionCount = buffer.Count; + continue; + } + + // One output pass per lookup: the cursor consumes the input side and + // every record streams to the output side exactly once, so a length + // change costs one streaming pass instead of one shift per mutation. + // The pass begins at the segment, adopting everything before it, and + // a pass that changes nothing closes without touching the tail. + // Input-side indices are stable for the whole pass, which the + // matchers rely on. + buffer.BeginOutputPass(index); + + // The segment's end is held as the number of records that follow it, + // which nothing the pass does can disturb: replacements, insertions, + // and the room a rewind opens all land before the tail. An absolute + // index would have to be corrected after each of them. + int totalBefore = buffer.Count; + int segmentEnd = index + count; + while (buffer.ReadIndex < segmentEnd && buffer.ReadIndex < buffer.Count) + { + // The digest cheaply rejects glyphs no subtable of this lookup can + // affect; a maybe falls through to the exact coverage test inside. + // Masked and ignored records still stream to the output unchanged, + // preserving every record. Consecutive rejections are adopted as + // one range so the output pass moves its cursors once. + int position = buffer.ReadIndex; + bool operationsLimitReached = false; + while (position < segmentEnd && position < buffer.Count) + { + if (buffer.Count >= maxCount || currentOperations++ >= maxOperationsCount) + { + operationsLimitReached = true; + break; + } + + ref GlyphShapingData candidate = ref buffer[position]; + if ((candidate.FeatureMask & featureMask) != 0 + && featureLookupTable.Digest.MightContain(candidate.GlyphId) + && !iterator.IsIgnored(position)) + { + break; + } + + position++; + } + + buffer.CopyGlyphs(position - buffer.ReadIndex); - // Account for substitutions changing the length of the collection. - int delta = collection.Count - collectionCount; - count += delta; - i += delta; + if (operationsLimitReached) + { + // The pass must always close: stream the remainder and + // reconcile the segment bookkeeping before bailing out. + buffer.EndOutputPass(); + count += buffer.Count - totalBefore; + i += buffer.Count - totalBefore; + collectionCount = buffer.Count; + return; + } + + if (buffer.ReadIndex >= segmentEnd || buffer.ReadIndex >= buffer.Count) + { + break; + } + + position = buffer.ReadIndex; + int lengthBefore = buffer.Count; + featureLookupTable.TrySubstitution(fontMetrics, this, buffer, feature, featureMask, position, segmentEnd - position); + + // Anything that lengthened the input side moved the records + // after the segment along with it: an in-place mutation, or the + // room a rewind opened. Both shift the segment's end by the + // same amount, so the bound stays a plain comparison. + segmentEnd += buffer.Count - lengthBefore; + + if (buffer.ReadIndex == position) + { + buffer.CopyGlyph(); } } + + buffer.EndOutputPass(); + + // The pass closed, so the buffer holds the records it produced: the + // whole segment's change surfaces once, here. + count += buffer.Count - totalBefore; + i += buffer.Count - totalBefore; + collectionCount = buffer.Count; } } /// - /// Tries to get the feature lookups for the given stage feature and script. + /// Tries to get the feature lookups for the given stage feature, script, and language. /// /// The font metrics. /// The feature tag for the current shaping stage. /// The script class. + /// + /// The candidate OpenType language system tags, most specific first. An empty array + /// selects the default language system. + /// /// When this method returns, contains the list of feature lookups if found. /// if lookups were found; otherwise, . - internal bool TryGetFeatureLookups( + public bool TryGetFeatureLookups( FontMetrics fontMetrics, in Tag stageFeature, ScriptClass script, + Tag[] languageTags, [NotNullWhen(true)] out List<(Tag Feature, ushort Index, LookupTable LookupTable)>? value) { if (this.ScriptList is null) @@ -315,11 +421,52 @@ internal bool TryGetFeatureLookups( return false; } + // Feature variations resolve against the font's live variation coordinates, so + // caching would mix results across differently configured variable fonts. + if (this.FeatureVariations is not null) + { + value = this.ResolveFeatureLookups(fontMetrics, stageFeature, script, languageTags); + return value.Count > 0; + } + + // Resolution depends only on this table's data for a given feature, script, + // and language candidates, so results, including empty ones, are cached for + // the table's lifetime. The cached list is shared: consumers must not mutate + // it. A concurrent first-resolution race only duplicates deterministic work. + FeatureLookupsKey key = new(stageFeature, script, languageTags); + if (!this.featureLookupsCache.TryGetValue(key, out value)) + { + value = this.ResolveFeatureLookups(fontMetrics, stageFeature, script, languageTags); + this.featureLookupsCache.TryAdd(key, value); + } + + return value.Count > 0; + } + + /// + /// Resolves the feature lookups for the given stage feature, script, and language + /// through the selection ladder documented inline. + /// + /// The font metrics. + /// The feature tag for the current shaping stage. + /// The script class. + /// The candidate OpenType language system tags, most specific first. + /// The resolved lookups; empty when the feature yields none. + private List<(Tag Feature, ushort Index, LookupTable LookupTable)> ResolveFeatureLookups( + FontMetrics fontMetrics, + Tag stageFeature, + ScriptClass script, + Tag[] languageTags) + { // Resolve feature substitutions from FeatureVariations (variable fonts). FeatureTableSubstitutionRecord[]? substitutions = this.FeatureVariations ?.FindMatchingSubstitutions(fontMetrics.GetNormalizedCoordinates()); - ScriptListTable scriptListTable = this.ScriptList.Default(); + // Step 1: script selection. Map the Unicode script class onto the font's + // script table, falling back to the font's first script when the font does not + // declare the script. + // The caching entry point rejects a null script list before dispatching here. + ScriptListTable scriptListTable = this.ScriptList!.Default(); Tag[] tags = UnicodeScriptTagMap.Instance[script]; for (int i = 0; i < tags.Length; i++) { @@ -330,15 +477,52 @@ internal bool TryGetFeatureLookups( } } + // Step 2: language selection. Walk the culture's candidate tags in priority + // order, most specific first, scanning the script's named language systems for + // a tag match; the first candidate the font declares wins, so a zh-HK run + // selects ZHH before ZHT. A match commits even when the selected language + // system lacks this stage feature: per the specification a LangSys table is the + // complete feature set for its language, so falling through to the default + // below would merge two languages' features, exactly the output language + // systems exist to prevent. An empty candidate array skips this step entirely. + LangSysTable[] langSysTables = scriptListTable.LangSysTables; + for (int i = 0; i < languageTags.Length; i++) + { + uint language = languageTags[i].Value; + for (int j = 0; j < langSysTables.Length; j++) + { + if (langSysTables[j].LangSysTag == language) + { + return this.GetFeatureLookups(stageFeature, substitutions, langSysTables[j]); + } + } + } + + // Step 3: no culture, or no candidate the font declares. A language system + // record explicitly tagged dflt is preferred over the true default: the tag is + // invalid per the specification, but fonts built from old documentation typos + // carry one, and the reference engines honor it. + LangSysTable[] langSysRecords = scriptListTable.LangSysTables; + for (int i = 0; i < langSysRecords.Length; i++) + { + if (langSysRecords[i].LangSysTag == DefaultLangSysTag.Value) + { + return this.GetFeatureLookups(stageFeature, substitutions, langSysRecords[i]); + } + } + LangSysTable? defaultLangSysTable = scriptListTable.DefaultLangSysTable; if (defaultLangSysTable != null) { - value = this.GetFeatureLookups(stageFeature, substitutions, defaultLangSysTable); - return value.Count > 0; + return this.GetFeatureLookups(stageFeature, substitutions, defaultLangSysTable); } - value = this.GetFeatureLookups(stageFeature, substitutions, scriptListTable.LangSysTables); - return value.Count > 0; + // Step 4: no default language system either. Nothing applies: the font scoped + // every feature to specific languages, and the reference engines agree that no + // language system means no lookups. Features such as SimSun's vertical + // alternates, which live only under its Chinese language systems, are reached by + // setting TextOptions.Culture to a Chinese culture. + return []; } /// @@ -431,36 +615,4 @@ private static FeatureTable ResolveFeatureTable( return featureList.FeatureTables[featureIndex]; } - - /// - /// Maps a script class to an effective script class, checking whether the font supports it. - /// Falls back to if the script is not present in the font. - /// - /// The script class to check. - /// The effective script class. - private ScriptClass GetScriptClass(ScriptClass current) - { - if (current is ScriptClass.Common or ScriptClass.Unknown or ScriptClass.Inherited) - { - return current; - } - - if (this.ScriptList is null) - { - return ScriptClass.Default; - } - - Tag[] tags = UnicodeScriptTagMap.Instance[current]; - - for (int i = 0; i < tags.Length; i++) - { - if (this.ScriptList.TryGetValue(tags[i].Value, out ScriptListTable? _)) - { - return current; - } - } - - // Script for `current` not present in the font: use default shaper. - return ScriptClass.Default; - } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/GlyphSetDigest.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GlyphSetDigest.cs new file mode 100644 index 000000000..8de30b1e5 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/GlyphSetDigest.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// An approximate glyph membership filter for lookup coverage. +/// +/// +/// +/// Conceptually a tiny Bloom filter tuned for glyph coverage queries: three 64 bit +/// words, each indexing the glyph id at a different shift so ids cluster into different +/// buckets per word. A glyph "may" be in the set only when all three words contain its +/// bucket bit. False positives fall through to the lookup's exact coverage test, so they +/// cost only what the pre-digest code always paid; false negatives cannot occur. +/// +/// +/// The filter is highly accurate when a lookup covers a local cluster of glyph ids, +/// which is the common case for real fonts, and degrades to always-maybe when coverage +/// is spread across the id space. The three shifts bucket runs of 16 ids, single ids, +/// and runs of 64 ids respectively. +/// +/// +internal struct GlyphSetDigest +{ + private const int BitsMinusOne = 63; + private ulong mask0; + private ulong mask1; + private ulong mask2; + + /// + /// Adds a single glyph id to the digest. + /// + /// The glyph id. + public void Add(ushort glyphId) + { + this.mask0 |= 1UL << ((glyphId >> 4) & BitsMinusOne); + this.mask1 |= 1UL << (glyphId & BitsMinusOne); + this.mask2 |= 1UL << ((glyphId >> 6) & BitsMinusOne); + } + + /// + /// Adds an inclusive range of glyph ids to the digest. + /// + /// The first glyph id in the range. + /// The last glyph id in the range. + public void AddRange(ushort start, ushort end) + { + this.mask0 = AddRange(this.mask0, start, end, 4); + this.mask1 = AddRange(this.mask1, start, end, 0); + this.mask2 = AddRange(this.mask2, start, end, 6); + } + + /// + /// Marks the digest as containing every glyph, used when a subtable's gating + /// coverage is unknown so the lookup is always attempted, matching the behavior + /// before digests existed. + /// + public void AddAll() + { + this.mask0 = ulong.MaxValue; + this.mask1 = ulong.MaxValue; + this.mask2 = ulong.MaxValue; + } + + /// + /// Gets a value indicating whether the glyph may be a member of the digested set. + /// A false result is definitive; a true result must be confirmed by the lookup's + /// exact coverage test. + /// + /// The glyph id. + /// when the glyph is definitely absent. + public readonly bool MightContain(ushort glyphId) + => (this.mask0 & (1UL << ((glyphId >> 4) & BitsMinusOne))) != 0 + && (this.mask1 & (1UL << (glyphId & BitsMinusOne))) != 0 + && (this.mask2 & (1UL << ((glyphId >> 6) & BitsMinusOne))) != 0; + + /// + /// Gets a value indicating whether this digest and may + /// share a member: every word pair must share at least one bucket bit. A false + /// result proves the underlying sets are disjoint; a true result is approximate. + /// + /// The other digest. + /// when the sets are definitely disjoint. + public readonly bool MightIntersect(in GlyphSetDigest other) + => (this.mask0 & other.mask0) != 0 + && (this.mask1 & other.mask1) != 0 + && (this.mask2 & other.mask2) != 0; + + /// + /// Sets every bucket bit from through + /// at the given shift, saturating the word when the range spans all buckets. + /// + /// The current word. + /// The first glyph id in the range. + /// The last glyph id in the range. + /// The bucket shift for this word. + /// The updated word. + private static ulong AddRange(ulong mask, ushort start, ushort end, int shift) + { + if ((end >> shift) - (start >> shift) >= BitsMinusOne) + { + return ulong.MaxValue; + } + + // Sets the contiguous bucket bits from start through end inclusive, wrapping + // within the word: with mb >= ma the expression is (mb << 1) - ma, the bits + // ma..mb; with mb < ma the unsigned wrap of (mb - ma) plus the borrowed 1 + // produces the two runs ma..63 and 0..mb. + ulong ma = 1UL << ((start >> shift) & BitsMinusOne); + ulong mb = 1UL << ((end >> shift) & BitsMinusOne); + return mask | unchecked(mb + (mb - ma) - (mb < ma ? 1UL : 0UL)); + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/OpenTypeLanguageTagMap.Generated.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/OpenTypeLanguageTagMap.Generated.cs index 94205a53a..a99b0fbb5 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/OpenTypeLanguageTagMap.Generated.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/OpenTypeLanguageTagMap.Generated.cs @@ -27,7 +27,7 @@ private static Dictionary CreateIsoLanguageMap() { "abq", new[] { Tag.Parse("ABA ") } }, // Abaza { "abs", new[] { Tag.Parse("CPP ") } }, // Creoles { "abv", new[] { Tag.Parse("ARA ") } }, // Arabic - { "acf", new[] { Tag.Parse("CPP "), Tag.Parse("FAN ") } }, // Creoles; French Antillean + { "acf", new[] { Tag.Parse("FAN "), Tag.Parse("CPP ") } }, // French Antillean; Creoles { "ach", new[] { Tag.Parse("ACH ") } }, // Acholi { "acm", new[] { Tag.Parse("ARA ") } }, // Arabic { "acq", new[] { Tag.Parse("ARA ") } }, // Arabic @@ -181,11 +181,11 @@ private static Dictionary CreateIsoLanguageMap() { "btd", new[] { Tag.Parse("BTD "), Tag.Parse("BTK ") } }, // Batak Dairi (Pakpak); Batak languages { "btj", new[] { Tag.Parse("MLY ") } }, // Malay { "btk", new[] { Tag.Parse("BTK ") } }, // Batak languages - { "btm", new[] { Tag.Parse("BTK "), Tag.Parse("BTM ") } }, // Batak languages; Batak Mandailing + { "btm", new[] { Tag.Parse("BTM "), Tag.Parse("BTK ") } }, // Batak Mandailing; Batak languages { "bto", new[] { Tag.Parse("BIK ") } }, // Bikol - { "bts", new[] { Tag.Parse("BTK "), Tag.Parse("BTS ") } }, // Batak languages; Batak Simalungun - { "btx", new[] { Tag.Parse("BTK "), Tag.Parse("BTX ") } }, // Batak languages; Batak Karo - { "btz", new[] { Tag.Parse("BTK "), Tag.Parse("BTZ ") } }, // Batak languages; Batak Alas-Kluet + { "bts", new[] { Tag.Parse("BTS "), Tag.Parse("BTK ") } }, // Batak Simalungun; Batak languages + { "btx", new[] { Tag.Parse("BTX "), Tag.Parse("BTK ") } }, // Batak Karo; Batak languages + { "btz", new[] { Tag.Parse("BTZ "), Tag.Parse("BTK ") } }, // Batak Alas-Kluet; Batak languages { "bug", new[] { Tag.Parse("BUG ") } }, // Bugis { "bul", new[] { Tag.Parse("BGR ") } }, // Bulgarian { "bum", new[] { Tag.Parse("BTI ") } }, // Beti @@ -197,13 +197,13 @@ private static Dictionary CreateIsoLanguageMap() { "bxp", new[] { Tag.Parse("BTI ") } }, // Beti { "bxr", new[] { Tag.Parse("RBU ") } }, // Russian Buriat { "byn", new[] { Tag.Parse("BIL ") } }, // Bilen - { "byv", new[] { Tag.Parse("BML "), Tag.Parse("BYV ") } }, // Bamileke; Medumba + { "byv", new[] { Tag.Parse("BYV "), Tag.Parse("BML ") } }, // Medumba; Bamileke { "bzc", new[] { Tag.Parse("MLG ") } }, // Malagasy { "bzj", new[] { Tag.Parse("CPP ") } }, // Creoles { "bzk", new[] { Tag.Parse("CPP ") } }, // Creoles { "caa", new[] { Tag.Parse("MYN ") } }, // Mayan { "cac", new[] { Tag.Parse("MYN ") } }, // Mayan - { "caf", new[] { Tag.Parse("ATH "), Tag.Parse("CRR ") } }, // Athapaskan languages; Carrier + { "caf", new[] { Tag.Parse("CRR "), Tag.Parse("ATH ") } }, // Carrier; Athapaskan languages { "cak", new[] { Tag.Parse("CAK "), Tag.Parse("MYN ") } }, // Kaqchikel; Mayan { "cat", new[] { Tag.Parse("CAT ") } }, // Catalan { "cay", new[] { Tag.Parse("CAY ") } }, // Cayuga @@ -214,6 +214,7 @@ private static Dictionary CreateIsoLanguageMap() { "ccm", new[] { Tag.Parse("CPP ") } }, // Creoles { "cco", new[] { Tag.Parse("CCHN") } }, // Chinantec { "ccq", new[] { Tag.Parse("ARK ") } }, // Rakhine + { "cdo", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "ceb", new[] { Tag.Parse("CEB ") } }, // Cebuano { "cek", new[] { Tag.Parse("QIN ") } }, // Chin { "ces", new[] { Tag.Parse("CSY ") } }, // Czech @@ -227,7 +228,7 @@ private static Dictionary CreateIsoLanguageMap() { "chk", new[] { Tag.Parse("CHK0") } }, // Chuukese { "chn", new[] { Tag.Parse("CPP ") } }, // Creoles { "cho", new[] { Tag.Parse("CHO ") } }, // Choctaw - { "chp", new[] { Tag.Parse("ATH "), Tag.Parse("CHP "), Tag.Parse("SAY ") } }, // Athapaskan languages; Chipewyan; Sayisi + { "chp", new[] { Tag.Parse("CHP "), Tag.Parse("SAY "), Tag.Parse("ATH ") } }, // Chipewyan; Sayisi; Athapaskan languages { "chq", new[] { Tag.Parse("CCHN") } }, // Chinantec { "chr", new[] { Tag.Parse("CHR ") } }, // Cherokee { "chu", new[] { Tag.Parse("CSL ") } }, // Church Slavonic @@ -237,6 +238,7 @@ private static Dictionary CreateIsoLanguageMap() { "ciw", new[] { Tag.Parse("OJB ") } }, // Ojibway { "cja", new[] { Tag.Parse("CJA ") } }, // Western Cham { "cjm", new[] { Tag.Parse("CJM ") } }, // Eastern Cham + { "cjy", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "cka", new[] { Tag.Parse("QIN ") } }, // Chin { "ckb", new[] { Tag.Parse("KUR ") } }, // Kurdish { "ckn", new[] { Tag.Parse("QIN ") } }, // Chin @@ -255,6 +257,7 @@ private static Dictionary CreateIsoLanguageMap() { "cnh", new[] { Tag.Parse("QIN ") } }, // Chin { "cnk", new[] { Tag.Parse("QIN ") } }, // Chin { "cnl", new[] { Tag.Parse("CCHN") } }, // Chinantec + { "cnp", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "cnr", new[] { Tag.Parse("SRB ") } }, // Serbian { "cnt", new[] { Tag.Parse("CCHN") } }, // Chinantec { "cnu", new[] { Tag.Parse("BBR ") } }, // Berber @@ -271,6 +274,7 @@ private static Dictionary CreateIsoLanguageMap() { "cpf", new[] { Tag.Parse("CPP ") } }, // Creoles { "cpi", new[] { Tag.Parse("CPP ") } }, // Creoles { "cpp", new[] { Tag.Parse("CPP ") } }, // Creoles + { "cpx", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "cqd", new[] { Tag.Parse("HMN ") } }, // Hmong { "cqu", new[] { Tag.Parse("QUH ") } }, // Quechua (Bolivia) { "cre", new[] { Tag.Parse("CRE ") } }, // Cree @@ -282,12 +286,13 @@ private static Dictionary CreateIsoLanguageMap() { "crm", new[] { Tag.Parse("LCR "), Tag.Parse("MCR ") } }, // L-Cree; Moose Cree { "crp", new[] { Tag.Parse("CPP ") } }, // Creoles { "crs", new[] { Tag.Parse("CPP ") } }, // Creoles - { "crx", new[] { Tag.Parse("ATH "), Tag.Parse("CRR ") } }, // Athapaskan languages; Carrier + { "crx", new[] { Tag.Parse("CRR "), Tag.Parse("ATH ") } }, // Carrier; Athapaskan languages { "csa", new[] { Tag.Parse("CCHN") } }, // Chinantec { "csb", new[] { Tag.Parse("CSB ") } }, // Kashubian { "csh", new[] { Tag.Parse("QIN ") } }, // Chin { "csj", new[] { Tag.Parse("QIN ") } }, // Chin { "cso", new[] { Tag.Parse("CCHN") } }, // Chinantec + { "csp", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "csv", new[] { Tag.Parse("QIN ") } }, // Chin { "csw", new[] { Tag.Parse("NCR "), Tag.Parse("NHC ") } }, // N-Cree; Norway House Cree { "csy", new[] { Tag.Parse("QIN ") } }, // Chin @@ -306,6 +311,8 @@ private static Dictionary CreateIsoLanguageMap() { "cvn", new[] { Tag.Parse("CCHN") } }, // Chinantec { "cwd", new[] { Tag.Parse("DCR "), Tag.Parse("TCR ") } }, // Woods Cree; TH-Cree { "cym", new[] { Tag.Parse("WEL ") } }, // Welsh + { "czh", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified + { "czo", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "czt", new[] { Tag.Parse("QIN ") } }, // Chin { "dag", new[] { Tag.Parse("DAG ") } }, // Dagbani { "dan", new[] { Tag.Parse("DAN ") } }, // Danish @@ -314,7 +321,7 @@ private static Dictionary CreateIsoLanguageMap() { "dar", new[] { Tag.Parse("DAR ") } }, // Dargwa { "dax", new[] { Tag.Parse("DAX ") } }, // Dayi { "dcr", new[] { Tag.Parse("CPP ") } }, // Creoles - { "den", new[] { Tag.Parse("ATH "), Tag.Parse("SLA ") } }, // Athapaskan languages; Slavey + { "den", new[] { Tag.Parse("SLA "), Tag.Parse("ATH ") } }, // Slavey; Athapaskan languages { "dep", new[] { Tag.Parse("CPP ") } }, // Creoles { "deu", new[] { Tag.Parse("DEU ") } }, // German { "dgo", new[] { Tag.Parse("DGO ") } }, // Dogri (individual language) @@ -335,11 +342,13 @@ private static Dictionary CreateIsoLanguageMap() { "dng", new[] { Tag.Parse("DUN ") } }, // Dungan { "dnj", new[] { Tag.Parse("DNJ ") } }, // Dan { "doi", new[] { Tag.Parse("DGR ") } }, // Dogri (macrolanguage) + { "drh", new[] { Tag.Parse("MNG ") } }, // Mongolian { "drw", new[] { Tag.Parse("DRI ") } }, // Dari { "dsb", new[] { Tag.Parse("LSB ") } }, // Lower Sorbian { "dty", new[] { Tag.Parse("NEP ") } }, // Nepali { "duj", new[] { Tag.Parse("DUJ ") } }, // Dhuwal { "dup", new[] { Tag.Parse("MLY ") } }, // Malay + { "dwk", new[] { Tag.Parse("KUI ") } }, // Kui { "dwu", new[] { Tag.Parse("DUJ ") } }, // Dhuwal { "dwy", new[] { Tag.Parse("DUJ ") } }, // Dhuwal { "dyu", new[] { Tag.Parse("JUL ") } }, // Jula @@ -372,13 +381,13 @@ private static Dictionary CreateIsoLanguageMap() { "fan", new[] { Tag.Parse("FAN0") } }, // Fang { "fao", new[] { Tag.Parse("FOS ") } }, // Faroese { "fas", new[] { Tag.Parse("FAR ") } }, // Persian - { "fat", new[] { Tag.Parse("AKA "), Tag.Parse("FAT ") } }, // Akan; Fanti + { "fat", new[] { Tag.Parse("FAT "), Tag.Parse("AKA ") } }, // Fanti; Akan { "fbl", new[] { Tag.Parse("BIK ") } }, // Bikol { "ffm", new[] { Tag.Parse("FUL ") } }, // Fulah { "fij", new[] { Tag.Parse("FJI ") } }, // Fijian { "fil", new[] { Tag.Parse("PIL ") } }, // Filipino { "fin", new[] { Tag.Parse("FIN ") } }, // Finnish - { "fmp", new[] { Tag.Parse("BML "), Tag.Parse("FMP ") } }, // Bamileke; Fe’fe’ + { "fmp", new[] { Tag.Parse("FMP "), Tag.Parse("BML ") } }, // Fe’fe’; Bamileke { "fng", new[] { Tag.Parse("CPP ") } }, // Creoles { "fon", new[] { Tag.Parse("FON ") } }, // Fon { "fpe", new[] { Tag.Parse("CPP ") } }, // Creoles @@ -399,6 +408,7 @@ private static Dictionary CreateIsoLanguageMap() { "gaa", new[] { Tag.Parse("GAD ") } }, // Ga { "gac", new[] { Tag.Parse("CPP ") } }, // Creoles { "gag", new[] { Tag.Parse("GAG ") } }, // Gagauz + { "gan", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "gax", new[] { Tag.Parse("ORO ") } }, // Oromo { "gaz", new[] { Tag.Parse("ORO ") } }, // Oromo { "gbm", new[] { Tag.Parse("GAW ") } }, // Garhwali @@ -408,6 +418,7 @@ private static Dictionary CreateIsoLanguageMap() { "gcr", new[] { Tag.Parse("CPP ") } }, // Creoles { "gda", new[] { Tag.Parse("RAJ ") } }, // Rajasthani { "gez", new[] { Tag.Parse("GEZ ") } }, // Geez + { "ggo", new[] { Tag.Parse("GON ") } }, // Gondi { "gha", new[] { Tag.Parse("BBR ") } }, // Berber { "ghc", new[] { Tag.Parse("IRT ") } }, // Irish Traditional { "ghk", new[] { Tag.Parse("KRN ") } }, // Karen @@ -452,8 +463,9 @@ private static Dictionary CreateIsoLanguageMap() { "haa", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages { "hae", new[] { Tag.Parse("ORO ") } }, // Oromo { "hai", new[] { Tag.Parse("HAI0") } }, // Haida + { "hak", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "har", new[] { Tag.Parse("HRI ") } }, // Harari - { "hat", new[] { Tag.Parse("CPP "), Tag.Parse("HAI ") } }, // Creoles; Haitian (Haitian Creole) + { "hat", new[] { Tag.Parse("HAI "), Tag.Parse("CPP ") } }, // Haitian (Haitian Creole); Creoles { "hau", new[] { Tag.Parse("HAU ") } }, // Hausa { "haw", new[] { Tag.Parse("HAW ") } }, // Hawaiian { "hax", new[] { Tag.Parse("HAI0") } }, // Haida @@ -480,7 +492,7 @@ private static Dictionary CreateIsoLanguageMap() { "hml", new[] { Tag.Parse("HMN ") } }, // Hmong { "hmm", new[] { Tag.Parse("HMN ") } }, // Hmong { "hmn", new[] { Tag.Parse("HMN ") } }, // Hmong - { "hmo", new[] { Tag.Parse("CPP "), Tag.Parse("HMO ") } }, // Creoles; Hiri Motu + { "hmo", new[] { Tag.Parse("HMO "), Tag.Parse("CPP ") } }, // Hiri Motu; Creoles { "hmp", new[] { Tag.Parse("HMN ") } }, // Hmong { "hmq", new[] { Tag.Parse("HMN ") } }, // Hmong { "hmr", new[] { Tag.Parse("QIN ") } }, // Chin @@ -491,6 +503,7 @@ private static Dictionary CreateIsoLanguageMap() { "hnd", new[] { Tag.Parse("HND ") } }, // Hindko { "hne", new[] { Tag.Parse("CHH ") } }, // Chattisgarhi { "hnj", new[] { Tag.Parse("HMN ") } }, // Hmong + { "hnm", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "hno", new[] { Tag.Parse("HND ") } }, // Hindko { "hoc", new[] { Tag.Parse("HO ") } }, // Ho { "hoi", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages @@ -500,13 +513,14 @@ private static Dictionary CreateIsoLanguageMap() { "hrm", new[] { Tag.Parse("HMN ") } }, // Hmong { "hrv", new[] { Tag.Parse("HRV ") } }, // Croatian { "hsb", new[] { Tag.Parse("USB ") } }, // Upper Sorbian + { "hsn", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "huj", new[] { Tag.Parse("HMN ") } }, // Hmong { "hun", new[] { Tag.Parse("HUN ") } }, // Hungarian { "hup", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages { "hur", new[] { Tag.Parse("HUR ") } }, // Halkomelem { "hus", new[] { Tag.Parse("MYN ") } }, // Mayan { "hwc", new[] { Tag.Parse("CPP ") } }, // Creoles - { "hye", new[] { Tag.Parse("HYE "), Tag.Parse("HYE0") } }, // Armenian; Armenian East + { "hye", new[] { Tag.Parse("HYE0"), Tag.Parse("HYE ") } }, // Armenian East; Armenian { "hyw", new[] { Tag.Parse("HYE ") } }, // Armenian { "iba", new[] { Tag.Parse("IBA ") } }, // Iban { "ibb", new[] { Tag.Parse("IBB ") } }, // Ibibio @@ -544,7 +558,7 @@ private static Dictionary CreateIsoLanguageMap() { "ixl", new[] { Tag.Parse("MYN ") } }, // Mayan { "jac", new[] { Tag.Parse("MYN ") } }, // Mayan { "jak", new[] { Tag.Parse("MLY ") } }, // Malay - { "jam", new[] { Tag.Parse("CPP "), Tag.Parse("JAM ") } }, // Creoles; Jamaican Creole + { "jam", new[] { Tag.Parse("JAM "), Tag.Parse("CPP ") } }, // Jamaican Creole; Creoles { "jav", new[] { Tag.Parse("JAV ") } }, // Javanese { "jax", new[] { Tag.Parse("MLY ") } }, // Malay { "jbe", new[] { Tag.Parse("BBR ") } }, // Berber @@ -560,7 +574,7 @@ private static Dictionary CreateIsoLanguageMap() { "jvd", new[] { Tag.Parse("CPP ") } }, // Creoles { "jw", new[] { Tag.Parse("JAV ") } }, // Javanese { "kaa", new[] { Tag.Parse("KRK ") } }, // Karakalpak - { "kab", new[] { Tag.Parse("BBR "), Tag.Parse("KAB0") } }, // Berber; Kabyle + { "kab", new[] { Tag.Parse("KAB0"), Tag.Parse("BBR ") } }, // Kabyle; Berber { "kal", new[] { Tag.Parse("GRN ") } }, // Greenlandic { "kam", new[] { Tag.Parse("KMB ") } }, // Kamba { "kan", new[] { Tag.Parse("KAN ") } }, // Kannada @@ -578,7 +592,7 @@ private static Dictionary CreateIsoLanguageMap() { "kde", new[] { Tag.Parse("KDE ") } }, // Makonde { "kdr", new[] { Tag.Parse("KRM ") } }, // Karaim { "kdt", new[] { Tag.Parse("KUY ") } }, // Kuy - { "kea", new[] { Tag.Parse("CPP "), Tag.Parse("KEA ") } }, // Creoles; Kabuverdianu (Crioulo) + { "kea", new[] { Tag.Parse("KEA "), Tag.Parse("CPP ") } }, // Kabuverdianu (Crioulo); Creoles { "kek", new[] { Tag.Parse("KEK "), Tag.Parse("MYN ") } }, // Kekchi; Mayan { "kex", new[] { Tag.Parse("KKN ") } }, // Kokni { "kfa", new[] { Tag.Parse("KOD ") } }, // Kodagu @@ -619,23 +633,24 @@ private static Dictionary CreateIsoLanguageMap() { "kok", new[] { Tag.Parse("KOK ") } }, // Konkani { "kom", new[] { Tag.Parse("KOM ") } }, // Komi { "kon", new[] { Tag.Parse("KON0") } }, // Kongo - { "kor", new[] { Tag.Parse("KOH "), Tag.Parse("KOR ") } }, // Korean Old Hangul; Korean + { "kor", new[] { Tag.Parse("KOR "), Tag.Parse("KOH ") } }, // Korean; Korean Old Hangul { "kos", new[] { Tag.Parse("KOS ") } }, // Kosraean { "koy", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages { "kpe", new[] { Tag.Parse("KPL ") } }, // Kpelle + { "kpp", new[] { Tag.Parse("KRN ") } }, // Karen { "kpv", new[] { Tag.Parse("KOZ ") } }, // Komi-Zyrian { "kpy", new[] { Tag.Parse("KYK ") } }, // Koryak { "kqs", new[] { Tag.Parse("KIS ") } }, // Kisii { "kqy", new[] { Tag.Parse("KRT ") } }, // Koorete { "krc", new[] { Tag.Parse("BAL "), Tag.Parse("KAR ") } }, // Balkar; Karachay - { "kri", new[] { Tag.Parse("CPP "), Tag.Parse("KRI ") } }, // Creoles; Krio + { "kri", new[] { Tag.Parse("KRI "), Tag.Parse("CPP ") } }, // Krio; Creoles { "krl", new[] { Tag.Parse("KRL ") } }, // Karelian { "krt", new[] { Tag.Parse("KNR ") } }, // Kanuri { "kru", new[] { Tag.Parse("KUU ") } }, // Kurukh { "ksh", new[] { Tag.Parse("KSH0") } }, // Ripuarian { "kss", new[] { Tag.Parse("KIS ") } }, // Kisii { "ksu", new[] { Tag.Parse("KSU ") } }, // Khamyang - { "ksw", new[] { Tag.Parse("KRN "), Tag.Parse("KSW ") } }, // Karen; S’gaw Karen + { "ksw", new[] { Tag.Parse("KSW "), Tag.Parse("KRN ") } }, // S’gaw Karen; Karen { "ktb", new[] { Tag.Parse("KEB ") } }, // Kebena { "ktu", new[] { Tag.Parse("KON ") } }, // Kikongo { "ktw", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages @@ -646,7 +661,7 @@ private static Dictionary CreateIsoLanguageMap() { "kuw", new[] { Tag.Parse("BAD0") } }, // Banda { "kvb", new[] { Tag.Parse("MLY ") } }, // Malay { "kvl", new[] { Tag.Parse("KRN ") } }, // Karen - { "kvq", new[] { Tag.Parse("KRN "), Tag.Parse("KVQ ") } }, // Karen; Geba Karen + { "kvq", new[] { Tag.Parse("KVQ "), Tag.Parse("KRN ") } }, // Geba Karen; Karen { "kvr", new[] { Tag.Parse("MLY ") } }, // Malay { "kvt", new[] { Tag.Parse("KRN ") } }, // Karen { "kvu", new[] { Tag.Parse("KRN ") } }, // Karen @@ -660,7 +675,7 @@ private static Dictionary CreateIsoLanguageMap() { "kxk", new[] { Tag.Parse("KRN ") } }, // Karen { "kxl", new[] { Tag.Parse("KUU ") } }, // Kurukh { "kxu", new[] { Tag.Parse("KUI ") } }, // Kui - { "kyu", new[] { Tag.Parse("KRN "), Tag.Parse("KYU ") } }, // Karen; Western Kayah + { "kyu", new[] { Tag.Parse("KYU "), Tag.Parse("KRN ") } }, // Western Kayah; Karen { "lac", new[] { Tag.Parse("MYN ") } }, // Mayan { "lad", new[] { Tag.Parse("JUD ") } }, // Ladino { "lao", new[] { Tag.Parse("LAO ") } }, // Lao @@ -709,6 +724,7 @@ private static Dictionary CreateIsoLanguageMap() { "lua", new[] { Tag.Parse("LUA ") } }, // Luba-Lulua { "lub", new[] { Tag.Parse("LUB ") } }, // Luba-Katanga { "lug", new[] { Tag.Parse("LUG ") } }, // Ganda + { "luh", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "luo", new[] { Tag.Parse("LUO ") } }, // Luo { "lus", new[] { Tag.Parse("MIZ "), Tag.Parse("QIN ") } }, // Mizo; Chin { "lut", new[] { Tag.Parse("LUT ") } }, // Lushootseed @@ -716,6 +732,7 @@ private static Dictionary CreateIsoLanguageMap() { "luz", new[] { Tag.Parse("LRC ") } }, // Luri { "lvs", new[] { Tag.Parse("LVI ") } }, // Latvian { "lwg", new[] { Tag.Parse("LUH ") } }, // Luyia + { "lzh", new[] { Tag.Parse("ZHT ") } }, // Chinese, Traditional { "lzz", new[] { Tag.Parse("LAZ ") } }, // Laz { "mad", new[] { Tag.Parse("MAD ") } }, // Madura { "mag", new[] { Tag.Parse("MAG ") } }, // Magahi @@ -740,7 +757,7 @@ private static Dictionary CreateIsoLanguageMap() { "mev", new[] { Tag.Parse("MEV ") } }, // Mano { "mfa", new[] { Tag.Parse("MFA ") } }, // Pattani Malay { "mfb", new[] { Tag.Parse("MLY ") } }, // Malay - { "mfe", new[] { Tag.Parse("CPP "), Tag.Parse("MFE ") } }, // Creoles; Morisyen + { "mfe", new[] { Tag.Parse("MFE "), Tag.Parse("CPP ") } }, // Morisyen; Creoles { "mfp", new[] { Tag.Parse("CPP ") } }, // Creoles { "mga", new[] { Tag.Parse("SGA ") } }, // Old Irish { "mhc", new[] { Tag.Parse("MYN ") } }, // Mayan @@ -759,6 +776,7 @@ private static Dictionary CreateIsoLanguageMap() { "mnh", new[] { Tag.Parse("BAD0") } }, // Banda { "mni", new[] { Tag.Parse("MNI ") } }, // Manipuri { "mnk", new[] { Tag.Parse("MND "), Tag.Parse("MNK ") } }, // Mandinka; Maninka + { "mnp", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "mns", new[] { Tag.Parse("MAN ") } }, // Mansi { "mnw", new[] { Tag.Parse("MON "), Tag.Parse("MONT") } }, // Mon; Thailand Mon { "mo", new[] { Tag.Parse("ROM ") } }, // Romanian @@ -799,11 +817,12 @@ private static Dictionary CreateIsoLanguageMap() { "mzb", new[] { Tag.Parse("BBR ") } }, // Berber { "mzn", new[] { Tag.Parse("MZN ") } }, // Mazanderani { "mzs", new[] { Tag.Parse("CPP ") } }, // Creoles - { "nag", new[] { Tag.Parse("CPP "), Tag.Parse("NAG ") } }, // Creoles; Naga-Assamese + { "nag", new[] { Tag.Parse("NAG "), Tag.Parse("CPP ") } }, // Naga-Assamese; Creoles { "nah", new[] { Tag.Parse("NAH ") } }, // Nahuatl + { "nan", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "nap", new[] { Tag.Parse("NAP ") } }, // Neapolitan { "nau", new[] { Tag.Parse("NAU ") } }, // Nauruan - { "nav", new[] { Tag.Parse("ATH "), Tag.Parse("NAV ") } }, // Athapaskan languages; Navajo + { "nav", new[] { Tag.Parse("NAV "), Tag.Parse("ATH ") } }, // Navajo; Athapaskan languages { "naz", new[] { Tag.Parse("NAH ") } }, // Nahuatl { "nbl", new[] { Tag.Parse("NDB ") } }, // Ndebele { "nch", new[] { Tag.Parse("NAH ") } }, // Nahuatl @@ -848,6 +867,7 @@ private static Dictionary CreateIsoLanguageMap() { "nla", new[] { Tag.Parse("BML ") } }, // Bamileke { "nld", new[] { Tag.Parse("NLD ") } }, // Dutch { "nle", new[] { Tag.Parse("LUH ") } }, // Luyia + { "nln", new[] { Tag.Parse("NAH ") } }, // Nahuatl { "nlv", new[] { Tag.Parse("NAH ") } }, // Nahuatl { "nnh", new[] { Tag.Parse("BML ") } }, // Bamileke { "nno", new[] { Tag.Parse("NYN ") } }, // Norwegian Nynorsk (Nynorsk, Norwegian) @@ -902,7 +922,7 @@ private static Dictionary CreateIsoLanguageMap() { "pag", new[] { Tag.Parse("PAG ") } }, // Pangasinan { "pam", new[] { Tag.Parse("PAM ") } }, // Pampangan { "pan", new[] { Tag.Parse("PAN ") } }, // Punjabi - { "pap", new[] { Tag.Parse("CPP "), Tag.Parse("PAP0") } }, // Creoles; Papiamentu + { "pap", new[] { Tag.Parse("PAP0"), Tag.Parse("CPP ") } }, // Papiamentu; Creoles { "pau", new[] { Tag.Parse("PAU ") } }, // Palauan { "pbt", new[] { Tag.Parse("PAS ") } }, // Pashto { "pbu", new[] { Tag.Parse("PAS ") } }, // Pashto @@ -919,7 +939,7 @@ private static Dictionary CreateIsoLanguageMap() { "pey", new[] { Tag.Parse("CPP ") } }, // Creoles { "pga", new[] { Tag.Parse("CPP ") } }, // Creoles { "phk", new[] { Tag.Parse("PHK ") } }, // Phake - { "pih", new[] { Tag.Parse("CPP "), Tag.Parse("PIH ") } }, // Creoles; Norfolk + { "pih", new[] { Tag.Parse("PIH "), Tag.Parse("CPP ") } }, // Norfolk; Creoles { "pis", new[] { Tag.Parse("CPP ") } }, // Creoles { "pkh", new[] { Tag.Parse("QIN ") } }, // Chin { "pko", new[] { Tag.Parse("KAL ") } }, // Kalenjin @@ -934,7 +954,7 @@ private static Dictionary CreateIsoLanguageMap() { "pmy", new[] { Tag.Parse("CPP ") } }, // Creoles { "pnb", new[] { Tag.Parse("PNB ") } }, // Western Panjabi { "poc", new[] { Tag.Parse("MYN ") } }, // Mayan - { "poh", new[] { Tag.Parse("MYN "), Tag.Parse("POH ") } }, // Mayan; Pocomchi + { "poh", new[] { Tag.Parse("POH "), Tag.Parse("MYN ") } }, // Pocomchi; Mayan { "pol", new[] { Tag.Parse("PLK ") } }, // Polish { "pon", new[] { Tag.Parse("PON ") } }, // Pohnpeian { "por", new[] { Tag.Parse("PTG ") } }, // Portuguese @@ -949,16 +969,26 @@ private static Dictionary CreateIsoLanguageMap() { "pub", new[] { Tag.Parse("QIN ") } }, // Chin { "pus", new[] { Tag.Parse("PAS ") } }, // Pashto { "puz", new[] { Tag.Parse("QIN ") } }, // Chin - { "pwo", new[] { Tag.Parse("KRN "), Tag.Parse("PWO ") } }, // Karen; Western Pwo Karen + { "pwo", new[] { Tag.Parse("PWO "), Tag.Parse("KRN ") } }, // Western Pwo Karen; Karen { "pww", new[] { Tag.Parse("KRN ") } }, // Karen - { "quc", new[] { Tag.Parse("MYN "), Tag.Parse("QUC ") } }, // Mayan; K’iche’ + { "quc", new[] { Tag.Parse("QUC "), Tag.Parse("MYN ") } }, // K’iche’; Mayan + { "que", new[] { Tag.Parse("QUZ ") } }, // Quechua + { "quf", new[] { Tag.Parse("QUZ ") } }, // Quechua { "quh", new[] { Tag.Parse("QUH ") } }, // Quechua (Bolivia) + { "quk", new[] { Tag.Parse("QUZ ") } }, // Quechua { "qum", new[] { Tag.Parse("MYN ") } }, // Mayan { "quv", new[] { Tag.Parse("MYN ") } }, // Mayan + { "quy", new[] { Tag.Parse("QUZ ") } }, // Quechua { "quz", new[] { Tag.Parse("QUZ ") } }, // Quechua + { "qvc", new[] { Tag.Parse("QUZ ") } }, // Quechua + { "qve", new[] { Tag.Parse("QUZ ") } }, // Quechua { "qvi", new[] { Tag.Parse("QVI ") } }, // Quechua (Ecuador) + { "qvs", new[] { Tag.Parse("QUZ ") } }, // Quechua + { "qwc", new[] { Tag.Parse("QUZ ") } }, // Quechua { "qwh", new[] { Tag.Parse("QWH ") } }, // Quechua (Peru) { "qwt", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages + { "qxp", new[] { Tag.Parse("QUZ ") } }, // Quechua + { "qxu", new[] { Tag.Parse("QUZ ") } }, // Quechua { "rag", new[] { Tag.Parse("LUH ") } }, // Luyia { "raj", new[] { Tag.Parse("RAJ ") } }, // Rajasthani { "ral", new[] { Tag.Parse("QIN ") } }, // Chin @@ -969,7 +999,7 @@ private static Dictionary CreateIsoLanguageMap() { "rej", new[] { Tag.Parse("REJ ") } }, // Rejang { "rhg", new[] { Tag.Parse("RHG ") } }, // Rohingya { "ria", new[] { Tag.Parse("RIA ") } }, // Riang - { "rif", new[] { Tag.Parse("BBR "), Tag.Parse("RIF ") } }, // Berber; Tarifit + { "rif", new[] { Tag.Parse("RIF "), Tag.Parse("BBR ") } }, // Tarifit; Berber { "rit", new[] { Tag.Parse("RIT ") } }, // Ritarungo { "rki", new[] { Tag.Parse("ARK ") } }, // Rakhine { "rkw", new[] { Tag.Parse("RKW ") } }, // Arakwal @@ -1004,7 +1034,7 @@ private static Dictionary CreateIsoLanguageMap() { "sck", new[] { Tag.Parse("SAD ") } }, // Sadri { "scn", new[] { Tag.Parse("SCN ") } }, // Sicilian { "sco", new[] { Tag.Parse("SCO ") } }, // Scots - { "scs", new[] { Tag.Parse("ATH "), Tag.Parse("SCS "), Tag.Parse("SLA ") } }, // Athapaskan languages; North Slavey; Slavey + { "scs", new[] { Tag.Parse("SCS "), Tag.Parse("SLA "), Tag.Parse("ATH ") } }, // North Slavey; Slavey; Athapaskan languages { "sdc", new[] { Tag.Parse("SRD ") } }, // Sardinian { "sdh", new[] { Tag.Parse("KUR ") } }, // Kurdish { "sdn", new[] { Tag.Parse("SRD ") } }, // Sardinian @@ -1019,7 +1049,7 @@ private static Dictionary CreateIsoLanguageMap() { "sgc", new[] { Tag.Parse("KAL ") } }, // Kalenjin { "sgs", new[] { Tag.Parse("SGS ") } }, // Samogitian { "sgw", new[] { Tag.Parse("CHG ") } }, // Chaha Gurage - { "shi", new[] { Tag.Parse("BBR "), Tag.Parse("SHI ") } }, // Berber; Tachelhit + { "shi", new[] { Tag.Parse("SHI "), Tag.Parse("BBR ") } }, // Tachelhit; Berber { "shl", new[] { Tag.Parse("QIN ") } }, // Chin { "shn", new[] { Tag.Parse("SHN ") } }, // Shan { "shu", new[] { Tag.Parse("ARA ") } }, // Arabic @@ -1028,6 +1058,7 @@ private static Dictionary CreateIsoLanguageMap() { "sin", new[] { Tag.Parse("SNH ") } }, // Sinhala (Sinhalese) { "siz", new[] { Tag.Parse("BBR ") } }, // Berber { "sja", new[] { Tag.Parse("SJA ") } }, // Epena + { "sjc", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "sjd", new[] { Tag.Parse("KSM ") } }, // Kildin Sami { "sje", new[] { Tag.Parse("SJE ") } }, // Pite Sami { "sjo", new[] { Tag.Parse("SIB ") } }, // Sibe @@ -1085,15 +1116,15 @@ private static Dictionary CreateIsoLanguageMap() { "swn", new[] { Tag.Parse("BBR ") } }, // Berber { "swv", new[] { Tag.Parse("MAW ") } }, // Marwari { "sxu", new[] { Tag.Parse("SXU ") } }, // Upper Saxon - { "syc", new[] { Tag.Parse("SYR "), Tag.Parse("SYRE"), Tag.Parse("SYRJ"), Tag.Parse("SYRN") } }, // Syriac; Syriac, Estrangela script-variant (equivalent to ISO 15924 'Syre'); Syriac, Western script-variant (equivalent to ISO 15924 'Syrj'); Syriac, Eastern script-variant (equivalent to ISO 15924 'Syrn') + { "syc", new[] { Tag.Parse("SYR ") } }, // Syriac { "syl", new[] { Tag.Parse("SYL ") } }, // Sylheti - { "syr", new[] { Tag.Parse("SYR "), Tag.Parse("SYRE"), Tag.Parse("SYRJ"), Tag.Parse("SYRN") } }, // Syriac; Syriac, Estrangela script-variant (equivalent to ISO 15924 'Syre'); Syriac, Western script-variant (equivalent to ISO 15924 'Syrj'); Syriac, Eastern script-variant (equivalent to ISO 15924 'Syrn') + { "syr", new[] { Tag.Parse("SYR ") } }, // Syriac { "szl", new[] { Tag.Parse("SZL ") } }, // Silesian { "taa", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages { "tab", new[] { Tag.Parse("TAB ") } }, // Tabasaran { "tah", new[] { Tag.Parse("THT ") } }, // Tahitian { "tam", new[] { Tag.Parse("TAM ") } }, // Tamil - { "taq", new[] { Tag.Parse("BBR "), Tag.Parse("TAQ "), Tag.Parse("TMH ") } }, // Berber; Tamasheq; Tamashek + { "taq", new[] { Tag.Parse("TAQ "), Tag.Parse("TMH "), Tag.Parse("BBR ") } }, // Tamasheq; Tamashek; Berber { "tas", new[] { Tag.Parse("CPP ") } }, // Creoles { "tat", new[] { Tag.Parse("TAT ") } }, // Tatar { "tau", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages @@ -1122,8 +1153,8 @@ private static Dictionary CreateIsoLanguageMap() { "tha", new[] { Tag.Parse("THA ") } }, // Thai { "thp", new[] { Tag.Parse("THP ") } }, // Thompson { "tht", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages - { "thv", new[] { Tag.Parse("BBR "), Tag.Parse("THV "), Tag.Parse("TMH ") } }, // Berber; Tahaggart Tamahaq; Tamashek - { "thz", new[] { Tag.Parse("BBR "), Tag.Parse("THZ "), Tag.Parse("TMH ") } }, // Berber; Tayart Tamajeq; Tamashek + { "thv", new[] { Tag.Parse("THV "), Tag.Parse("TMH "), Tag.Parse("BBR ") } }, // Tahaggart Tamahaq; Tamashek; Berber + { "thz", new[] { Tag.Parse("THZ "), Tag.Parse("TMH "), Tag.Parse("BBR ") } }, // Tayart Tamajeq; Tamashek; Berber { "tia", new[] { Tag.Parse("BBR ") } }, // Berber { "tig", new[] { Tag.Parse("TGR ") } }, // Tigre { "tir", new[] { Tag.Parse("TGY ") } }, // Tigrinya @@ -1134,7 +1165,7 @@ private static Dictionary CreateIsoLanguageMap() { "tli", new[] { Tag.Parse("TLI ") } }, // Tlingit { "tly", new[] { Tag.Parse("TLY ") } }, // Talysh { "tmg", new[] { Tag.Parse("CPP ") } }, // Creoles - { "tmh", new[] { Tag.Parse("BBR "), Tag.Parse("TMH ") } }, // Berber; Tamashek + { "tmh", new[] { Tag.Parse("TMH "), Tag.Parse("BBR ") } }, // Tamashek; Berber { "tmw", new[] { Tag.Parse("MLY ") } }, // Malay { "tnf", new[] { Tag.Parse("DRI ") } }, // Dari { "tod", new[] { Tag.Parse("TOD0") } }, // Toma @@ -1143,15 +1174,15 @@ private static Dictionary CreateIsoLanguageMap() { "tol", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages { "ton", new[] { Tag.Parse("TGN ") } }, // Tongan { "tor", new[] { Tag.Parse("BAD0") } }, // Banda - { "tpi", new[] { Tag.Parse("CPP "), Tag.Parse("TPI ") } }, // Creoles; Tok Pisin + { "tpi", new[] { Tag.Parse("TPI "), Tag.Parse("CPP ") } }, // Tok Pisin; Creoles { "trf", new[] { Tag.Parse("CPP ") } }, // Creoles - { "tru", new[] { Tag.Parse("SYR "), Tag.Parse("TUA ") } }, // Syriac; Turoyo Aramaic + { "tru", new[] { Tag.Parse("TUA "), Tag.Parse("SYR ") } }, // Turoyo Aramaic; Syriac { "tsj", new[] { Tag.Parse("TSJ ") } }, // Tshangla { "tsn", new[] { Tag.Parse("TNA ") } }, // Tswana { "tso", new[] { Tag.Parse("TSG ") } }, // Tsonga { "ttc", new[] { Tag.Parse("MYN ") } }, // Mayan { "ttm", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages - { "ttq", new[] { Tag.Parse("BBR "), Tag.Parse("TMH "), Tag.Parse("TTQ ") } }, // Berber; Tamashek; Tawallammat Tamajaq + { "ttq", new[] { Tag.Parse("TTQ "), Tag.Parse("TMH "), Tag.Parse("BBR ") } }, // Tawallammat Tamajaq; Tamashek; Berber { "tuk", new[] { Tag.Parse("TKM ") } }, // Turkmen { "tum", new[] { Tag.Parse("TUM ") } }, // Tumbuka { "tur", new[] { Tag.Parse("TRK ") } }, // Turkish @@ -1161,19 +1192,20 @@ private static Dictionary CreateIsoLanguageMap() { "tvl", new[] { Tag.Parse("TVL ") } }, // Tuvalu { "tvy", new[] { Tag.Parse("CPP ") } }, // Creoles { "tw", new[] { Tag.Parse("AKA ") } }, // Akan - { "twi", new[] { Tag.Parse("AKA "), Tag.Parse("TWI ") } }, // Akan; Twi + { "twi", new[] { Tag.Parse("TWI "), Tag.Parse("AKA ") } }, // Twi; Akan { "txc", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages { "txy", new[] { Tag.Parse("MLG ") } }, // Malagasy { "tyv", new[] { Tag.Parse("TUV ") } }, // Tuvin { "tyz", new[] { Tag.Parse("TYZ ") } }, // Tày { "tzh", new[] { Tag.Parse("MYN ") } }, // Mayan { "tzj", new[] { Tag.Parse("MYN ") } }, // Mayan - { "tzm", new[] { Tag.Parse("BBR "), Tag.Parse("TZM ") } }, // Berber; Tamazight - { "tzo", new[] { Tag.Parse("MYN "), Tag.Parse("TZO ") } }, // Mayan; Tzotzil + { "tzm", new[] { Tag.Parse("TZM "), Tag.Parse("BBR ") } }, // Tamazight; Berber + { "tzo", new[] { Tag.Parse("TZO "), Tag.Parse("MYN ") } }, // Tzotzil; Mayan { "ubl", new[] { Tag.Parse("BIK ") } }, // Bikol { "udi", new[] { Tag.Parse("UDI ") } }, // Udi { "udm", new[] { Tag.Parse("UDM ") } }, // Udmurt { "uig", new[] { Tag.Parse("UYG ") } }, // Uyghur + { "uki", new[] { Tag.Parse("KUI ") } }, // Kui { "ukr", new[] { Tag.Parse("UKR ") } }, // Ukrainian { "uln", new[] { Tag.Parse("CPP ") } }, // Creoles { "umb", new[] { Tag.Parse("UMB ") } }, // Umbundu @@ -1215,6 +1247,7 @@ private static Dictionary CreateIsoLanguageMap() { "wry", new[] { Tag.Parse("MAW ") } }, // Marwari { "wsg", new[] { Tag.Parse("GON ") } }, // Gondi { "wtm", new[] { Tag.Parse("WTM ") } }, // Mewati + { "wuu", new[] { Tag.Parse("ZHS ") } }, // Chinese, Simplified { "wyn", new[] { Tag.Parse("WYN ") } }, // Wyandot { "xal", new[] { Tag.Parse("KLM "), Tag.Parse("TOD ") } }, // Kalmyk; Todo { "xan", new[] { Tag.Parse("SEK ") } }, // Sekota @@ -1230,11 +1263,12 @@ private static Dictionary CreateIsoLanguageMap() { "xnr", new[] { Tag.Parse("DGR ") } }, // Dogri (macrolanguage) { "xog", new[] { Tag.Parse("XOG ") } }, // Soga { "xpe", new[] { Tag.Parse("XPE ") } }, // Kpelle (Liberia) - { "xsl", new[] { Tag.Parse("ATH "), Tag.Parse("SLA "), Tag.Parse("SSL ") } }, // Athapaskan languages; Slavey; South Slavey + { "xsl", new[] { Tag.Parse("SSL "), Tag.Parse("SLA "), Tag.Parse("ATH ") } }, // South Slavey; Slavey; Athapaskan languages { "xst", new[] { Tag.Parse("SIG ") } }, // Silte Gurage { "xub", new[] { Tag.Parse("XUB ") } }, // Bette Kuruma { "xuj", new[] { Tag.Parse("XUJ ") } }, // Jennu Kuruma { "xup", new[] { Tag.Parse("ATH ") } }, // Athapaskan languages + { "xwo", new[] { Tag.Parse("TOD ") } }, // Todo { "yaj", new[] { Tag.Parse("BAD0") } }, // Banda { "yao", new[] { Tag.Parse("YAO ") } }, // Yao { "yap", new[] { Tag.Parse("YAP ") } }, // Yapese @@ -1257,7 +1291,7 @@ private static Dictionary CreateIsoLanguageMap() { "zeh", new[] { Tag.Parse("ZHA ") } }, // Zhuang { "zen", new[] { Tag.Parse("BBR ") } }, // Berber { "zgb", new[] { Tag.Parse("ZHA ") } }, // Zhuang - { "zgh", new[] { Tag.Parse("BBR "), Tag.Parse("ZGH ") } }, // Berber; Standard Moroccan Tamazight + { "zgh", new[] { Tag.Parse("ZGH "), Tag.Parse("BBR ") } }, // Standard Moroccan Tamazight; Berber { "zgm", new[] { Tag.Parse("ZHA ") } }, // Zhuang { "zgn", new[] { Tag.Parse("ZHA ") } }, // Zhuang { "zha", new[] { Tag.Parse("ZHA ") } }, // Zhuang diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/OpenTypeLanguageTagMap.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/OpenTypeLanguageTagMap.cs index a9e86d5e5..c07de4710 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/OpenTypeLanguageTagMap.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/OpenTypeLanguageTagMap.cs @@ -12,9 +12,10 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic; /// /// /// Candidates are ordered most specific first; callers select the first tag the font's -/// script table declares and fall back to the default language system otherwise. -/// The registry data half of this class is generated; see the UnicodeTrieGenerator -/// project. +/// script table declares and fall back to the default language system otherwise. The +/// resolution rules mirror HarfBuzz's hb_ot_tags_from_language and are pinned against +/// its test corpus. The registry data half of this class is generated; see the +/// UnicodeTrieGenerator project. /// internal sealed partial class OpenTypeLanguageTagMap { @@ -24,10 +25,10 @@ internal sealed partial class OpenTypeLanguageTagMap private static readonly Lazy> LazyMap = new(CreateIsoLanguageMap, isThreadSafe: true); /// - /// Maps BCP 47 variant subtags to the language system tags the registry defines by - /// cross reference rather than by ISO 639 code. + /// Maps BCP 47 variant and script subtags to the language system tags the registry + /// defines by cross reference rather than by ISO 639 code. /// - private static readonly Dictionary VariantTagMap = new(StringComparer.Ordinal) + private static readonly Dictionary SubtagTagMap = new(StringComparer.Ordinal) { { "fonipa", Tag.Parse("IPPH") }, // Phonetic transcription, IPA conventions { "fonnapa", Tag.Parse("APPH") }, // Phonetic transcription, Americanist conventions @@ -37,6 +38,7 @@ internal sealed partial class OpenTypeLanguageTagMap { "syre", Tag.Parse("SYRE") }, // Syriac, Estrangela script variant { "syrj", Tag.Parse("SYRJ") }, // Syriac, Western script variant { "syrn", Tag.Parse("SYRN") }, // Syriac, Eastern script variant + { "latg", Tag.Parse("IRT ") }, // Irish Traditional, Latin Gaelic script { "arevmda", Tag.Parse("HYE ") }, // Western Armenian }; @@ -46,6 +48,10 @@ internal sealed partial class OpenTypeLanguageTagMap private static readonly Tag ZhhTag = Tag.Parse("ZHH "); + private static readonly Tag ZhtmTag = Tag.Parse("ZHTM"); + + private static readonly Tag MolTag = Tag.Parse("MOL "); + /// /// Prevents a default instance of the class /// from being created. @@ -76,61 +82,159 @@ public static bool TryGetTags(CultureInfo? culture, out Tag[] tags) List candidates = []; string[] subtags = culture.Name.ToLowerInvariant().Split('-'); + string threeLetter = culture.ThreeLetterISOLanguageName.ToLowerInvariant(); - // BCP 47 variant subtags override the language mapping entirely: the registry - // defines these tags by variant, not by ISO code. + // BCP 47 variant and script subtags override the language mapping entirely: the + // registry defines these tags by subtag, not by ISO code. for (int i = 1; i < subtags.Length; i++) { - if (VariantTagMap.TryGetValue(subtags[i], out Tag variantTag)) + if (SubtagTagMap.TryGetValue(subtags[i], out Tag subtagTag)) { - AddDistinct(candidates, variantTag); + AddDistinct(candidates, subtagTag); } } - string threeLetter = culture.ThreeLetterISOLanguageName.ToLowerInvariant(); - - if (subtags[0] == "zh" || threeLetter is "zho" or "cmn") + if (subtags[0] is "zh" or "yue" or "cmn" || threeLetter is "zho" or "cmn" or "yue") { - // Chinese language system tags encode script and region rather than - // language: Hong Kong and Macao conventions first where they apply, then - // traditional for Hant or Taiwan, then simplified, with the remaining - // registry tags as fallbacks below. - bool traditional = false; - bool hongKong = false; - for (int i = 1; i < subtags.Length; i++) - { - traditional |= subtags[i] is "hant" or "tw"; - hongKong |= subtags[i] is "hk" or "mo"; - } + AddChineseCandidates(candidates, subtags); + } + else if ((subtags[0] == "ro" || threeLetter == "ron") && HasSubtag(subtags, "md")) + { + // Moldavian keeps its own registered tag for Romanian in Moldova; the + // Romanian tag follows from the general lookup below. + AddDistinct(candidates, MolTag); + } - if (hongKong) + // The registry lists ISO 639-3 codes; older rows also carry two letter forms. + // The runtime reports an empty or echoed code for languages it has no data for, + // so the two letter form doubles as the raw subtag for unknown languages. + string twoLetter = culture.TwoLetterISOLanguageName.ToLowerInvariant(); + Dictionary map = LazyMap.Value; + if (map.TryGetValue(threeLetter, out Tag[]? mapped) || map.TryGetValue(twoLetter, out mapped)) + { + foreach (Tag tag in mapped) { - AddDistinct(candidates, ZhhTag); - AddDistinct(candidates, ZhtTag); + AddDistinct(candidates, tag); } - else if (traditional) + } + else if (TryGetSynthesisSource(threeLetter, twoLetter, out string source)) + { + // The specification directs fonts supporting languages without registered + // tags to use the uppercase ISO 639-3 code as the language system tag, so an + // unmapped code synthesizes that candidate, matching HarfBuzz. + Span synthesized = ['\0', '\0', '\0', ' ']; + for (int i = 0; i < 3; i++) { - AddDistinct(candidates, ZhtTag); + synthesized[i] = char.ToUpperInvariant(source[i]); } - else + + AddDistinct(candidates, Tag.Parse(new string(synthesized))); + } + + tags = [.. candidates]; + return tags.Length > 0; + } + + /// + /// Adds the Chinese language system candidates. The Chinese tags encode script and + /// region rather than language, so resolution mirrors HarfBuzz: an explicit + /// simplified script wins over any region, an explicit traditional script defers to + /// the Macao and Hong Kong regional conventions, then regions decide, and bare + /// Cantonese defaults to the Hong Kong conventions while any other bare Chinese + /// defaults to simplified. Traditional regional conventions fall back to the general + /// traditional tag. + /// + /// The candidate list to add to. + /// The lowercase culture name subtags. + private static void AddChineseCandidates(List candidates, string[] subtags) + { + bool simplified = HasSubtag(subtags, "hans"); + bool traditional = HasSubtag(subtags, "hant"); + bool taiwan = HasSubtag(subtags, "tw"); + bool hongKong = HasSubtag(subtags, "hk"); + bool macao = HasSubtag(subtags, "mo"); + + if (simplified) + { + AddDistinct(candidates, ZhsTag); + } + else if (macao) + { + // Macao's dedicated tag is rare in fonts; the Hong Kong conventions are the + // regional fallback before the general traditional tag, matching HarfBuzz's + // language-tags shaping expectations. + AddDistinct(candidates, ZhtmTag); + AddDistinct(candidates, ZhhTag); + AddDistinct(candidates, ZhtTag); + } + else if (hongKong || subtags[0] == "yue") + { + // Cantonese without an explicit simplified script or region uses the Hong + // Kong conventions, including with an explicit traditional script. + AddDistinct(candidates, ZhhTag); + AddDistinct(candidates, ZhtTag); + } + else if (traditional || taiwan) + { + AddDistinct(candidates, ZhtTag); + } + else + { + AddDistinct(candidates, ZhsTag); + } + } + + /// + /// Selects the ISO 639-3 code an unmapped language synthesizes its tag from, + /// preferring the resolved three letter code and falling back to the raw subtag the + /// runtime echoes for languages it has no data for. + /// + /// The lowercase three letter ISO language name. + /// The lowercase two letter ISO language name. + /// The selected code. + /// if a three letter code is available; otherwise . + private static bool TryGetSynthesisSource(string threeLetter, string twoLetter, out string source) + { + if (threeLetter.Length == 3 && threeLetter != "und" && IsAsciiLetters(threeLetter)) + { + source = threeLetter; + return true; + } + + if (twoLetter.Length == 3 && twoLetter != "und" && IsAsciiLetters(twoLetter)) + { + source = twoLetter; + return true; + } + + source = string.Empty; + return false; + } + + private static bool HasSubtag(string[] subtags, string value) + { + for (int i = 1; i < subtags.Length; i++) + { + if (subtags[i] == value) { - AddDistinct(candidates, ZhsTag); + return true; } } - // The registry lists ISO 639-3 codes; older rows also carry two letter forms. - Dictionary map = LazyMap.Value; - if (map.TryGetValue(threeLetter, out Tag[]? mapped) - || map.TryGetValue(culture.TwoLetterISOLanguageName.ToLowerInvariant(), out mapped)) + return false; + } + + private static bool IsAsciiLetters(string value) + { + foreach (char c in value) { - foreach (Tag tag in mapped) + if (c is < 'a' or > 'z') { - AddDistinct(candidates, tag); + return false; } } - tags = [.. candidates]; - return tags.Length > 0; + return true; } private static void AddDistinct(List candidates, Tag tag) diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/ScriptItemizer.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ScriptItemizer.cs new file mode 100644 index 000000000..19d780743 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ScriptItemizer.cs @@ -0,0 +1,330 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; +using SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; +using SixLabors.Fonts.Unicode; +using SixLabors.Fonts.Unicode.Resources; + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// Splits a shaping buffer into runs of a single script and language and plans a +/// shaper for each run. +/// +/// Planning is what prepares a run's text and registers its features, so it +/// belongs to every font rather than to the fonts that carry a substitution +/// table. A font without one still needs the characters a script cannot be read +/// without, and positioning reuses the runs planning records. +/// +/// +internal static class ScriptItemizer +{ + /// + /// Reads the run of one script that begins at , + /// leaving on the run's last record. + /// + /// Feature lookups are assigned to runs rather than to the text as a whole so + /// that the shapers of two languages cannot interfere with one another. + /// Characters belonging to no script of their own join the run around them. + /// + /// + /// The glyph shaping buffer. + /// The zero-based index of the run's first record, left on its last. + /// The largest record count a run may reach. + /// When this method returns, contains the number of records in the run. + /// The script and culture shared by the run. + public static ShapingRun ReadRun(ShapingBuffer buffer, ref int index, int maxCount, out int count) + { + ShapingRun run = new(buffer, index); + count = 1; + + while (index < buffer.Count - 1) + { + if (!run.TryInclude(buffer, index + 1)) + { + break; + } + + index++; + count++; + + if (index >= maxCount) + { + break; + } + } + + return run; + } + + /// + /// Resolves the script that applies to one shaping record. + /// + /// The glyph shaping buffer. + /// The zero-based record index. + /// The explicit run script, the whole-text script, or the script inferred from the character, in that order. + public static ScriptClass ResolveScript(ShapingBuffer buffer, int index) + { + ref GlyphShapingData data = ref buffer[index]; + ScriptClass? script = buffer.TextRuns[data.TextRunIndex].Script ?? buffer.TextOptions.Script; + + // Explicit metadata belongs to the whole declared run, including its + // punctuation and any characters whose Unicode Script value differs. + return script ?? CodePoint.GetScriptClass(data.CodePoint); + } + + /// + /// Resolves the culture that applies to one shaping record. + /// + /// The glyph shaping buffer. + /// The zero-based record index. + /// The explicit run culture, the whole-text culture, or the current culture, in that order. + public static CultureInfo ResolveCulture(ShapingBuffer buffer, int index) + { + ref GlyphShapingData data = ref buffer[index]; + return buffer.TextRuns[data.TextRunIndex].Culture ?? buffer.TextOptions.Culture ?? CultureInfo.CurrentCulture; + } + + /// + /// Resolves the additional feature tags that apply to one shaping record. + /// + /// The glyph shaping buffer. + /// The zero-based record index. + /// The run-specific tags, or the whole-text tags when the run inherits them. + public static IReadOnlyList ResolveFeatureTags(ShapingBuffer buffer, int index) + { + ref GlyphShapingData data = ref buffer[index]; + + // Null means inheritance; an explicitly empty run list must remain empty so + // it can disable whole-text features over only that run. + return buffer.TextRuns[data.TextRunIndex].FeatureTags ?? buffer.TextOptions.FeatureTags; + } + + /// + /// Plans a shaper over every run of the buffer and records the runs for the + /// positioning pass. Used for a font that carries no substitution table, + /// where there are no lookups to apply but the text still has to be prepared. + /// + /// The font metrics. + /// The glyph shaping buffer. + public static void PlanRuns(FontMetrics fontMetrics, ShapingBuffer buffer) + { + // Set max constraints to prevent OutOfMemoryException or infinite loops from attacks. + int maxCount = AdvancedTypographicUtils.GetMaxAllowableShapingCollectionCount(buffer.Count); + + for (int i = 0; i < buffer.Count; i++) + { + int index = i; + ShapingRun run = ReadRun(buffer, ref i, maxCount, out int count); + + // With no substitution table the font offers no script of its own, so + // the run is planned against the default design. + ShapePlan shapePlan = buffer.GetOrCreatePlan(run.Script, default, fontMetrics, run.Culture, run.FeatureTags); + + // Preparing the text can insert records, so the run grows with it. + int collectionCount = buffer.Count; + + shapePlan.Shaper.Plan(fontMetrics, buffer, index, count); + + int delta = buffer.Count - collectionCount; + i += delta; + count += delta; + + // A substitution table with no lookups still walks every stage and + // invokes its actions. Preserve that path for fonts with no table, + // accounting for actions that insert or remove records. + List> groups = shapePlan.GetOrBuildGSubStageGroups(); + List stages = shapePlan.Stages; + for (int g = 0; g < groups.Count; g++) + { + ShapePlanStageGroup group = groups[g]; + + collectionCount = buffer.Count; + stages[group.Start].PreProcessFeature(shapePlan, buffer, index, count); + + delta = buffer.Count - collectionCount; + i += delta; + count += delta; + + collectionCount = buffer.Count; + stages[group.End - 1].PostProcessFeature(shapePlan, buffer, index, count); + + delta = buffer.Count - collectionCount; + i += delta; + count += delta; + } + + MarkCursiveTrackingRun(buffer, index, count, run.Script); + buffer.SegmentPlans.Add((index, count, run.Script, shapePlan)); + } + } + + /// + /// Marks a resolved cursive-script run so layout can preserve its joins when + /// applying tracking. The pass is skipped entirely when tracking is disabled. + /// + /// The shaped glyph buffer. + /// The zero-based index of the first glyph in the run. + /// The number of glyphs in the run. + /// The resolved script shared by the run. + public static void MarkCursiveTrackingRun(ShapingBuffer buffer, int index, int count, ScriptClass script) + { + // Layout only needs this metadata when it will add tracking. Avoid touching + // every output record in the common zero-tracking case. + if (buffer.TextOptions.Tracking == 0 + || script is not (ScriptClass.Arabic + or ScriptClass.HanifiRohingya + or ScriptClass.Mandaic + or ScriptClass.Mongolian + or ScriptClass.Nko + or ScriptClass.PhagsPa + or ScriptClass.Syriac)) + { + return; + } + + // Browsers implement CSS Text §8.2.1 by suppressing letter spacing inside + // cursively joined runs: + // https://www.w3.org/TR/css-text-4/#cursive-tracking + // Store the resolved classification on each post-GSUB record so layout + // does not re-itemize the shaped glyph stream. + int end = index + count; + for (int i = index; i < end; i++) + { + buffer[i].IsCursiveScript = true; + } + } + + /// + /// Tracks the script and language shared by one shaping run. + /// + public struct ShapingRun + { + private int textRunIndex; + private readonly TextDirection direction; + + /// + /// Initializes a new instance of the struct from the first shaping record in a run. + /// + /// The glyph shaping buffer. + /// The zero-based index of the first record. + public ShapingRun(ShapingBuffer buffer, int index) + { + this.Script = ResolveScript(buffer, index); + this.Culture = ResolveCulture(buffer, index); + this.FeatureTags = ResolveFeatureTags(buffer, index); + this.textRunIndex = buffer[index].TextRunIndex; + this.direction = buffer[index].Direction; + } + + /// + /// Gets the script resolved for the run. + /// + public ScriptClass Script { get; private set; } + + /// + /// Gets the culture resolved for the run. + /// + public CultureInfo Culture { get; } + + /// + /// Gets the additional feature tags resolved for the run. + /// + public IReadOnlyList FeatureTags { get; } + + /// + /// Attempts to extend the run through one adjacent shaping record. + /// + /// The glyph shaping buffer. + /// The zero-based index of the adjacent record. + /// when the record belongs to this run. + public bool TryInclude(ShapingBuffer buffer, int index) + { + CodePoint codePoint = buffer[index].CodePoint; + ScriptClass next = ResolveScript(buffer, index); + int nextTextRunIndex = buffer[index].TextRunIndex; + if (buffer[index].Direction != this.direction) + { + // Browsers resolve bidi runs before shaping and require the + // direction to match before adjacent segments can share one + // shaping call. + // Splitting here confines GSUB, GPOS, and cursive attachment to + // the same directional boundary without another glyph pass. + return false; + } + + if (nextTextRunIndex != this.textRunIndex) + { + // Styling-only TextRun boundaries must not fragment contextual + // shaping. Split only when a value consumed by the shaper changes. + + // Language-system features differ within the same script, so a + // culture change is a shaping boundary even when the script matches. + CultureInfo nextCulture = ResolveCulture(buffer, index); + if (!ReferenceEquals(nextCulture, this.Culture) && !string.Equals(nextCulture.Name, this.Culture.Name, StringComparison.Ordinal)) + { + return false; + } + + IReadOnlyList nextFeatureTags = ResolveFeatureTags(buffer, index); + if (!FeatureTagsEqual(this.FeatureTags, nextFeatureTags)) + { + return false; + } + } + + if (next != this.Script && + this.Script is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited && + next is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited && + !ScriptExtensionData.Contains(codePoint, this.Script)) + { + return false; + } + + if (this.Script is ScriptClass.Common or ScriptClass.Unknown or ScriptClass.Inherited) + { + this.Script = next; + } + + this.textRunIndex = nextTextRunIndex; + return true; + } + + /// + /// Compares effective feature lists without turning equivalent run + /// declarations into shaping boundaries. + /// + /// The first feature list. + /// The second feature list. + /// when both lists contain the same ordered tags. + private static bool FeatureTagsEqual(IReadOnlyList left, IReadOnlyList right) + { + // Inherited features normally resolve to the same collection, making + // the usual path one reference comparison. + if (ReferenceEquals(left, right)) + { + return true; + } + + // Feature lists are normally only a few tags long. A scalar comparison + // avoids allocating contiguous storage for an interface-backed list. + if (left.Count != right.Count) + { + return false; + } + + // Order matters because later user declarations are planned after + // defaults and can override whether a feature is enabled. + for (int i = 0; i < left.Count; i++) + { + if (left[i] != right[i]) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlan.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlan.cs new file mode 100644 index 000000000..48909fc55 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlan.cs @@ -0,0 +1,408 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; +using SixLabors.Fonts.Unicode; + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// The prebuilt shaping plan for one (font, script, options) identity: the shaper, +/// its stage list, and per layout table the pause-delimited stage groups with their +/// merged lookup lists. Structure that does not depend on the text is resolved once +/// here; per-glyph feature assignment and mask values remain per-pass state. Plans +/// are immutable after construction apart from the lazily built positioning groups, +/// and are only shared within a single buffer, which is exclusively owned. +/// +internal sealed class ShapePlan +{ + /// + /// The substitution table the plan resolved against, or + /// when the font has none; retained for pause-time feature queries that fall + /// outside the resolved set. + /// + private readonly GSubTable? gsubTable; + + /// + /// The features resolved while building the substitution groups, kept as a flat + /// list because a plan resolves a few dozen at most. Pause-time queries from the + /// complex shapers read these instead of re-resolving per syllable. + /// + private readonly List<(Tag Feature, List<(Tag Feature, ushort Index, GSub.LookupTable LookupTable)>? Lookups)> resolvedGsubFeatures = new(); + + /// + /// The lazily built substitution stage groups. Shapers register their stages + /// while planning a segment, not at construction, so groups can only be built + /// after the first plan invocation; a stage count change afterwards triggers a + /// rebuild so conditionally registered stages are never missed. + /// + private List>? gsubStageGroups; + + /// + /// The lazily built positioning stage groups. Positioning tables are only + /// reachable from the positioning pass, so the first positioning use builds + /// them. + /// + private List>? gposStageGroups; + + /// + /// The stage count the groups were built over. A later plan invocation that + /// grows the stage list invalidates both tables' groups. + /// + private int builtStageCount = -1; + + /// + /// Initializes a new instance of the class. Use + /// , which also resolves the substitution groups. + /// + /// The font the plan binds to. + /// The script class the plan shapes. + /// The resolved OpenType script tag. + /// The shaper constructed for the plan. + /// The shaper's stage list. + /// The language system candidates the plan resolved under. + /// The substitution table, or . + private ShapePlan( + FontMetrics fontMetrics, + ScriptClass script, + Tag scriptTag, + BaseShaper shaper, + List stages, + Tag[] languageTags, + GSubTable? gsubTable) + { + this.FontMetrics = fontMetrics; + this.Script = script; + this.ScriptTag = scriptTag; + this.Shaper = shaper; + this.Stages = stages; + this.LanguageTags = languageTags; + this.gsubTable = gsubTable; + } + + /// + /// Gets the font the plan binds to. + /// + public FontMetrics FontMetrics { get; } + + /// + /// Gets the script class the plan shapes. + /// + public ScriptClass Script { get; } + + /// + /// Gets the resolved OpenType script tag. + /// + public Tag ScriptTag { get; } + + /// + /// Gets the shaper constructed for the plan. Its per-segment state is reassigned + /// at every pause invocation, so reusing the instance across segments and passes + /// is safe within the owning buffer. + /// + public BaseShaper Shaper { get; } + + /// + /// Gets the shaper's live stage list; group boundaries index into it. Shapers + /// append stages while planning, so the list is only complete after a plan + /// invocation and additions are deduplicated by feature tag. + /// + public List Stages { get; } + + /// + /// Gets the language system candidates the plan resolved under. + /// + public Tag[] LanguageTags { get; } + + /// + /// Gets the plan's feature classification and bit assignment, shared with the + /// shaper that created it. Global features share one reserved bit; varying + /// features gain distinct bits in registration order, append-only, so plans of + /// the same identity assign identical layouts. + /// + public ShapePlanFeatures Features => this.Shaper.Features; + + /// + /// Gets a value indicating whether the plan may be cached. A substitution table + /// carrying feature variations resolves against live variation coordinates, so + /// its plans must be rebuilt per operation. + /// + public bool IsCacheable => this.gsubTable?.FeatureVariations is null; + + /// + /// Builds the plan for the given identity: constructs the shaper and captures + /// its live stage list. Stage groups are built lazily by the first + /// call after a plan invocation has + /// registered the shaper's stages. + /// + /// The font the plan binds to. + /// The script class the plan shapes. + /// The resolved OpenType script tag. + /// The options whose values the shaper captures. + /// The additional feature tags for the plan's shaping runs. + /// The language system candidates to resolve under. + /// The built . + public static ShapePlan Build(FontMetrics fontMetrics, ScriptClass script, Tag scriptTag, TextOptions textOptions, IReadOnlyList featureTags, Tag[] languageTags) + { + BaseShaper shaper = ShaperFactory.Create(script, scriptTag, fontMetrics, textOptions, languageTags); + + // A TextRun list replaces the whole-text feature list. Assign the already + // resolved list before planning so GSUB and GPOS use the same features. + shaper.FeatureTags = featureTags; + List stages = shaper.GetShapingStages(); + + _ = fontMetrics.TryGetGSubTable(out GSubTable? gsubTable); + return new(fontMetrics, script, scriptTag, shaper, stages, languageTags, gsubTable); + } + + /// + /// Gets the substitution stage groups, building them from the live stage list + /// on first use and rebuilding both tables' groups if a later plan invocation + /// registered additional stages. + /// + /// The substitution stage groups. + public List> GetOrBuildGSubStageGroups() + { + // The build body lives in a separate method because its resolver captures + // state: keeping the lambda out of this method keeps the steady-state hit + // path free of closure allocation, which would otherwise occur on every + // call at method entry regardless of the early return. + if (this.gsubStageGroups is not null && this.Stages.Count == this.builtStageCount) + { + return this.gsubStageGroups; + } + + return this.BuildGSubStageGroups(); + } + + /// + /// Gets the positioning stage groups, building them on first use over the same + /// stage boundaries as the substitution groups. A positioning table carrying + /// feature variations resolves against live variation coordinates, so its + /// groups are rebuilt on every call instead of stored. + /// + /// The positioning table to resolve against. + /// The positioning stage groups. + public List> GetOrBuildGPosStageGroups(GPosTable gposTable) + { + // Ensure the group boundaries reflect the current stage list before the + // positioning groups are built or served. The build body lives in a + // separate method for the same closure-allocation reason as the + // substitution side. + _ = this.GetOrBuildGSubStageGroups(); + + if (this.gposStageGroups is not null && gposTable.FeatureVariations is null) + { + return this.gposStageGroups; + } + + return this.BuildGPosStageGroups(gposTable); + } + + /// + /// Builds the substitution stage groups from the live stage list, invalidating + /// everything built over the old boundaries first: the positioning groups and + /// the resolved feature snapshot both index the same partition. + /// + /// The substitution stage groups. + private List> BuildGSubStageGroups() + { + this.builtStageCount = this.Stages.Count; + this.gposStageGroups = null; + this.resolvedGsubFeatures.Clear(); + + List> groups = new(); + GSubTable? gsubTable = this.gsubTable; + FontMetrics fontMetrics = this.FontMetrics; + ScriptClass script = this.Script; + Tag[] languageTags = this.LanguageTags; + this.BuildStageGroups( + this.Stages, + groups, + (in Tag featureTag, out List<(Tag Feature, ushort Index, GSub.LookupTable LookupTable)>? lookups) => + { + if (gsubTable is null) + { + lookups = null; + return false; + } + + bool found = gsubTable.TryGetFeatureLookups(fontMetrics, in featureTag, script, languageTags, out lookups); + this.resolvedGsubFeatures.Add((featureTag, lookups)); + return found; + }); + + this.gsubStageGroups = groups; + return groups; + } + + /// + /// Builds the positioning stage groups over the current stage boundaries and + /// stores them unless the table's resolution depends on live variation + /// coordinates. + /// + /// The positioning table to resolve against. + /// The positioning stage groups. + private List> BuildGPosStageGroups(GPosTable gposTable) + { + List> groups = new(); + FontMetrics fontMetrics = this.FontMetrics; + ScriptClass script = this.Script; + Tag[] languageTags = this.LanguageTags; + this.BuildStageGroups( + this.Stages, + groups, + (in Tag featureTag, out List<(Tag Feature, ushort Index, GPos.LookupTable LookupTable)>? lookups) => + gposTable.TryGetFeatureLookups(fontMetrics, in featureTag, script, languageTags, out lookups)); + + if (gposTable.FeatureVariations is null) + { + this.gposStageGroups = groups; + } + + return groups; + } + + /// + /// Gets the resolved substitution lookups for a feature, serving pause-time + /// queries from the complex shapers. Features the plan resolved while building + /// its groups answer from the plan; anything else falls through to the table so + /// behavior never depends on the plan's coverage. + /// + /// The feature tag to query. + /// When this method returns, contains the feature's lookups if any. + /// if the feature resolves to at least one lookup. + public bool TryGetGSubFeatureLookups(in Tag featureTag, [NotNullWhen(true)] out List<(Tag Feature, ushort Index, GSub.LookupTable LookupTable)>? lookups) + { + List<(Tag Feature, List<(Tag Feature, ushort Index, GSub.LookupTable LookupTable)>? Lookups)> resolved = this.resolvedGsubFeatures; + for (int i = 0; i < resolved.Count; i++) + { + if (resolved[i].Feature == featureTag) + { + lookups = resolved[i].Lookups; + return lookups is not null && lookups.Count > 0; + } + } + + if (this.gsubTable is null) + { + lookups = null; + return false; + } + + return this.gsubTable.TryGetFeatureLookups(this.FontMetrics, in featureTag, this.Script, this.LanguageTags, out lookups); + } + + /// + /// Computes the pause-delimited stage groups over the stage list and resolves + /// each group's features into a merged lookup list sorted by lookup index. A + /// lookup registered by several of a group's features gains their combined + /// plan-assigned mask instead of a second entry. + /// + /// The layout table's lookup type. + /// The stage list to group. + /// The group list to fill. + /// The per-feature lookup resolver for the table. + private void BuildStageGroups( + List stages, + List> groups, + ShapePlanLookupResolver resolver) + { + // The stage list is a flat sequence of (feature, optional pre action, + // optional post action) entries. Application order requires that every + // lookup registered by stages before a pause action has applied before + // that action runs, so the sequence partitions into groups: a stage with + // a pre action starts a new group, and a stage with a post action ends + // its group. Within one group all features apply together, ordered by + // lookup index rather than by stage, because the specification orders + // lookups within a single application pass by their position in the + // font's lookup list. + int stageIndex = 0; + while (stageIndex < stages.Count) + { + // Walk forward from the group's first stage until something closes + // it: the current stage carries a post action, the list ends, or the + // next stage carries a pre action and therefore opens the next group. + int groupEnd = stageIndex; + while (true) + { + groupEnd++; + if (stages[groupEnd - 1].HasPostAction || groupEnd >= stages.Count || stages[groupEnd].HasPreAction) + { + break; + } + } + + // Resolve each stage feature in the group to its lookups and fold + // them into one list ordered by lookup index, freezing each entry's + // combined mask from the classification recorded at registration. A + // zero mask means the feature is disabled for the plan or has no bit, + // so its lookups are never collected. The scan below runs backwards + // from the tail because resolved lookups arrive mostly ascending, so + // the insertion point is almost always at or near the end. + ShapePlanStageGroup group = new(stageIndex, groupEnd); + List<(Tag Feature, ushort Index, TLookup LookupTable, uint Mask, bool AutoZwnj, bool AutoZwj, bool Random, bool PerSyllable)> merged = group.Lookups; + for (int s = stageIndex; s < groupEnd; s++) + { + Tag featureTag = stages[s].FeatureTag; + uint featureMask = this.Features.GetMask(featureTag); + if (featureMask == 0) + { + continue; + } + + if (!resolver(in featureTag, out List<(Tag Feature, ushort Index, TLookup LookupTable)>? lookups) || lookups is null) + { + continue; + } + + ShapingFeatureFlags featureFlags = this.Features.GetFlags(featureTag); + bool autoZwnj = (featureFlags & ShapingFeatureFlags.ManualZwnj) == 0; + bool autoZwj = (featureFlags & ShapingFeatureFlags.ManualZwj) == 0; + bool random = (featureFlags & ShapingFeatureFlags.Random) != 0; + bool perSyllable = (featureFlags & ShapingFeatureFlags.PerSyllable) != 0; + + foreach ((Tag Feature, ushort Index, TLookup LookupTable) featureLookup in lookups) + { + // Scan from the tail toward the head. Three outcomes: the + // lookup index is already present, so this feature's mask + // joins the entry and the joiner handling intersects; a + // smaller index is found, so the new entry inserts directly + // after it; or the head is reached, so the new entry inserts + // first. + int insertAt = merged.Count; + bool alreadyMerged = false; + while (insertAt > 0) + { + (Tag Feature, ushort Index, TLookup LookupTable, uint Mask, bool AutoZwnj, bool AutoZwj, bool Random, bool PerSyllable) prior = merged[insertAt - 1]; + if (prior.Index == featureLookup.Index) + { + merged[insertAt - 1] = (prior.Feature, prior.Index, prior.LookupTable, prior.Mask | featureMask, prior.AutoZwnj && autoZwnj, prior.AutoZwj && autoZwj, prior.Random, prior.PerSyllable); + alreadyMerged = true; + break; + } + + if (prior.Index < featureLookup.Index) + { + break; + } + + insertAt--; + } + + if (!alreadyMerged) + { + merged.Insert(insertAt, (featureLookup.Feature, featureLookup.Index, featureLookup.LookupTable, featureMask, autoZwnj, autoZwj, random, perSyllable)); + } + } + } + + groups.Add(group); + + // The next group starts where this one ended; every stage belongs to + // exactly one group. + stageIndex = groupEnd; + } + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlanFeatures.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlanFeatures.cs new file mode 100644 index 000000000..577651f9d --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlanFeatures.cs @@ -0,0 +1,311 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// The feature classification and bit assignment owned by one shape plan. Features +/// that apply to every glyph of the plan's segments share a single reserved global +/// bit, so any number of them fits one mask; only features whose per-glyph state +/// varies receive distinct bits, assigned in registration order and append-only, so +/// plans of the same identity assign identical layouts. Classification moves in one +/// direction only: a global feature may be demoted to varying or disabled outright, +/// and either move is terminal, which keeps repeated per-segment planning +/// convergent. The vertical trio occupies reserved bits in the applied-mask space, +/// which is independent of the registration space managed here. +/// +internal sealed class ShapePlanFeatures +{ + /// + /// The fixed mask bit for the vertical alternates feature. The vertical trio + /// keeps reserved bits identical across every plan so applied-mask consumers + /// that span plans, such as the copy-out's vertical detection, never depend on + /// any single plan's layout. + /// + public const uint VerticalAlternatesMask = 1U << 0; + + /// + /// The fixed mask bit for the vertical alternates for rotation feature. + /// + public const uint VerticalAlternatesForRotationMask = 1U << 1; + + /// + /// The fixed mask bit for the vertical kerning feature. + /// + public const uint VerticalKerningMask = 1U << 2; + + /// + /// The combined mask of the three vertical alternate features, constant across + /// plans by the reserved-bit contract above. + /// + public const uint VerticalFeatureMask = VerticalAlternatesMask | VerticalAlternatesForRotationMask | VerticalKerningMask; + + /// + /// The single mask bit shared by every global feature: one that applies to all + /// glyphs of the plan's segments. Every planned glyph carries this bit, so the + /// lookups of any number of global features match with one bit between them and + /// the distinct bits are kept for features whose per-glyph state varies. + /// + public const uint GlobalFeatureMask = 1U << 31; + + /// + /// The first bit available to varying features; lower bits stay clear of the + /// applied-mask space's reserved trio so a mask value's space is identifiable + /// at a glance when debugging. + /// + private const int FirstAssignableBit = 3; + + /// + /// The number of assignable varying-feature bits: everything between the first + /// assignable bit and the reserved global bit. + /// + private const int AssignableBitCount = 31 - FirstAssignableBit; + + /// + /// The varying feature tag values, indexed by bit position above the reserved + /// bits. Stored as raw values so lookups take the runtime's + /// vectorized primitive search path. + /// + private readonly List featureTags = new(16); + + /// + /// The global feature tag values; each resolves to the shared + /// bit. + /// + private readonly List globalTags = new(32); + + /// + /// The disabled feature tag values. A disabled feature resolves to a zero mask + /// and never re-registers, so its lookups are never collected and its state + /// survives repeated per-segment planning. + /// + private readonly List disabledTags = new(1); + + /// + /// The tag values carrying registration flags, parallel to + /// . Flags are recorded at registration and read only + /// at plan build, so a linear search suffices. + /// + private readonly List flagTags = new(16); + + /// + /// The registration flags, parallel to . + /// + private readonly List flagValues = new(16); + + /// + /// The most recently resolved tag value. Queries strongly repeat the same + /// feature during one feature's application, so a single-entry memo answers + /// almost every query without a list search. Zero means the memo is empty; the + /// zero tag is never a valid feature. + /// + private uint lastTagValue; + + /// + /// The mask paired with . + /// + private uint lastMask; + + /// + /// Gets the fixed vertical-trio mask bit for a feature, or zero for any other + /// feature. Applied-mask consumers only ever read the vertical bits, which are + /// reserved and constant across plans, so applied recording needs no plan state. + /// + /// The feature tag whose lookups applied. + /// The fixed vertical mask bit, or zero. + public static uint GetVerticalMask(Tag tag) + { + if (tag == KnownFeatureTags.VerticalAlternates) + { + return VerticalAlternatesMask; + } + + if (tag == KnownFeatureTags.VerticalAlternatesForRotation) + { + return VerticalAlternatesForRotationMask; + } + + if (tag == KnownFeatureTags.VerticalKerning) + { + return VerticalKerningMask; + } + + return 0; + } + + /// + /// Records registration flags for a feature, combining with any flags already + /// recorded. Flags accumulate the way masks do: a feature registered by several + /// sites keeps every site's requirements. + /// + /// The feature tag. + /// The flags to record. + public void AddFlags(Tag tag, ShapingFeatureFlags flags) + { + if (flags == ShapingFeatureFlags.None) + { + return; + } + + int index = this.flagTags.IndexOf(tag.Value); + if (index >= 0) + { + this.flagValues[index] |= flags; + return; + } + + this.flagTags.Add(tag.Value); + this.flagValues.Add(flags); + } + + /// + /// Gets the registration flags recorded for a feature, or none. + /// + /// The feature tag. + /// The recorded flags. + public ShapingFeatureFlags GetFlags(Tag tag) + { + int index = this.flagTags.IndexOf(tag.Value); + return index >= 0 ? this.flagValues[index] : ShapingFeatureFlags.None; + } + + /// + /// Gets the mask for the given feature tag: a varying feature's distinct bit, + /// the shared global bit for a global feature, or zero when the tag is disabled + /// or unknown. A zero result is safe at every consumption site: testing it + /// enables or matches nothing and clearing it clears nothing. + /// + /// The feature tag. + /// The feature's mask, or zero. + public uint GetMask(Tag tag) + { + if (tag.Value == this.lastTagValue) + { + return this.lastMask; + } + + if (this.disabledTags.Count > 0 && this.disabledTags.Contains(tag.Value)) + { + return 0; + } + + int index = this.featureTags.IndexOf(tag.Value); + if (index >= 0) + { + uint mask = 1U << (FirstAssignableBit + index); + this.lastTagValue = tag.Value; + this.lastMask = mask; + return mask; + } + + if (this.globalTags.Contains(tag.Value)) + { + this.lastTagValue = tag.Value; + this.lastMask = GlobalFeatureMask; + return GlobalFeatureMask; + } + + // A zero mask is never memoized: the tag may gain an assignment later and + // the memo must not serve a stale zero after that. + return 0; + } + + /// + /// Gets the distinct bit for a varying feature, assigning the next free bit + /// when the tag has none. A feature currently classified global is demoted to + /// varying, keeping the terminal classification: repeated per-segment planning + /// re-registers the same features, and a demoted feature must stay demoted. A + /// disabled feature stays disabled, and a plan that has exhausted its bits + /// assigns nothing; both cases return zero, which registers nothing, enables + /// nothing, and matches nothing at every consumption site. + /// + /// The feature tag. + /// The distinct bit, or zero when disabled or exhausted. + public uint GetOrAddMask(Tag tag) + { + if (this.disabledTags.Count > 0 && this.disabledTags.Contains(tag.Value)) + { + return 0; + } + + int index = this.featureTags.IndexOf(tag.Value); + if (index >= 0) + { + uint mask = 1U << (FirstAssignableBit + index); + this.lastTagValue = tag.Value; + this.lastMask = mask; + return mask; + } + + // Demote a global registration: removal is a no-op when the tag was never + // global. The allocation below then gives the feature its distinct bit. + this.globalTags.Remove(tag.Value); + + if (this.featureTags.Count == AssignableBitCount) + { + return 0; + } + + this.featureTags.Add(tag.Value); + uint added = 1U << (FirstAssignableBit + this.featureTags.Count - 1); + this.lastTagValue = tag.Value; + this.lastMask = added; + return added; + } + + /// + /// Gets the shared global bit for a feature that applies to every glyph, + /// recording the tag as global when it is new to this plan. A feature already + /// demoted to varying keeps its distinct bit and a disabled feature stays + /// disabled: both classifications are terminal so repeated per-segment planning + /// converges instead of oscillating. + /// + /// The feature tag. + /// The feature's mask, or zero when the feature is disabled. + public uint GetOrAddGlobalMask(Tag tag) + { + if (this.disabledTags.Count > 0 && this.disabledTags.Contains(tag.Value)) + { + return 0; + } + + int index = this.featureTags.IndexOf(tag.Value); + if (index >= 0) + { + uint mask = 1U << (FirstAssignableBit + index); + this.lastTagValue = tag.Value; + this.lastMask = mask; + return mask; + } + + if (!this.globalTags.Contains(tag.Value)) + { + this.globalTags.Add(tag.Value); + } + + this.lastTagValue = tag.Value; + this.lastMask = GlobalFeatureMask; + return GlobalFeatureMask; + } + + /// + /// Disables a feature for the whole plan: its mask resolves to zero from now + /// on, so no lookups are collected for it and no per-glyph state can enable it. + /// Disabling is terminal; later registrations of the tag do not resurrect it. + /// + /// The feature tag. + public void DisableFeature(Tag tag) + { + this.globalTags.Remove(tag.Value); + if (!this.disabledTags.Contains(tag.Value)) + { + this.disabledTags.Add(tag.Value); + } + + if (this.lastTagValue == tag.Value) + { + this.lastTagValue = 0; + this.lastMask = 0; + } + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlanLookupResolver{TLookup}.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlanLookupResolver{TLookup}.cs new file mode 100644 index 000000000..744db382c --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlanLookupResolver{TLookup}.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// Resolves a feature tag to its lookups for one layout table while a shape plan is +/// being built. Runs only at plan build; application reads the plan's prebuilt +/// lists. +/// +/// The layout table's lookup type. +/// The feature tag to resolve. +/// When this method returns, contains the resolved lookups if any. +/// if the feature resolved to at least one lookup. +internal delegate bool ShapePlanLookupResolver(in Tag featureTag, out List<(Tag Feature, ushort Index, TLookup LookupTable)>? lookups); diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlanStageGroup{TLookup}.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlanStageGroup{TLookup}.cs new file mode 100644 index 000000000..ddec25480 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapePlanStageGroup{TLookup}.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// One pause-delimited stage group of a shape plan: the stage index range it covers +/// and the group's lookups merged into lookup-index order, each entry carrying the +/// combined plan-assigned mask of every feature that registered it, frozen when the +/// plan is built. +/// +/// The layout table's lookup type. +internal sealed class ShapePlanStageGroup +{ + /// + /// Initializes a new instance of the + /// class covering the given stage range. + /// + /// The index of the first stage in the group. + /// The exclusive index of the last stage in the group. + public ShapePlanStageGroup(int start, int end) + { + this.Start = start; + this.End = end; + this.Lookups = new(); + } + + /// + /// Gets the index of the first stage in the group; its pre action opens the + /// group. + /// + public int Start { get; } + + /// + /// Gets the exclusive index of the last stage in the group; the previous stage's + /// post action closes the group. + /// + public int End { get; } + + /// + /// Gets the group's lookups merged across its stage features into lookup-index + /// order. A lookup registered by several features appears once with their + /// plan-assigned masks combined and their joiner handling intersected: a + /// lookup skips a joiner automatically only when every registering feature + /// allows it. Application consumes the list directly. + /// + public List<(Tag Feature, ushort Index, TLookup LookupTable, uint Mask, bool AutoZwnj, bool AutoZwj, bool Random, bool PerSyllable)> Lookups { get; } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ArabicFallbackSubstitutions.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ArabicFallbackSubstitutions.cs new file mode 100644 index 000000000..acdada640 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ArabicFallbackSubstitutions.cs @@ -0,0 +1,281 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Binary; +using SixLabors.Fonts.Unicode; +using SixLabors.Fonts.Unicode.Resources; + +namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; + +/// +/// Applies Arabic presentation forms and required ligatures when a font provides no joining-form substitutions. +/// +internal sealed class ArabicFallbackSubstitutions +{ + /// + /// The initial presentation-form column. + /// + private const int InitialFormIndex = 0; + + /// + /// The medial presentation-form column. + /// + private const int MedialFormIndex = 1; + + /// + /// The final presentation-form column. + /// + private const int FinalFormIndex = 2; + + /// + /// The isolated presentation-form column. + /// + private const int IsolatedFormIndex = 3; + + /// + /// The byte offset of the second character in a packed ligature entry. + /// + private const int SecondCharacterOffset = sizeof(ushort); + + /// + /// The byte offset of the third character in a packed three-character ligature entry. + /// + private const int ThirdCharacterOffset = sizeof(ushort) * 2; + + /// + /// The byte offset of the result in a packed two-character ligature entry. + /// + private const int TwoCharacterResultOffset = sizeof(ushort) * 2; + + /// + /// The byte offset of the result in a packed three-character ligature entry. + /// + private const int ThreeCharacterResultOffset = sizeof(ushort) * 3; + + /// + /// The synthesized three-character required ligatures. + /// + private readonly FallbackLigature[] threeCharacterLigatures; + + /// + /// The synthesized two-character required ligatures. + /// + private readonly FallbackLigature[] twoCharacterLigatures; + + /// + /// The synthesized mark required ligatures. + /// + private readonly FallbackLigature[] markLigatures; + + /// + /// Initializes a new instance of the class. + /// + /// The three-character required ligatures available in the font. + /// The two-character required ligatures available in the font. + /// The mark required ligatures available in the font. + private ArabicFallbackSubstitutions(FallbackLigature[] threeCharacterLigatures, FallbackLigature[] twoCharacterLigatures, FallbackLigature[] markLigatures) + { + this.threeCharacterLigatures = threeCharacterLigatures; + this.twoCharacterLigatures = twoCharacterLigatures; + this.markLigatures = markLigatures; + } + + /// + /// Creates the fallback substitutions supported by the given font. + /// + /// The font metrics used to resolve characters to glyph identifiers. + /// The resolved fallback substitutions. + public static ArabicFallbackSubstitutions Create(FontMetrics fontMetrics) + => new( + ResolveLigatures(fontMetrics, ArabicFallbackData.ThreeCharacterLigatures, true), + ResolveLigatures(fontMetrics, ArabicFallbackData.TwoCharacterLigatures, false), + ResolveLigatures(fontMetrics, ArabicFallbackData.MarkLigatures, false)); + + /// + /// Applies the joining forms and required ligatures to a shaping segment. + /// + /// The shaping plan supplying feature masks and font metrics. + /// The shaping buffer. + /// The zero-based index of the first record. + /// The number of records in the segment. + /// The initial-form feature tag. + /// The medial-form feature tag. + /// The final-form feature tag. + /// The isolated-form feature tag. + /// The required-ligatures feature tag. + public void Apply(ShapePlan plan, ShapingBuffer buffer, int index, int count, Tag initialTag, Tag medialTag, Tag finalTag, Tag isolatedTag, Tag requiredLigaturesTag) + { + FontMetrics fontMetrics = plan.FontMetrics; + ApplyPresentationForms(fontMetrics, plan.Features.GetMask(initialTag), buffer, index, count, initialTag, InitialFormIndex); + ApplyPresentationForms(fontMetrics, plan.Features.GetMask(medialTag), buffer, index, count, medialTag, MedialFormIndex); + ApplyPresentationForms(fontMetrics, plan.Features.GetMask(finalTag), buffer, index, count, finalTag, FinalFormIndex); + ApplyPresentationForms(fontMetrics, plan.Features.GetMask(isolatedTag), buffer, index, count, isolatedTag, IsolatedFormIndex); + + uint requiredLigaturesMask = plan.Features.GetMask(requiredLigaturesTag); + buffer.SetLookupMatchState(requiredLigaturesMask, true, false, false, false); + + count = ApplyLigatures(fontMetrics, buffer, index, count, requiredLigaturesTag, requiredLigaturesMask, this.threeCharacterLigatures, LookupFlags.IgnoreMarks); + count = ApplyLigatures(fontMetrics, buffer, index, count, requiredLigaturesTag, requiredLigaturesMask, this.twoCharacterLigatures, LookupFlags.IgnoreMarks); + _ = ApplyLigatures(fontMetrics, buffer, index, count, requiredLigaturesTag, requiredLigaturesMask, this.markLigatures, default); + } + + /// + /// Applies one presentation-form column to records carrying the corresponding feature mask. + /// + /// The font metrics. + /// The joining-form feature mask. + /// The shaping buffer. + /// The zero-based index of the first record. + /// The number of records in the segment. + /// The joining-form feature tag. + /// The presentation-form column. + private static void ApplyPresentationForms(FontMetrics fontMetrics, uint featureMask, ShapingBuffer buffer, int index, int count, Tag feature, int formIndex) + { + int end = index + count; + for (int i = index; i < end; i++) + { + ref GlyphShapingData data = ref buffer[i]; + if ((data.FeatureMask & featureMask) == 0 + || !fontMetrics.TryGetGlyphId(data.CodePoint, out ushort baseGlyphId) + || data.GlyphId != baseGlyphId) + { + continue; + } + + ushort presentationForm = ArabicFallbackData.GetPresentationForm(data.CodePoint.Value, formIndex); + if (presentationForm != 0 + && fontMetrics.TryGetGlyphId(new CodePoint(presentationForm), out ushort presentationGlyphId) + && presentationGlyphId != baseGlyphId) + { + buffer.Replace(i, presentationGlyphId, feature); + } + } + } + + /// + /// Resolves one packed required-ligature table against the font's character map. + /// + /// The font metrics. + /// The packed character entries. + /// Whether each entry contains a third input character. + /// The entries whose input and result characters all have glyphs. + private static FallbackLigature[] ResolveLigatures(FontMetrics fontMetrics, ReadOnlySpan data, bool hasThirdCharacter) + { + int entrySize = hasThirdCharacter ? ArabicFallbackData.ThreeCharacterLigatureEntrySize : ArabicFallbackData.TwoCharacterLigatureEntrySize; + int resultOffset = hasThirdCharacter ? ThreeCharacterResultOffset : TwoCharacterResultOffset; + List ligatures = new(data.Length / entrySize); + for (int offset = 0; offset < data.Length; offset += entrySize) + { + ushort firstCharacter = BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(offset, sizeof(ushort))); + ushort secondCharacter = BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(offset + SecondCharacterOffset, sizeof(ushort))); + ushort thirdCharacter = hasThirdCharacter ? BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(offset + ThirdCharacterOffset, sizeof(ushort))) : (ushort)0; + ushort resultCharacter = BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(offset + resultOffset, sizeof(ushort))); + ushort thirdGlyph = 0; + + if (!fontMetrics.TryGetGlyphId(new CodePoint(firstCharacter), out ushort firstGlyph) + || !fontMetrics.TryGetGlyphId(new CodePoint(secondCharacter), out ushort secondGlyph) + || (hasThirdCharacter && !fontMetrics.TryGetGlyphId(new CodePoint(thirdCharacter), out thirdGlyph)) + || !fontMetrics.TryGetGlyphId(new CodePoint(resultCharacter), out ushort resultGlyph)) + { + continue; + } + + ushort[] components; + if (hasThirdCharacter) + { + components = [secondGlyph, thirdGlyph]; + } + else + { + components = [secondGlyph]; + } + + ligatures.Add(new FallbackLigature(firstGlyph, components, resultGlyph)); + } + + return [.. ligatures]; + } + + /// + /// Applies one synthesized required-ligature lookup. + /// + /// The font metrics. + /// The shaping buffer. + /// The zero-based index of the first record. + /// The number of records in the segment. + /// The required-ligatures feature tag. + /// The required-ligatures feature mask. + /// The synthesized ligatures. + /// The lookup flags controlling skipped glyphs. + /// The segment count after substitutions. + private static int ApplyLigatures(FontMetrics fontMetrics, ShapingBuffer buffer, int index, int count, Tag feature, uint featureMask, FallbackLigature[] ligatures, LookupFlags lookupFlags) + { + int end = index + count; + Span matchBuffer = buffer.GetContextMatchPositions()[..AdvancedTypographicUtils.MaxContextLength]; + for (int position = index; position < end; position++) + { + ref GlyphShapingData data = ref buffer[position]; + if ((data.FeatureMask & featureMask) == 0) + { + continue; + } + + for (int i = 0; i < ligatures.Length; i++) + { + FallbackLigature ligature = ligatures[i]; + if (data.GlyphId != ligature.FirstGlyph) + { + continue; + } + + SkippingGlyphIterator iterator = new(fontMetrics, buffer, position, lookupFlags, 0); + if (!AdvancedTypographicUtils.MatchInputSequence(iterator, featureMask, 1, ligature.Components, matchBuffer)) + { + continue; + } + + Span matches = matchBuffer[..ligature.Components.Length]; + AdvancedTypographicUtils.ApplyLigatureSubstitution(fontMetrics, buffer, position, matches, ligature.ResultGlyph, feature, end - position); + end -= matches.Length; + count -= matches.Length; + break; + } + } + + return count; + } + + /// + /// Stores one font-resolved ligature substitution. + /// + private readonly struct FallbackLigature + { + /// + /// Initializes a new instance of the struct. + /// + /// The first input glyph. + /// The remaining input glyphs. + /// The result glyph. + public FallbackLigature(ushort firstGlyph, ushort[] components, ushort resultGlyph) + { + this.FirstGlyph = firstGlyph; + this.Components = components; + this.ResultGlyph = resultGlyph; + } + + /// + /// Gets the first input glyph. + /// + public ushort FirstGlyph { get; } + + /// + /// Gets the remaining input glyphs. + /// + public ushort[] Components { get; } + + /// + /// Gets the result glyph. + /// + public ushort ResultGlyph { get; } + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ArabicJoining.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ArabicJoining.cs new file mode 100644 index 000000000..0e7217239 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ArabicJoining.cs @@ -0,0 +1,286 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; + +namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; + +/// +/// Chooses the form each character of a cursive script takes from the characters +/// around it. Several scripts join this way, and the shapers that handle them all +/// settle the forms here. +/// +internal static class ArabicJoining +{ + /// + /// No form is chosen. + /// + private const byte None = 0; + + /// + /// Isolated form action. + /// + private const byte Isol = 1; + + /// + /// Final form action. + /// + private const byte Fina = 2; + + /// + /// Final form #2 action (for ALAPH). + /// + private const byte Fin2 = 3; + + /// + /// Final form #3 action (for ALAPH after DALATH RISH). + /// + private const byte Fin3 = 4; + + /// + /// Medial form action. + /// + private const byte Medi = 5; + + /// + /// Medial form #2 action (for ALAPH). + /// + private const byte Med2 = 6; + + /// + /// Initial form action. + /// + private const byte Init = 7; + + /// + /// The 'isol' (isolated forms) feature tag. + /// + public static readonly Tag IsolTag = Tag.Parse("isol"); + + /// + /// The 'fina' (terminal forms) feature tag. + /// + public static readonly Tag FinaTag = Tag.Parse("fina"); + + /// + /// The 'fin2' (terminal forms #2) feature tag. + /// + public static readonly Tag Fin2Tag = Tag.Parse("fin2"); + + /// + /// The 'fin3' (terminal forms #3) feature tag. + /// + public static readonly Tag Fin3Tag = Tag.Parse("fin3"); + + /// + /// The 'medi' (medial forms) feature tag. + /// + public static readonly Tag MediTag = Tag.Parse("medi"); + + /// + /// The 'med2' (medial forms #2) feature tag. + /// + public static readonly Tag Med2Tag = Tag.Parse("med2"); + + /// + /// The 'init' (initial forms) feature tag. + /// + public static readonly Tag InitTag = Tag.Parse("init"); + + /// + /// The joining state machine table. Each entry is [prevAction, curAction, nextState]. + /// Rows are states (0-6), columns are joining categories. + /// + private static readonly byte[,][] StateTable = + { + // # NonJoining, LeftJoining, RightJoining, DualJoining, ALAPH, DALATH RISH + // State 0: prev was U, not willing to join. + { new byte[] { None, None, 0 }, new byte[] { None, Isol, 2 }, new byte[] { None, Isol, 1 }, new byte[] { None, Isol, 2 }, new byte[] { None, Isol, 1 }, new byte[] { None, Isol, 6 } }, + + // State 1: prev was R or ISOL/ALAPH, not willing to join. + { new byte[] { None, None, 0 }, new byte[] { None, Isol, 2 }, new byte[] { None, Isol, 1 }, new byte[] { None, Isol, 2 }, new byte[] { None, Fin2, 5 }, new byte[] { None, Isol, 6 } }, + + // State 2: prev was D/L in ISOL form, willing to join. + { new byte[] { None, None, 0 }, new byte[] { None, Isol, 2 }, new byte[] { Init, Fina, 1 }, new byte[] { Init, Fina, 3 }, new byte[] { Init, Fina, 4 }, new byte[] { Init, Fina, 6 } }, + + // State 3: prev was D in FINA form, willing to join. + { new byte[] { None, None, 0 }, new byte[] { None, Isol, 2 }, new byte[] { Medi, Fina, 1 }, new byte[] { Medi, Fina, 3 }, new byte[] { Medi, Fina, 4 }, new byte[] { Medi, Fina, 6 } }, + + // State 4: prev was FINA ALAPH, not willing to join. + { new byte[] { None, None, 0 }, new byte[] { None, Isol, 2 }, new byte[] { Med2, Isol, 1 }, new byte[] { Med2, Isol, 2 }, new byte[] { Med2, Fin2, 5 }, new byte[] { Med2, Isol, 6 } }, + + // State 5: prev was FIN2/FIN3 ALAPH, not willing to join. + { new byte[] { None, None, 0 }, new byte[] { None, Isol, 2 }, new byte[] { Isol, Isol, 1 }, new byte[] { Isol, Isol, 2 }, new byte[] { Isol, Fin2, 5 }, new byte[] { Isol, Isol, 6 } }, + + // State 6: prev was DALATH/RISH, not willing to join. + { new byte[] { None, None, 0 }, new byte[] { None, Isol, 2 }, new byte[] { None, Isol, 1 }, new byte[] { None, Isol, 2 }, new byte[] { None, Fin3, 5 }, new byte[] { None, Isol, 6 } }, + }; + + /// + /// Determines whether the script's characters take their form from the ones + /// around them. Only these scripts carry the data the state machine reads. + /// + /// The script the text is written in. + /// when the script joins. + public static bool Joins(ScriptClass script) + => script switch + { + ScriptClass.Arabic + or ScriptClass.Mongolian + or ScriptClass.Syriac + or ScriptClass.Nko + or ScriptClass.PhagsPa + or ScriptClass.Mandaic + or ScriptClass.Manichaean + or ScriptClass.PsalterPahlavi + or ScriptClass.Adlam + or ScriptClass.HanifiRohingya + or ScriptClass.Chorasmian + or ScriptClass.Sogdian + or ScriptClass.OldUyghur => true, + _ => false, + }; + + /// + /// Walks the text and turns on, for each character, the feature naming the form + /// it takes among its neighbours. + /// + /// The glyph shaping buffer. + /// The zero-based index of the first record. + /// The number of records. + /// The script the text is written in. + /// The features holding the masks to turn on. + public static void Apply(ShapingBuffer buffer, int index, int count, ScriptClass script, ShapePlanFeatures features) + { + int prev = -1; + int state = 0; + byte[] actions = buffer.GetShaperScratch(count); + + for (int i = 0; i < count; i++) + { + ref GlyphShapingData data = ref buffer[i + index]; + ArabicJoiningClass joiningClass = CodePoint.GetArabicJoiningClass(data.CodePoint); + if (joiningClass.JoiningType == ArabicJoiningType.Transparent) + { + actions[i] = None; + continue; + } + + byte[] actionsWithState = StateTable[state, GetShapingClassIndex(joiningClass)]; + byte prevAction = actionsWithState[0]; + byte curAction = actionsWithState[1]; + state = actionsWithState[2]; + + if (prevAction != None && prev != -1) + { + actions[prev] = prevAction; + } + + actions[i] = curAction; + prev = i; + } + + if (script == ScriptClass.Mongolian) + { + CarryFormsToVariationSelectors(buffer, index, count, actions); + } + + // Form selection uses the same small feature set for the complete run. + // Resolve the masks once because lookup in the plan's feature lists is + // otherwise repeated for every non-transparent character. + uint finaMask = features.GetMask(FinaTag); + uint fin2Mask = features.GetMask(Fin2Tag); + uint fin3Mask = features.GetMask(Fin3Tag); + uint isolMask = features.GetMask(IsolTag); + uint initMask = features.GetMask(InitTag); + uint mediMask = features.GetMask(MediTag); + uint med2Mask = features.GetMask(Med2Tag); + + for (int i = 0; i < count; i++) + { + switch (actions[i]) + { + case Fina: + buffer.EnableShapingFeature(i + index, finaMask); + break; + case Fin2: + buffer.EnableShapingFeature(i + index, fin2Mask); + break; + case Fin3: + buffer.EnableShapingFeature(i + index, fin3Mask); + break; + case Isol: + buffer.EnableShapingFeature(i + index, isolMask); + break; + case Init: + buffer.EnableShapingFeature(i + index, initMask); + break; + case Medi: + buffer.EnableShapingFeature(i + index, mediMask); + break; + case Med2: + buffer.EnableShapingFeature(i + index, med2Mask); + break; + } + } + } + + /// + /// Gives each free variation selector the form of the character it follows. A + /// selector chooses between shapes of that character, so it has to be drawn in + /// the same form as the character it qualifies. + /// + /// The glyph shaping buffer. + /// The zero-based index of the first record. + /// The number of records. + /// The form chosen for each record. + private static void CarryFormsToVariationSelectors(ShapingBuffer buffer, int index, int count, byte[] actions) + { + // U+180B..U+180D FREE VARIATION SELECTOR ONE..THREE and + // U+180F FREE VARIATION SELECTOR FOUR. + const int FirstFreeVariationSelector = 0x180B; + const int LastFreeVariationSelector = 0x180D; + const int FourthFreeVariationSelector = 0x180F; + + for (int i = 1; i < count; i++) + { + int value = buffer[i + index].CodePoint.Value; + if ((value >= FirstFreeVariationSelector && value <= LastFreeVariationSelector) + || value == FourthFreeVariationSelector) + { + actions[i] = actions[i - 1]; + } + } + } + + /// + /// Maps the joining properties of a character to the column of the state table + /// that describes it. + /// + /// The joining properties of the character. + /// The state table column index. + private static int GetShapingClassIndex(ArabicJoiningClass joiningClass) + { + // Two joining groups have rules of their own, and the columns that carry + // them stand in place of the ones the joining type would choose. + if (joiningClass.JoiningGroup == ArabicJoiningGroup.Alaph) + { + return 4; + } + + if (joiningClass.JoiningGroup == ArabicJoiningGroup.DalathRish) + { + return 5; + } + + return joiningClass.JoiningType switch + { + ArabicJoiningType.NonJoining => 0, + ArabicJoiningType.LeftJoining => 1, + ArabicJoiningType.RightJoining => 2, + ArabicJoiningType.DualJoining or ArabicJoiningType.JoinCausing => 3, + ArabicJoiningType.Transparent => 6, + _ => 0, + }; + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ArabicShaper.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ArabicShaper.cs index b447ba2ba..8af17ffde 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ArabicShaper.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ArabicShaper.cs @@ -1,65 +1,162 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Globalization; using SixLabors.Fonts.Unicode; namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; /// /// This is a shaper for Arabic, and other cursive scripts. -/// The shaping state machine was ported from fontkit. -/// /// +/// +/// The joining state machine and feature order follow hb-ot-shaper-arabic.cc. +/// internal sealed class ArabicShaper : DefaultShaper { - /// The 'mset' (mark positioning via substitution) feature tag. + /// + /// The canonical combining class for marks placed below a base. + /// + private const int BelowMarkOrder = 220; + + /// + /// The canonical combining class for marks placed above a base. + /// + private const int AboveMarkOrder = 230; + + /// + /// The temporary ordering class assigned to reordered marks below a base. + /// + private const int ReorderedBelowMarkOrder = 22; + + /// + /// The temporary ordering class assigned to reordered marks above a base. + /// + private const int ReorderedAboveMarkOrder = 26; + + /// + /// The 'mset' (mark positioning via substitution) feature tag. + /// private static readonly Tag MsetTag = Tag.Parse("mset"); - /// The 'fina' (terminal forms) feature tag. + /// + /// The 'stch' (stretching glyph decomposition) feature tag. + /// + private static readonly Tag StchTag = Tag.Parse("stch"); + + /// + /// The 'fina' (terminal forms) feature tag. + /// private static readonly Tag FinaTag = Tag.Parse("fina"); - /// The 'fin2' (terminal forms #2) feature tag. + /// + /// The 'fin2' (terminal forms #2) feature tag. + /// private static readonly Tag Fin2Tag = Tag.Parse("fin2"); - /// The 'fin3' (terminal forms #3) feature tag. + /// + /// The 'fin3' (terminal forms #3) feature tag. + /// private static readonly Tag Fin3Tag = Tag.Parse("fin3"); - /// The 'isol' (isolated forms) feature tag. + /// + /// The 'isol' (isolated forms) feature tag. + /// private static readonly Tag IsolTag = Tag.Parse("isol"); - /// The 'init' (initial forms) feature tag. + /// + /// The 'init' (initial forms) feature tag. + /// private static readonly Tag InitTag = Tag.Parse("init"); - /// The 'medi' (medial forms) feature tag. + /// + /// The 'medi' (medial forms) feature tag. + /// private static readonly Tag MediTag = Tag.Parse("medi"); - /// The 'med2' (medial forms #2) feature tag. + /// + /// The 'med2' (medial forms #2) feature tag. + /// private static readonly Tag Med2Tag = Tag.Parse("med2"); - /// No joining action. + /// + /// No joining action. + /// private const byte None = 0; - /// Isolated form action. + /// + /// Isolated form action. + /// private const byte Isol = 1; - /// Final form action. + /// + /// Final form action. + /// private const byte Fina = 2; - /// Final form #2 action (for ALAPH). + /// + /// Final form #2 action (for ALAPH). + /// private const byte Fin2 = 3; - /// Final form #3 action (for ALAPH after DALATH RISH). + /// + /// Final form #3 action (for ALAPH after DALATH RISH). + /// private const byte Fin3 = 4; - /// Medial form action. + /// + /// Medial form action. + /// private const byte Medi = 5; - /// Medial form #2 action (for ALAPH). + /// + /// Medial form #2 action (for ALAPH). + /// private const byte Med2 = 6; - /// Initial form action. + /// + /// Initial form action. + /// private const byte Init = 7; + /// + /// The pause action separating joining-form lookup stages. + /// + private readonly Action pauseAction; + + /// + /// The action recording fixed and repeating pieces produced by stretch + /// decomposition. + /// + private readonly Action recordStretchPartsAction; + + /// + /// The action applying presentation-form fallback after required ligatures. + /// + private readonly Action? fallbackAction; + + /// + /// Whether contextual alternates must complete before the common ligature + /// features begin. + /// + private readonly bool pauseAfterContextualAlternates; + + /// + /// Whether the selected script and language provide stretch decomposition. + /// + private readonly bool hasStretchFeature; + + /// + /// The maximum number of fixed and repeated tiles emitted for one stretch + /// decomposition. + /// + private const int MaximumStretchGlyphs = 256; + + /// + /// The font-resolved fallback substitutions, created on first use. + /// + private ArabicFallbackSubstitutions? fallbackSubstitutions; + /// /// Arabic joining state machine table. Each entry is [prevAction, curAction, nextState]. /// Rows are states (0-6), columns are joining type categories. @@ -94,119 +191,409 @@ internal sealed class ArabicShaper : DefaultShaper /// /// The script classification. /// The text options. - public ArabicShaper(ScriptClass script, TextOptions textOptions) + /// The font metrics used to resolve feature availability. + /// The language system candidates used to resolve features. + public ArabicShaper(ScriptClass script, TextOptions textOptions, FontMetrics fontMetrics, Tag[] languageTags) : base(script, MarkZeroingMode.PostGpos, textOptions) { + this.pauseAction = Pause; + this.recordStretchPartsAction = this.RecordStretchParts; + this.fallbackAction = script == ScriptClass.Arabic ? this.ApplyFallback : null; + + // Arabic fonts without required contextual alternates finish 'calt' + // before common ligatures. When 'rclt' exists, both features belong to + // the same substitution stage. Feature availability is resolved for the + // plan's selected script and language in either layout table. + bool hasRequiredContextualAlternates = false; + if (fontMetrics.TryGetGSubTable(out GSubTable? gsub)) + { + this.hasStretchFeature = gsub.TryGetFeatureLookups(fontMetrics, in StchTag, script, languageTags, out _); + hasRequiredContextualAlternates = gsub.TryGetFeatureLookups(fontMetrics, in RcltTag, script, languageTags, out _); + } + + if (!hasRequiredContextualAlternates && fontMetrics.TryGetGPosTable(out GPosTable? gpos)) + { + hasRequiredContextualAlternates = gpos.TryGetFeatureLookups(fontMetrics, in RcltTag, script, languageTags, out _); + } + + this.pauseAfterContextualAlternates = script == ScriptClass.Arabic && !hasRequiredContextualAlternates; + } + + /// + protected override void PlanFeatures(ShapingBuffer buffer, int index, int count) + { + // Stretch decomposition must be recorded immediately after its substitution + // stage; later multiple substitutions use the same component metadata. + this.EnableFeature(buffer, index, count, StchTag, null, this.recordStretchPartsAction); + + // Canonical composition and localized forms complete before the joining-form + // stages, so their output becomes the input to every contextual form lookup. + this.EnableFeature(buffer, index, count, CcmpTag, ShapingFeatureFlags.ManualZwj); + this.EnableFeature(buffer, index, count, LoclTag, ShapingFeatureFlags.ManualZwj, null, this.pauseAction); + + this.AddFeature(buffer, index, count, IsolTag, ShapingFeatureFlags.ManualZwj, false, null, this.pauseAction); + this.AddFeature(buffer, index, count, FinaTag, ShapingFeatureFlags.ManualZwj, false, null, this.pauseAction); + this.AddFeature(buffer, index, count, Fin2Tag, ShapingFeatureFlags.ManualZwj, false, null, this.pauseAction); + this.AddFeature(buffer, index, count, Fin3Tag, ShapingFeatureFlags.ManualZwj, false, null, this.pauseAction); + this.AddFeature(buffer, index, count, MediTag, ShapingFeatureFlags.ManualZwj, false, null, this.pauseAction); + this.AddFeature(buffer, index, count, Med2Tag, ShapingFeatureFlags.ManualZwj, false, null, this.pauseAction); + this.AddFeature(buffer, index, count, InitTag, ShapingFeatureFlags.ManualZwj, false, null, this.pauseAction); + + // The ligature trio and the required composition and ligature features + // match the joiners themselves for this script's shaping model. + this.EnableFeature(buffer, index, count, RligTag, ShapingFeatureFlags.ManualZwj, null, this.fallbackAction); + this.EnableFeature(buffer, index, count, CaltTag, ShapingFeatureFlags.ManualZwj, null, this.pauseAfterContextualAlternates ? this.pauseAction : null); + this.Features.AddFlags(LigaTag, ShapingFeatureFlags.ManualZwj); + this.Features.AddFlags(CligTag, ShapingFeatureFlags.ManualZwj); + } + + /// + protected override void PlanPostprocessingFeatures(ShapingBuffer buffer, int index, int count) + { + base.PlanPostprocessingFeatures(buffer, index, count); + + this.EnableFeature(buffer, index, count, MsetTag, ShapingFeatureFlags.ManualZwj); + } + + /// + protected override void AssignFeatures(ShapingBuffer buffer, int index, int count) + { + base.AssignFeatures(buffer, index, count); + + ArabicJoining.Apply(buffer, index, count, this.ScriptClass, this.Features); } /// - protected override void PlanFeatures(IGlyphShapingCollection collection, int index, int count) + public override void ReorderNormalizedMarks(ShapingBuffer buffer, int start, int end) { - 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); - - // HarfBuzz plans these as Arabic-script features, independently of the - // generic horizontal feature list. Horizontal runs already get them from - // DefaultShaper; forced vertical Arabic needs them here as well. - if (collection.TextOptions.LayoutMode.IsVertical()) + // Only the ordinary below and above classes receive the Arabic modifier + // treatment. Canonical ordering has already grouped equal classes stably. + int scan = start; + for (int order = BelowMarkOrder; order <= AboveMarkOrder; order += AboveMarkOrder - BelowMarkOrder) { - this.AddFeature(collection, index, count, CaltTag); - this.AddFeature(collection, index, count, LigaTag); - this.AddFeature(collection, index, count, CligTag); - } + while (scan < end && buffer[scan].MarkOrderingClass < order) + { + scan++; + } + + if (scan == end) + { + break; + } + + if (buffer[scan].MarkOrderingClass > order) + { + continue; + } + + int modifierEnd = scan; + while (modifierEnd < end + && buffer[modifierEnd].MarkOrderingClass == order + && UnicodeData.IsArabicModifierCombiningMark((uint)buffer[modifierEnd].CodePoint.Value)) + { + modifierEnd++; + } + + if (modifierEnd == scan) + { + // The modifier must lead its equal-class block. A later modifier is + // ordinary input here and canonical order remains unchanged. + continue; + } + + // The leading modifier block at this class belongs before every earlier + // mark in the run. Moving each record in turn preserves both blocks' + // internal order without allocating temporary storage. + int movedCount = modifierEnd - scan; + buffer.CombineInputStarts(start, modifierEnd); + for (int i = 0; i < movedCount; i++) + { + buffer.MoveGlyph(scan + i, start + i); + } + + // The moved marks must still sort before the ordinary Arabic classes + // during joiner handling and composition. Fallback positioning later + // folds these two temporary orders back to below and above geometry. + int reorderedOrder = order == BelowMarkOrder ? ReorderedBelowMarkOrder : ReorderedAboveMarkOrder; + int reorderedEnd = start + movedCount; + while (start < reorderedEnd) + { + buffer[start].MarkOrderOverride = reorderedOrder; + start++; + } - this.AddFeature(collection, index, count, MsetTag); + scan = modifierEnd; + } } /// - protected override void AssignFeatures(IGlyphShapingCollection collection, int index, int count) + public override void PostprocessGlyphs(ShapingBuffer buffer, int index, int count) { - base.AssignFeatures(collection, index, count); + int segmentEnd = index + count; + bool hasStretch = false; + + // Most runs do not use stretch decomposition. Avoid reversing or scanning + // their positioned records a second time. + for (int i = index; i < segmentEnd; i++) + { + hasStretch |= buffer[i].IsFixedStretch || buffer[i].IsRepeatingStretch; + } - int prev = -1; - int state = 0; - byte[] actions = new byte[count]; + if (!hasStretch) + { + return; + } + + bool rightToLeft = buffer[index].Direction == TextDirection.RightToLeft; - // Apply the state machine to map glyphs to features. - for (int i = 0; i < count; i++) + // Stretch geometry is computed in displayed order. The library keeps its + // shared shaping result in logical order, so reverse only this segment for + // the calculation and restore logical order when expansion is complete. + buffer.ReverseRange(index, segmentEnd); + + int addedGlyphs = 0; + int scan = segmentEnd; + while (scan > index) { - GlyphShapingData data = collection[i + index]; - ArabicJoiningClass joiningClass = CodePoint.GetArabicJoiningClass(data.CodePoint); - ArabicJoiningType joiningType = joiningClass.JoiningType; - if (joiningType == ArabicJoiningType.Transparent) + scan--; + if (!IsStretch(buffer[scan])) { - actions[i] = None; continue; } - int shapingClassIndex = GetShapingClassIndex(joiningType); - byte[] actionsWithState = StateTable[state, shapingClassIndex]; - byte prevAction = actionsWithState[0]; - byte curAction = actionsWithState[1]; - state = actionsWithState[2]; + // One decomposition is a contiguous alternating run of fixed and + // repeating tiles. Its original advances determine how much of the + // covered word can be filled without copying. + int stretchEnd = scan + 1; + int fixedWidth = 0; + int repeatingWidth = 0; + int fixedCount = 0; + int repeatingCount = 0; + while (scan >= index && IsStretch(buffer[scan])) + { + int width = buffer.MetricsAt(scan).Metrics.AdvanceWidth; + if (buffer[scan].IsFixedStretch) + { + fixedWidth += width; + fixedCount++; + } + else + { + repeatingWidth += width; + repeatingCount++; + } + + scan--; + } + + // The stretch covers the adjacent word preceding the tile run in + // displayed order. Punctuation and separators terminate the context; + // positioning adjustments are included in the available width. + int stretchStart = scan + 1; + int context = stretchStart; + int availableWidth = 0; + while (context > index && !IsStretch(buffer[context - 1]) && IsStretchContext(buffer[context - 1])) + { + context--; + ref ShapingBuffer.GlyphMetricsEntry contextMetrics = ref buffer.MetricsAt(context); + ref GlyphShapingPosition contextPosition = ref buffer.PositionAt(context); + availableWidth += contextMetrics.GetAdvanceWidth(in contextPosition); + } + + // Fixed tiles are emitted once. Fill the remaining width with complete + // copies of the repeating pattern, leaving any residual width to center + // the result over the covered word. + int remainingWidth = availableWidth - fixedWidth; + int repeatCopies = 0; + if (remainingWidth > repeatingWidth && repeatingWidth > 0) + { + repeatCopies = (remainingWidth / repeatingWidth) - 1; + } + + // When another complete pattern would overrun the target, permit the + // repeated tiles to overlap evenly. This produces a closer fit than + // leaving an uncovered shortfall. + int repeatOverlap = 0; + int shortfall = remainingWidth - (repeatingWidth * (repeatCopies + 1)); + if (shortfall > 0 && repeatingCount > 0) + { + repeatCopies++; + int excess = ((repeatCopies + 1) * repeatingWidth) - remainingWidth; + if (excess > 0) + { + repeatOverlap = excess / (repeatCopies * repeatingCount); + remainingWidth = 0; + } + } - if (prevAction != None && prev != -1) + // Malformed fonts must not expand one decomposition without bound. The + // limit includes both original tiles and every inserted repeat. + int baseGlyphs = fixedCount + repeatingCount; + int maximumCopies = repeatingCount > 0 && baseGlyphs < MaximumStretchGlyphs + ? (MaximumStretchGlyphs - baseGlyphs) / repeatingCount + : 0; + repeatCopies = Math.Min(repeatCopies, maximumCopies); + + // Half of any uncovered width is left on each side. Every tile becomes + // zero-advance and is placed explicitly within that centered span. + int xOffset = remainingWidth / 2; + for (int tile = stretchEnd - 1; tile >= stretchStart; tile--) { - actions[prev] = prevAction; + ref GlyphShapingData tileData = ref buffer[tile]; + ShapingBuffer.GlyphMetricsEntry tileMetrics = buffer.MetricsAt(tile); + GlyphShapingPosition tilePosition = buffer.PositionAt(tile); + int width = tileMetrics.Metrics.AdvanceWidth; + int repetitions = tileData.IsRepeatingStretch ? repeatCopies + 1 : 1; + + // Copies are inserted at the same index. Each later insertion + // therefore lands before the preceding copy, matching the reverse + // write order used to preserve the tile sequence. + for (int repetition = 0; repetition < repetitions; repetition++) + { + if (rightToLeft) + { + xOffset -= width; + if (repetition > 0) + { + xOffset += repeatOverlap; + } + } + + tilePosition.Bounds.X = xOffset; + tilePosition.Bounds.Width = 0; + if (repetition == 0) + { + // Reuse the original record for the first copy so its text + // identity and resolved metrics remain authoritative. + buffer.PositionAt(tile) = tilePosition; + } + else + { + buffer.InsertPositioned(tile, in tileData, in tileMetrics, in tilePosition); + addedGlyphs++; + } + + if (!rightToLeft) + { + xOffset += width; + if (repetition > 0) + { + xOffset -= repeatOverlap; + } + } + } } + } + + buffer.ReverseRange(index, segmentEnd + addedGlyphs); + } - actions[i] = curAction; - prev = i; + /// + /// Records the alternating fixed and repeating pieces emitted by the stretch + /// feature. Only substitution creates these pieces; the same stage action is + /// also visible to positioning because both tables share stage boundaries. + /// + /// The shaping plan. + /// The shaping buffer. + /// The zero-based index of the first record. + /// The number of records in the segment. + private void RecordStretchParts(ShapePlan plan, ShapingBuffer buffer, int index, int count) + { + if (!this.hasStretchFeature || buffer.Role != ShapingBufferRole.Substitution) + { + return; } - // Apply the chosen features to their respective glyphs. - for (int i = 0; i < actions.Length; i++) + int end = index + count; + for (int i = index; i < end; i++) { - switch (actions[i]) + ref GlyphShapingData data = ref buffer[i]; + if (!data.IsDecomposed) { - 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; + continue; } + + // Multiple substitution numbers its output components from zero. Even + // components are fixed end pieces; odd components form the repeatable + // interior pattern. + bool repeating = (data.LigatureComponent & 1) != 0; + data.IsFixedStretch = !repeating; + data.IsRepeatingStretch = repeating; + } + } + + /// + /// Determines whether a record is one of the tiles produced by stretch + /// decomposition. + /// + /// The shaping record. + /// for a fixed or repeating tile. + private static bool IsStretch(GlyphShapingData data) => data.IsFixedStretch || data.IsRepeatingStretch; + + /// + /// Determines whether a record contributes advance to the word covered by a + /// stretch decomposition. + /// + /// The shaping record. + /// when the record belongs to the covered word. + private static bool IsStretchContext(GlyphShapingData data) + { + if (data.IsDefaultIgnorable) + { + return true; } + + // This deliberately matches the word categories used by stretch + // justification. In particular, Arabic and Syriac letters are OtherLetter; + // broadening this to every Unicode letter category would cross context that + // the feature does not cover. + UnicodeCategory category = CodePoint.GetGeneralCategory(data.CodePoint); + return category is UnicodeCategory.OtherNotAssigned + or UnicodeCategory.PrivateUse + or UnicodeCategory.ModifierLetter + or UnicodeCategory.OtherLetter + or UnicodeCategory.SpacingCombiningMark + or UnicodeCategory.EnclosingMark + or UnicodeCategory.NonSpacingMark + or UnicodeCategory.DecimalDigitNumber + or UnicodeCategory.LetterNumber + or UnicodeCategory.OtherNumber + or UnicodeCategory.CurrencySymbol + or UnicodeCategory.ModifierSymbol + or UnicodeCategory.MathSymbol + or UnicodeCategory.OtherSymbol; } /// - /// Maps an Arabic joining type to the corresponding column index in the state table. + /// Separates joining-form features into distinct substitution stages. /// - /// The Arabic joining type. - /// The state table column index. - private static int GetShapingClassIndex(ArabicJoiningType joiningType) => joiningType switch + /// The shaping plan. + /// The shaping buffer. + /// The zero-based index of the first record. + /// The number of records in the segment. + private static void Pause(ShapePlan plan, ShapingBuffer buffer, int index, int count) { - ArabicJoiningType.NonJoining => 0, - ArabicJoiningType.LeftJoining => 1, - ArabicJoiningType.RightJoining => 2, - ArabicJoiningType.DualJoining or ArabicJoiningType.JoinCausing => 3, - - // TODO: ALAPH: 4 - // TODO: DALATH RISH': 5 - ArabicJoiningType.Transparent => 6, - _ => 0, - }; + } + + /// + /// Applies presentation-form fallback when all four Arabic joining-form features are absent. + /// + /// The shaping plan. + /// The shaping buffer. + /// The zero-based index of the first record. + /// The number of records in the segment. + private void ApplyFallback(ShapePlan plan, ShapingBuffer buffer, int index, int count) + { + if (this.ScriptClass != ScriptClass.Arabic + || plan.TryGetGSubFeatureLookups(in IsolTag, out _) + || plan.TryGetGSubFeatureLookups(in FinaTag, out _) + || plan.TryGetGSubFeatureLookups(in MediTag, out _) + || plan.TryGetGSubFeatureLookups(in InitTag, out _)) + { + return; + } + + this.fallbackSubstitutions ??= ArabicFallbackSubstitutions.Create(plan.FontMetrics); + this.fallbackSubstitutions.Apply(plan, buffer, index, count, InitTag, MediTag, FinaTag, IsolTag, RligTag); + } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/BaseShaper.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/BaseShaper.cs index 9e3ca8c67..b12152610 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/BaseShaper.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/BaseShaper.cs @@ -11,6 +11,60 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; /// internal abstract class BaseShaper { + /// + /// Gets the feature bit assignment for the plan this shaper belongs to. The + /// shaper creates it and the owning plan adopts it, so both hold the same + /// non-null instance for their whole lifetime; a shaper exists only inside a + /// plan, so feature state is never reachable without one. + /// + public ShapePlanFeatures Features { get; } = new(); + + /// + /// Gets or sets the user-specified feature tags for the shaping segments + /// owned by this plan. + /// + public IReadOnlyList FeatureTags { get; set; } = Array.Empty(); + + /// + /// Gets a value indicating whether the first planning pass has collected the + /// plan's feature registrations. Registration is deterministic for a plan's + /// identity, so later passes replay the collected masks instead of + /// re-registering. + /// + public bool FeaturesCollected { get; private set; } + + /// + /// Gets or sets a value indicating whether the first planning pass is + /// currently collecting registrations; whole-segment feature additions fold + /// their masks only while this is set. Shapers clear it around additions + /// whose ranges depend on the text, such as direction spans. + /// + public bool CollectingFeatures { get; protected set; } + + /// + /// Gets or sets the registered-mask fold of every whole-segment feature the + /// collecting pass added, replayed over later segments in one walk. + /// + public uint FoldedRegisteredMask { get; protected set; } + + /// + /// Gets or sets the enabled-mask fold paired with + /// . + /// + public uint FoldedEnabledMask { get; protected set; } + + /// + /// Gets the segment start the collecting pass is planning; feature additions + /// fold only when their range is exactly the planned segment. + /// + public int CollectSegmentIndex { get; private set; } + + /// + /// Gets the segment length the collecting pass is planning, tracking count + /// adjustments between phases. + /// + public int CollectSegmentCount { get; private set; } + /// /// Gets or sets the script classification for this shaper. /// @@ -22,78 +76,206 @@ internal abstract class BaseShaper public MarkZeroingMode MarkZeroingMode { get; protected set; } /// - /// Assigns the features to each glyph within the collection. + /// Gets or sets a value indicating whether marks are positioned from glyph extents when no positioning table is available. + /// + public bool FallbackMarkPositioning { get; protected set; } + + /// + /// Gets or sets how far the text is taken apart and put back together before it + /// is shaped. Defaults to joining the marks the font offers joined forms for, + /// which is what a script without an engine of its own wants. + /// + public NormalizationMode NormalizationMode { get; protected set; } = NormalizationMode.ComposedDiacritics; + + /// + /// Takes a character apart into the pair it is canonically equivalent to. A + /// script whose characters spell a sound the font draws in pieces overrides this + /// to hand those pieces back. + /// + /// The character to take apart. + /// When this method returns, contains the leading character. + /// + /// When this method returns, contains the trailing character, or the default when + /// the character stands for a single other one. + /// + /// when the character comes apart. + public virtual bool TryDecompose(CodePoint codePoint, out CodePoint first, out CodePoint second) + => CodePoint.TryDecompose(codePoint, out first, out second); + + /// + /// Joins a pair of characters into the single character they are canonically + /// equivalent to. A script that must not have two of its marks joined back + /// together overrides this to refuse. + /// + /// The leading character. + /// The trailing character. + /// When this method returns, contains the joined character. + /// when the pair joins. + public virtual bool TryCompose(CodePoint first, CodePoint second, out CodePoint composed) + => CodePoint.TryCompose(first, second, out composed); + + /// + /// Applies script-specific ordering within a canonically ordered mark run. + /// + /// The glyph shaping buffer. + /// The zero-based index of the first mark. + /// The exclusive end index of the mark run. + public virtual void ReorderNormalizedMarks(ShapingBuffer buffer, int start, int end) + { + } + + /// + /// Applies script-specific expansion after positioning has completed. The + /// default shaper leaves the positioned records unchanged. + /// + /// The positioned shaping buffer. + /// The zero-based index of the first record. + /// The number of records in the segment. + public virtual void PostprocessGlyphs(ShapingBuffer buffer, int index, int count) + { + } + + /// + /// Assigns the features to each glyph within the buffer. /// - /// The glyph shaping collection. + /// The font metrics, which decide what the font can draw. + /// The glyph shaping buffer. /// The zero-based index of the elements to assign. /// The number of elements to assign. - public void Plan(IGlyphShapingCollection collection, int index, int count) + public void Plan(FontMetrics fontMetrics, ShapingBuffer buffer, int index, int count) { - int collectionCount = collection.Count; + // The text is prepared before any feature is planned, so every later + // stage sees the records this step inserts. It reads the text alone, so + // it runs once, on the pass that substitutes. + if (buffer.Role == ShapingBufferRole.Substitution) + { + int preprocessCount = buffer.Count; + + this.PreprocessText(buffer, index, count); + + count += buffer.Count - preprocessCount; - this.PlanPreprocessingFeatures(collection, index, count); + // Normalizing settles which characters the run is spelled with, so it + // runs before any feature is planned against them, and once. + count += TextNormalizer.Normalize(this, fontMetrics, buffer, index, count); + } - RecalculateCount(collection, ref collectionCount, ref count); + // Registration is deterministic for a plan's identity, so the first pass + // collects it once: feature bits, stages, joiner flags, and the fold of + // every whole-segment mask. Later passes replay the fold in one walk and + // run only the per-text work. A mixed-vertical layout decides features + // from the glyphs themselves, so it re-plans every pass. + if (!this.FeaturesCollected) + { + int collectionCount = buffer.Count; + this.CollectingFeatures = true; + this.CollectSegmentIndex = index; + this.CollectSegmentCount = count; - this.PlanFeatures(collection, index, count); + this.PlanPreprocessingFeatures(buffer, index, count); - RecalculateCount(collection, ref collectionCount, ref count); + RecalculateCount(buffer, ref collectionCount, ref count); + this.CollectSegmentCount = count; - this.PlanPostprocessingFeatures(collection, index, count); + this.PlanFeatures(buffer, index, count); - RecalculateCount(collection, ref collectionCount, ref count); + RecalculateCount(buffer, ref collectionCount, ref count); + this.CollectSegmentCount = count; - this.AssignFeatures(collection, index, count); + this.PlanPostprocessingFeatures(buffer, index, count); + + RecalculateCount(buffer, ref collectionCount, ref count); + + this.CollectingFeatures = false; + this.FeaturesCollected = !buffer.TextOptions.LayoutMode.IsVerticalMixed(); + } + else + { + if (this.FoldedRegisteredMask != 0) + { + buffer.AddShapingFeatureMasks(index, count, this.FoldedRegisteredMask, this.FoldedEnabledMask); + } + } + + this.SetupMasks(buffer, index, count); + + this.AssignFeatures(buffer, index, count); + } + + /// + /// Prepares the text of the segment before any feature is planned. A script + /// whose text can spell something it must not be read as separates the + /// characters here, where the records still stand as the text wrote them. + /// + /// The glyph shaping buffer. + /// The zero-based index of the first record. + /// The number of records. + protected virtual void PreprocessText(ShapingBuffer buffer, int index, int count) + { + } + + /// + /// Applies the per-text feature masks that cannot fold into the collected + /// whole-segment masks, such as spans that follow the resolved direction of + /// the records. Runs on every planning pass, after collection or replay. + /// + /// The glyph shaping buffer. + /// The zero-based index of the elements to assign. + /// The number of elements to assign. + protected virtual void SetupMasks(ShapingBuffer buffer, int index, int count) + { } /// - /// Assigns the features to each glyph within the collection. + /// Assigns the features to each glyph within the buffer. /// - /// The glyph shaping collection. + /// The glyph shaping buffer. /// The zero-based index of the elements to assign. /// The number of elements to assign. - protected abstract void PlanFeatures(IGlyphShapingCollection collection, int index, int count); + protected abstract void PlanFeatures(ShapingBuffer buffer, int index, int count); /// - /// Assigns the preprocessing features to each glyph within the collection. + /// Assigns the preprocessing features to each glyph within the buffer. /// - /// The glyph shaping collection. + /// The glyph shaping buffer. /// The zero-based index of the elements to assign. /// The number of elements to assign. - protected abstract void PlanPreprocessingFeatures(IGlyphShapingCollection collection, int index, int count); + protected abstract void PlanPreprocessingFeatures(ShapingBuffer buffer, int index, int count); /// - /// Assigns the postprocessing features to each glyph within the collection. + /// Assigns the postprocessing features to each glyph within the buffer. /// - /// The glyph shaping collection. + /// The glyph shaping buffer. /// The zero-based index of the elements to assign. /// The number of elements to assign. - protected abstract void PlanPostprocessingFeatures(IGlyphShapingCollection collection, int index, int count); + protected abstract void PlanPostprocessingFeatures(ShapingBuffer buffer, int index, int count); /// - /// Assigns the shaper specific substitution features to each glyph within the collection. + /// Assigns the shaper specific substitution features to each glyph within the buffer. /// - /// The glyph shaping collection. + /// The glyph shaping buffer. /// The zero-based index of the elements to assign. /// The number of elements to assign. - protected abstract void AssignFeatures(IGlyphShapingCollection collection, int index, int count); + protected abstract void AssignFeatures(ShapingBuffer buffer, int index, int count); /// - /// Gets the ordered collection of shaping stages for this shaper. + /// Gets the ordered buffer of shaping stages for this shaper. The concrete + /// list type lets the per-section stage walk enumerate without interface + /// dispatch or a boxed enumerator. /// /// The shaping stages. - public abstract IEnumerable GetShapingStages(); + public abstract List GetShapingStages(); /// - /// Recalculates the count when the collection size changes during shaping. + /// Recalculates the count when the buffer size changes during shaping. /// - /// The glyph shaping collection. - /// The previous collection count, updated to the current count. + /// The glyph shaping buffer. + /// The previous buffer count, updated to the current count. /// The element count, adjusted by the size delta. - private static void RecalculateCount(IGlyphShapingCollection collection, ref int oldCount, ref int count) + private static void RecalculateCount(ShapingBuffer buffer, ref int oldCount, ref int count) { - // If the collection has changed size we need to recalculate the count. - int delta = collection.Count - oldCount; + // If the buffer has changed size we need to recalculate the count. + int delta = buffer.Count - oldCount; count += delta; oldCount += delta; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/DefaultShaper.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/DefaultShaper.cs index 6194f4b25..17aba787f 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/DefaultShaper.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/DefaultShaper.cs @@ -11,90 +11,162 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; /// internal class DefaultShaper : BaseShaper { - /// The 'rvrn' (required variation alternates) feature tag. + /// + /// The 'rvrn' (required variation alternates) feature tag. + /// protected static readonly Tag RvnrTag = Tag.Parse("rvrn"); - /// The 'ltra' (left-to-right alternates) feature tag. + /// + /// The random alternate feature tag. + /// + protected static readonly Tag RandTag = Tag.Parse("rand"); + + /// + /// The 'ltra' (left-to-right alternates) feature tag. + /// protected static readonly Tag LtraTag = Tag.Parse("ltra"); - /// The 'ltrm' (left-to-right mirrored forms) feature tag. + /// + /// The 'ltrm' (left-to-right mirrored forms) feature tag. + /// protected static readonly Tag LtrmTag = Tag.Parse("ltrm"); - /// The 'rtla' (right-to-left alternates) feature tag. + /// + /// The 'rtla' (right-to-left alternates) feature tag. + /// protected static readonly Tag RtlaTag = Tag.Parse("rtla"); - /// The 'rtlm' (right-to-left mirrored forms) feature tag. + /// + /// The 'rtlm' (right-to-left mirrored forms) feature tag. + /// protected static readonly Tag RtlmTag = Tag.Parse("rtlm"); - /// The 'frac' (fractions) feature tag. + /// + /// The 'frac' (fractions) feature tag. + /// protected static readonly Tag FracTag = Tag.Parse("frac"); - /// The 'numr' (numerators) feature tag. + /// + /// The 'numr' (numerators) feature tag. + /// protected static readonly Tag NumrTag = Tag.Parse("numr"); - /// The 'dnom' (denominators) feature tag. + /// + /// The 'dnom' (denominators) feature tag. + /// protected static readonly Tag DnomTag = Tag.Parse("dnom"); - /// The 'ccmp' (glyph composition/decomposition) feature tag. + /// + /// The 'ccmp' (glyph composition/decomposition) feature tag. + /// protected static readonly Tag CcmpTag = Tag.Parse("ccmp"); - /// The 'locl' (localized forms) feature tag. + /// + /// The 'locl' (localized forms) feature tag. + /// protected static readonly Tag LoclTag = Tag.Parse("locl"); - /// The 'rlig' (required ligatures) feature tag. + /// + /// The 'rlig' (required ligatures) feature tag. + /// protected static readonly Tag RligTag = Tag.Parse("rlig"); - /// The 'mark' (mark positioning) feature tag. + /// + /// The 'mark' (mark positioning) feature tag. + /// protected static readonly Tag MarkTag = Tag.Parse("mark"); - /// The 'mkmk' (mark-to-mark positioning) feature tag. + /// + /// The 'mkmk' (mark-to-mark positioning) feature tag. + /// protected static readonly Tag MkmkTag = Tag.Parse("mkmk"); - /// The 'calt' (contextual alternates) feature tag. + /// + /// The 'abvm' (above-base mark positioning) feature tag. + /// + protected static readonly Tag AbvmTag = Tag.Parse("abvm"); + + /// + /// The 'blwm' (below-base mark positioning) feature tag. + /// + protected static readonly Tag BlwmTag = Tag.Parse("blwm"); + + /// + /// The 'calt' (contextual alternates) feature tag. + /// protected static readonly Tag CaltTag = Tag.Parse("calt"); - /// The 'clig' (contextual ligatures) feature tag. + /// + /// The 'clig' (contextual ligatures) feature tag. + /// protected static readonly Tag CligTag = Tag.Parse("clig"); - /// The 'liga' (standard ligatures) feature tag. + /// + /// The 'liga' (standard ligatures) feature tag. + /// protected static readonly Tag LigaTag = Tag.Parse("liga"); - /// The 'rclt' (required contextual alternates) feature tag. + /// + /// The 'rclt' (required contextual alternates) feature tag. + /// protected static readonly Tag RcltTag = Tag.Parse("rclt"); - /// The 'curs' (cursive positioning) feature tag. + /// + /// The 'curs' (cursive positioning) feature tag. + /// protected static readonly Tag CursTag = Tag.Parse("curs"); - /// The 'kern' (kerning) feature tag. + /// + /// The 'dist' (distances) feature tag. + /// + protected static readonly Tag DistTag = Tag.Parse("dist"); + + /// + /// The 'kern' (kerning) feature tag. + /// protected static readonly Tag KernTag = Tag.Parse("kern"); - /// The 'vert' (vertical alternates) feature tag. + /// + /// The 'vert' (vertical alternates) feature tag. + /// protected static readonly Tag VertTag = Tag.Parse("vert"); - /// The 'vkrn' (vertical kerning) feature tag. + /// + /// The 'vkrn' (vertical kerning) feature tag. + /// protected static readonly Tag VKernTag = Tag.Parse("vkrn"); - /// The fraction slash code point (U+2044). + /// + /// The fraction slash code point (U+2044), the only code point that forms + /// a fraction from the digit runs surrounding it. + /// private static readonly CodePoint FractionSlash = new(0x2044); - /// The solidus (slash) code point (U+002F). - private static readonly CodePoint Slash = new(0x002F); - - /// The set of shaping stages accumulated during feature planning. - private readonly HashSet shapingStages = []; + /// + /// The shaping stages accumulated during feature planning, in registration order. + /// Stage counts are small (≤ ~16), so duplicate suppression scans the list by tag; + /// this keeps application order deterministic and avoids per-call hashing. + /// + private readonly List shapingStages = new(16); - /// The kerning mode from the text options. + /// + /// The kerning mode from the text options. + /// private readonly KerningMode kerningMode; - /// The user-specified feature tags from the text options. - private readonly IReadOnlyList featureTags; + /// + /// Whether layout will add tracking after shaping. Optional ligatures and + /// contextual alternates must stay off so spacing can be applied at every + /// grapheme boundary. + /// + private readonly bool hasTracking; /// /// Initializes a new instance of the class with PostGpos mark zeroing. /// /// The script classification. /// The text options. - internal DefaultShaper(ScriptClass script, TextOptions textOptions) + public DefaultShaper(ScriptClass script, TextOptions textOptions) : this(script, MarkZeroingMode.PostGpos, textOptions) { } @@ -109,57 +181,160 @@ protected DefaultShaper(ScriptClass script, MarkZeroingMode markZeroingMode, Tex { this.ScriptClass = script; this.MarkZeroingMode = markZeroingMode; + this.FallbackMarkPositioning = true; this.kerningMode = textOptions.KerningMode; - this.featureTags = textOptions.FeatureTags; + this.hasTracking = textOptions.Tracking != 0; } /// - protected override void PlanFeatures(IGlyphShapingCollection collection, int index, int count) + protected override void PlanFeatures(ShapingBuffer buffer, int index, int count) { } /// - protected override void PlanPreprocessingFeatures(IGlyphShapingCollection collection, int index, int count) + protected override void PlanPreprocessingFeatures(ShapingBuffer buffer, int index, int count) { // Add variation Features. - this.AddFeature(collection, index, count, RvnrTag); + this.EnableFeature(buffer, index, count, RvnrTag); - // Add directional features. - for (int i = index; i < index + count; i++) + this.AddDirectionalFeatures(buffer, index, count); + this.EnableFeature(buffer, index, count, RandTag, ShapingFeatureFlags.Random); + } + + /// + protected override void SetupMasks(ShapingBuffer buffer, int index, int count) + { + this.AddDirectionalFeatures(buffer, index, count); + + if (buffer.HasFractionSlash) + { + this.SetupFractionMasks(buffer, index, count); + } + } + + /// + /// Turns on the fraction features over the digit runs a fraction slash + /// joins: the numerator before it, the denominator after it, and the slash + /// itself. A slash without digits on both sides forms no fraction. The + /// numerator and denominator features swap roles for right-to-left text, + /// where the leading run is the denominator. + /// + /// The glyph shaping buffer. + /// The zero-based index of the first element. + /// The number of elements. + private void SetupFractionMasks(ShapingBuffer buffer, int index, int count) + { + uint fracMask = this.Features.GetOrAddMask(FracTag); + uint numrMask = this.Features.GetOrAddMask(NumrTag); + uint dnomMask = this.Features.GetOrAddMask(DnomTag); + + int end = index + count; + for (int i = index; i < end; i++) + { + if (buffer[i].CodePoint != FractionSlash) + { + continue; + } + + int runStart = i; + while (runStart > index && CodePoint.IsDigit(buffer[runStart - 1].CodePoint)) + { + runStart--; + } + + int runEnd = i + 1; + while (runEnd < end && CodePoint.IsDigit(buffer[runEnd].CodePoint)) + { + runEnd++; + } + + // A slash missing digits on either side is just a slash. + if (runStart == i || runEnd == i + 1) + { + continue; + } + + bool leftToRight = buffer[i].Direction != TextDirection.RightToLeft; + uint preMask = leftToRight ? numrMask | fracMask : fracMask | dnomMask; + uint postMask = leftToRight ? fracMask | dnomMask : numrMask | fracMask; + + for (int j = runStart; j < i; j++) + { + buffer.EnableShapingFeature(j, preMask); + } + + buffer.EnableShapingFeature(i, fracMask); + + for (int j = i + 1; j < runEnd; j++) + { + buffer.EnableShapingFeature(j, postMask); + } + + i = runEnd - 1; + } + } + + /// + /// Adds the directional features once per direction span. A segment may span + /// direction runs, so these stay varying features whose bits cover only + /// their own span; a global classification would let one direction's + /// lookups match glyphs of the other. Their masks depend on each text's + /// resolved directions, so they never fold and re-apply on every pass. + /// + /// The glyph shaping buffer. + /// The zero-based index of the first element. + /// The number of elements. + private void AddDirectionalFeatures(ShapingBuffer buffer, int index, int count) + { + bool collecting = this.CollectingFeatures; + this.CollectingFeatures = false; + + int end = index + count; + int spanStart = index; + while (spanStart < end) { - GlyphShapingData shapingData = collection[i]; + TextDirection direction = buffer[spanStart].Direction; + int spanEnd = spanStart + 1; + while (spanEnd < end && buffer[spanEnd].Direction == direction) + { + spanEnd++; + } - if (shapingData.Direction == TextDirection.LeftToRight) + int spanCount = spanEnd - spanStart; + if (direction == TextDirection.LeftToRight) { - this.AddFeature(collection, i, 1, LtraTag); - this.AddFeature(collection, i, 1, LtrmTag); + this.AddFeature(buffer, spanStart, spanCount, LtraTag); + this.AddFeature(buffer, spanStart, spanCount, LtrmTag); } else { - this.AddFeature(collection, i, 1, RtlaTag); - this.AddFeature(collection, i, 1, RtlmTag); + this.AddFeature(buffer, spanStart, spanCount, RtlaTag); + this.AddFeature(buffer, spanStart, spanCount, RtlmTag); } + + spanStart = spanEnd; } - // TODO: Fractional feature should be assigned here but disabled. - // They should then be enabled in AssignFeatures. + this.CollectingFeatures = collecting; } /// - protected override void PlanPostprocessingFeatures(IGlyphShapingCollection collection, int index, int count) + protected override void PlanPostprocessingFeatures(ShapingBuffer buffer, int index, int count) { // Add common features. - this.AddFeature(collection, index, count, CcmpTag); - this.AddFeature(collection, index, count, LoclTag); - this.AddFeature(collection, index, count, RligTag); - this.AddFeature(collection, index, count, MarkTag); - this.AddFeature(collection, index, count, MkmkTag); - - LayoutMode layoutMode = collection.TextOptions.LayoutMode; + this.EnableFeature(buffer, index, count, AbvmTag); + this.EnableFeature(buffer, index, count, BlwmTag); + this.EnableFeature(buffer, index, count, CcmpTag); + this.EnableFeature(buffer, index, count, LoclTag); + this.EnableFeature(buffer, index, count, RligTag); + this.EnableFeature(buffer, index, count, MarkTag, ShapingFeatureFlags.ManualJoiners); + this.EnableFeature(buffer, index, count, MkmkTag, ShapingFeatureFlags.ManualJoiners); + + LayoutMode layoutMode = buffer.TextOptions.LayoutMode; bool isVerticalLayout = false; for (int i = index; i < index + count; i++) { - GlyphShapingData shapingData = collection[i]; + ref GlyphShapingData shapingData = ref buffer[i]; isVerticalLayout |= AdvancedTypographicUtils.IsVerticalGlyph(shapingData.CodePoint, layoutMode); } @@ -167,12 +342,21 @@ protected override void PlanPostprocessingFeatures(IGlyphShapingCollection colle if (!isVerticalLayout) { // Add horizontal features. - this.AddFeature(collection, index, count, CaltTag); - this.AddFeature(collection, index, count, CligTag); - this.AddFeature(collection, index, count, LigaTag); - this.AddFeature(collection, index, count, RcltTag); - this.AddFeature(collection, index, count, CursTag); - this.AddFeature(collection, index, count, KernTag); + if (!this.hasTracking) + { + // Browsers disable these optional substitutions for non-zero letter + // spacing because they can erase boundaries where spacing is applied. + // Mandatory script shaping stays enabled, and an explicit feature + // below can opt an optional substitution in. + this.EnableFeature(buffer, index, count, CaltTag); + this.EnableFeature(buffer, index, count, CligTag); + this.EnableFeature(buffer, index, count, LigaTag); + } + + this.EnableFeature(buffer, index, count, RcltTag); + this.EnableFeature(buffer, index, count, CursTag); + this.EnableFeature(buffer, index, count, DistTag); + this.EnableFeature(buffer, index, count, KernTag); } else { @@ -184,36 +368,44 @@ protected override void PlanPostprocessingFeatures(IGlyphShapingCollection colle // matter which script/langsys it is listed (or not) under. // See various bugs referenced from: // https://github.com/harfbuzz/harfbuzz/issues/63 - this.AddFeature(collection, index, count, VertTag); + this.EnableFeature(buffer, index, count, VertTag); } - // Add user defined features. - foreach (Tag feature in this.featureTags) + // Register the fraction trio without enabling it: the features exist + // for every plan, and each text turns them on over the digit runs that + // surround a fraction slash. A font without them has no lookups to + // apply, so registration alone costs nothing. + this.AddFeature(buffer, index, count, FracTag, false); + this.AddFeature(buffer, index, count, NumrTag, false); + this.AddFeature(buffer, index, count, DnomTag, false); + + // Apply user features after defaults so an explicit tag can restore an + // optional feature that tracking disabled above. + foreach (Tag feature in this.FeatureTags) { - // We've already dealt with fractional features. - if (feature != FracTag && feature != NumrTag && feature != DnomTag) + if (feature == FracTag || feature == NumrTag || feature == DnomTag) + { + // The trio is registered off for automatic fraction forming; an + // explicit request turns it on over the whole segment, so the + // font's own fraction lookups apply wherever they match. + this.AddFeature(buffer, index, count, feature, true); + } + else { - this.AddFeature(collection, index, count, feature); + this.EnableFeature(buffer, index, count, feature); } } } /// - protected override void AssignFeatures(IGlyphShapingCollection collection, int index, int count) + protected override void AssignFeatures(ShapingBuffer buffer, int index, int count) { - // TODO: We shouldn't be relying on the feature list - // User defined fractional features require special treatment. - // https://docs.microsoft.com/en-us/typography/opentype/spec/features_fj#tag-frac - if (this.HasFractions()) - { - this.AssignFractionalFeatures(collection, index, count); - } } /// - /// Adds a shaping feature to the specified range of glyphs in the collection and registers the corresponding shaping stage. + /// Adds a shaping feature to the specified range of glyphs in the buffer and registers the corresponding shaping stage. /// - /// The glyph shaping collection. + /// The glyph shaping buffer. /// The zero-based index of the first element. /// The number of elements. /// The feature tag to add. @@ -221,13 +413,13 @@ protected override void AssignFeatures(IGlyphShapingCollection collection, int i /// An optional action to invoke before the feature is applied. /// An optional action to invoke after the feature is applied. protected void AddFeature( - IGlyphShapingCollection collection, + ShapingBuffer buffer, int index, int count, Tag feature, bool enabled = true, - Action? preAction = null, - Action? postAction = null) + Action? preAction = null, + Action? postAction = null) { if (this.kerningMode == KerningMode.None) { @@ -237,100 +429,158 @@ protected void AddFeature( } } - int end = index + count; - for (int i = index; i < end; i++) + uint mask = this.Features.GetOrAddMask(feature); + + // A whole-segment addition during the collecting pass folds into the + // masks the replay pass applies in one walk. + if (this.CollectingFeatures && index == this.CollectSegmentIndex && count == this.CollectSegmentCount) { - collection.AddShapingFeature(i, new TagEntry(feature, enabled)); + this.FoldedRegisteredMask |= mask; + if (enabled) + { + this.FoldedEnabledMask |= mask; + } } - this.shapingStages.Add(new ShapingStage(feature, preAction, postAction)); + buffer.AddShapingFeatureRange(index, count, new TagEntry(feature, enabled), mask); + this.AddStage(feature, preAction, postAction); } - /// - public override IEnumerable GetShapingStages() => this.shapingStages; + /// + /// Adds a varying shaping feature with registration flags controlling its + /// lookups' joiner handling and syllable scope. + /// + /// The glyph shaping buffer. + /// The zero-based index of the first element. + /// The number of elements. + /// The feature tag to add. + /// The registration flags. + /// Whether the feature is initially enabled. + /// The action to invoke before the feature is applied, or . + /// The action to invoke after the feature is applied, or . + protected void AddFeature( + ShapingBuffer buffer, + int index, + int count, + Tag feature, + ShapingFeatureFlags flags, + bool enabled, + Action? preAction, + Action? postAction) + { + this.Features.AddFlags(feature, flags); + this.AddFeature(buffer, index, count, feature, enabled, preAction, postAction); + } + + /// + /// Registers a global feature over the given range: one that applies to every + /// glyph of the plan's segments and therefore shares the plan's single global + /// mask bit instead of consuming a distinct bit. Features whose per-glyph + /// state varies register through the add-feature overloads instead. + /// + /// The glyph shaping buffer. + /// The zero-based index of the first element. + /// The number of elements. + /// The feature tag to enable. + protected void EnableFeature(ShapingBuffer buffer, int index, int count, Tag feature) + => this.EnableFeature(buffer, index, count, feature, null, null); /// - /// Assigns fractional feature tags (numerator, denominator, fraction) to glyphs forming fraction sequences. + /// Registers a global feature with registration flags controlling its lookups' + /// joiner handling and syllable scope. /// - /// The glyph shaping collection. + /// The glyph shaping buffer. /// The zero-based index of the first element. /// The number of elements. - private void AssignFractionalFeatures(IGlyphShapingCollection collection, int index, int count) + /// The feature tag to enable. + /// The registration flags. + protected void EnableFeature(ShapingBuffer buffer, int index, int count, Tag feature, ShapingFeatureFlags flags) { - // Enable contextual fractions. - for (int i = index; i < index + count; i++) - { - GlyphShapingData shapingData = collection[i]; - if (shapingData.CodePoint == FractionSlash || shapingData.CodePoint == Slash) - { - int start = i; - int end = i + 1; - - // Apply numerator. - if (start > 0) - { - shapingData = collection[start - 1]; - while (start > 0 && CodePoint.IsDigit(shapingData.CodePoint)) - { - this.AddFeature(collection, start - 1, 1, NumrTag); - this.AddFeature(collection, start - 1, 1, FracTag); - start--; - } - } - - // Apply denominator. - if (end < collection.Count) - { - shapingData = collection[end]; - while (end < collection.Count && CodePoint.IsDigit(shapingData.CodePoint)) - { - this.AddFeature(collection, end, 1, DnomTag); - this.AddFeature(collection, end, 1, FracTag); - end++; - } - } - - // Apply fraction slash. - this.AddFeature(collection, i, 1, FracTag); - i = end - 1; - } - } + this.Features.AddFlags(feature, flags); + this.EnableFeature(buffer, index, count, feature, null, null); } /// - /// Determines whether the user-specified feature tags include fractional features. + /// Registers a global feature with registration flags and stage actions. /// - /// if fractional features are present; otherwise, . - private bool HasFractions() + /// The glyph shaping buffer. + /// The zero-based index of the first element. + /// The number of elements. + /// The feature tag to enable. + /// The registration flags. + /// The action to invoke before the feature is applied, or . + /// The action to invoke after the feature is applied, or . + protected void EnableFeature( + ShapingBuffer buffer, + int index, + int count, + Tag feature, + ShapingFeatureFlags flags, + Action? preAction, + Action? postAction) { - bool hasNmr = false; - bool hasDnom = false; + this.Features.AddFlags(feature, flags); + this.EnableFeature(buffer, index, count, feature, preAction, postAction); + } - // My kingdom for a binary search on IReadOnlyList - for (int i = 0; i < this.featureTags.Count; i++) + /// + /// Registers a global feature and attaches the supplied stage actions. The + /// feature applies to every glyph of the plan's segments and therefore shares + /// the plan's single global mask bit, which every glyph record is born + /// carrying: registration records the classification and the stage without + /// touching any glyph. + /// + /// The glyph shaping buffer. + /// The zero-based index of the first element. + /// The number of elements. + /// The feature tag to enable. + /// The action to invoke before the feature is applied, or . + /// The action to invoke after the feature is applied, or . + protected void EnableFeature( + ShapingBuffer buffer, + int index, + int count, + Tag feature, + Action? preAction, + Action? postAction) + { + if (this.kerningMode == KerningMode.None) { - Tag feature = this.featureTags[i]; - if (feature == FracTag) - { - return true; - } - - if (feature == DnomTag) + if (feature == KernTag || feature == VKernTag) { - hasDnom = true; + return; } + } - if (feature == NumrTag) - { - hasNmr = true; - } + _ = this.Features.GetOrAddGlobalMask(feature); + this.AddStage(feature, preAction, postAction); + } - if (hasDnom && hasNmr) + /// + /// Appends a shaping stage for the feature unless one already exists. The + /// first registration wins, matching the previous set semantics: a duplicate + /// tag keeps the originally supplied pre and post actions. + /// + /// The feature tag the stage applies. + /// The action to invoke before the feature is applied, or . + /// The action to invoke after the feature is applied, or . + private void AddStage( + Tag feature, + Action? preAction, + Action? postAction) + { + List stages = this.shapingStages; + for (int i = 0; i < stages.Count; i++) + { + if (stages[i].FeatureTag == feature) { - return true; + return; } } - return false; + stages.Add(new ShapingStage(feature, preAction, postAction)); } + + /// + public override List GetShapingStages() => this.shapingStages; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/HangulShaper.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/HangulShaper.cs index c6453db0e..82cec9612 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/HangulShaper.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/HangulShaper.cs @@ -12,82 +12,134 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; /// internal sealed class HangulShaper : DefaultShaper { - /// The 'ljmo' (leading Jamo forms) feature tag. + /// + /// The 'ljmo' (leading Jamo forms) feature tag. + /// private static readonly Tag LjmoTag = Tag.Parse("ljmo"); - /// The 'vjmo' (vowel Jamo forms) feature tag. + /// + /// The 'vjmo' (vowel Jamo forms) feature tag. + /// private static readonly Tag VjmoTag = Tag.Parse("vjmo"); - /// The 'tjmo' (trailing Jamo forms) feature tag. + /// + /// The 'tjmo' (trailing Jamo forms) feature tag. + /// private static readonly Tag TjmoTag = Tag.Parse("tjmo"); - /// The base code point for precomposed Hangul syllables (U+AC00). + /// + /// The base code point for precomposed Hangul syllables (U+AC00). + /// private const int HangulBase = 0xac00; - /// The base code point for leading consonant Jamo (U+1100). + /// + /// The base code point for leading consonant Jamo (U+1100). + /// private const int LBase = 0x1100; // lead - /// The base code point for vowel Jamo (U+1161). + /// + /// The base code point for vowel Jamo (U+1161). + /// private const int VBase = 0x1161; // vowel - /// The base code point for trailing consonant Jamo (U+11A7). + /// + /// The base code point for trailing consonant Jamo (U+11A7). + /// private const int TBase = 0x11a7; // trail - /// The number of leading consonant Jamo. + /// + /// The number of leading consonant Jamo. + /// private const int LCount = 19; - /// The number of vowel Jamo. + /// + /// The number of vowel Jamo. + /// private const int VCount = 21; - /// The number of trailing consonant Jamo (including no-trail). + /// + /// The number of trailing consonant Jamo (including no-trail). + /// private const int TCount = 28; - /// The last leading consonant Jamo code point. + /// + /// The last leading consonant Jamo code point. + /// private const int LEnd = LBase + LCount - 1; - /// The last vowel Jamo code point. + /// + /// The last vowel Jamo code point. + /// private const int VEnd = VBase + VCount - 1; - /// The last trailing consonant Jamo code point. + /// + /// The last trailing consonant Jamo code point. + /// private const int TEnd = TBase + TCount - 1; - /// The dotted circle code point (U+25CC) used as a placeholder base. + /// + /// The dotted circle code point (U+25CC) used as a placeholder base. + /// private const int DottedCircle = 0x25cc; - /// Other character category. + /// + /// Other character category. + /// private const byte X = 0; - /// Leading consonant category. + /// + /// Leading consonant category. + /// private const byte L = 1; - /// Medial vowel category. + /// + /// Medial vowel category. + /// private const byte V = 2; - /// Trailing consonant category. + /// + /// Trailing consonant category. + /// private const byte T = 3; - /// Composed lead-vowel syllable category. + /// + /// Composed lead-vowel syllable category. + /// private const byte LV = 4; - /// Composed lead-vowel-trail syllable category. + /// + /// Composed lead-vowel-trail syllable category. + /// private const byte LVT = 5; - /// Tone mark category. + /// + /// Tone mark category. + /// private const byte M = 6; - /// No action. + /// + /// No action. + /// private const byte None = 0; - /// Decompose composed syllable action. + /// + /// Decompose composed syllable action. + /// private const byte Decompose = 1; - /// Compose Jamo sequence action. + /// + /// Compose Jamo sequence action. + /// private const byte Compose = 2; - /// Reorder tone mark action. + /// + /// Reorder tone mark action. + /// private const byte ToneMark = 4; - /// Invalid sequence (insert dotted circle) action. + /// + /// Invalid sequence (insert dotted circle) action. + /// private const byte Invalid = 5; /// @@ -110,7 +162,9 @@ internal sealed class HangulShaper : DefaultShaper { new byte[] { None, 0 }, new byte[] { None, 1 }, new byte[] { None, 0 }, new byte[] { None, 0 }, new byte[] { Decompose, 2 }, new byte[] { Decompose, 3 }, new byte[] { ToneMark, 0 } }, }; - /// The font metrics used for glyph lookups during composition/decomposition. + /// + /// The font metrics used for glyph lookups during composition/decomposition. + /// private readonly FontMetrics fontMetrics; /// @@ -121,50 +175,67 @@ internal sealed class HangulShaper : DefaultShaper /// The font metrics for glyph lookups. public HangulShaper(ScriptClass script, TextOptions textOptions, FontMetrics fontMetrics) : base(script, MarkZeroingMode.None, textOptions) - => this.fontMetrics = fontMetrics; + { + this.fontMetrics = fontMetrics; + + // The text is left exactly as it was written. This shaper composes and + // decomposes syllables itself, by the rules the script's own features + // describe, and a font of this script is not built to mix ready-made + // syllables with the letters they are built from. Taking the text apart + // beforehand would hand it text of both kinds at once. + this.NormalizationMode = NormalizationMode.None; + } /// - protected override void PlanFeatures(IGlyphShapingCollection collection, int index, int count) + protected override void PlanFeatures(ShapingBuffer buffer, int index, int count) { - this.AddFeature(collection, index, count, LjmoTag, false); - this.AddFeature(collection, index, count, VjmoTag, false); - this.AddFeature(collection, index, count, TjmoTag, false); + this.AddFeature(buffer, index, count, LjmoTag, false); + this.AddFeature(buffer, index, count, VjmoTag, false); + this.AddFeature(buffer, index, count, TjmoTag, false); } /// - protected override void AssignFeatures(IGlyphShapingCollection collection, int index, int count) + protected override void PlanPostprocessingFeatures(ShapingBuffer buffer, int index, int count) { - for (int i = index; i < count; i++) - { - // Uniscribe does not apply 'calt' for Hangul, and certain fonts - // (Noto Sans CJK, Source Sans Han, etc) apply all of jamo lookups - // in calt, which is not desirable. - collection.DisableShapingFeature(i, CaltTag); - } + base.PlanPostprocessingFeatures(buffer, index, count); + + // Certain fonts (Noto Sans CJK, Source Han Sans, etc) apply all of the + // jamo lookups through contextual alternates, which is not desirable. + // The feature is demoted from global to a varying feature whose mask is + // off by default: no glyph applies it unless a later range registration + // enables it, and feature assignment clears it on jamo even then, so the + // jamo lookups such fonts hide behind the feature can never fire there. + this.AddFeature(buffer, index, count, CaltTag, false); + } + + /// + protected override void AssignFeatures(ShapingBuffer buffer, int index, int count) + { + int entryCount = buffer.Count; // Apply the state machine to map glyphs to features. - if (collection is GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role == ShapingBufferRole.Substitution) { - // Allocate a small buffer for composition operations. + // Hangul composition and decomposition use at most three jamo. Keep one + // fixed scratch span outside the state-machine loop for every syllable. Span compositionBuffer = stackalloc ushort[3]; // GSub int state = 0; for (int i = 0; i < count; i++) { - if (i + index >= substitutionCollection.Count) + if (i + index >= buffer.Count) { break; } - GlyphShapingData data = substitutionCollection[i + index]; + ref GlyphShapingData data = ref buffer[i + index]; CodePoint codePoint = data.CodePoint; int type = GetSyllableType(codePoint); byte[] actionsWithState = StateTable[state, type]; byte action = actionsWithState[0]; state = actionsWithState[1]; - // TODO: Do not stackalloc in the loop. switch (action) { case Decompose: @@ -172,7 +243,7 @@ protected override void AssignFeatures(IGlyphShapingCollection collection, int i // Decompose the composed syllable if it is not supported by the font. if (data.GlyphId == 0) { - i = this.DecomposeGlyph(substitutionCollection, data, i, compositionBuffer); + i = this.DecomposeGlyph(buffer, ref data, i, compositionBuffer); } break; @@ -180,19 +251,19 @@ protected override void AssignFeatures(IGlyphShapingCollection collection, int i case Compose: // Found a decomposed syllable. Try to compose if supported by the font. - i = this.ComposeGlyph(substitutionCollection, i, type, compositionBuffer); + i = this.ComposeGlyph(buffer, i, type, compositionBuffer); break; case ToneMark: // Got a valid syllable, followed by a tone mark. Move the tone mark to the beginning of the syllable. - this.ReOrderToneMark(substitutionCollection, data, i); + this.ReOrderToneMark(buffer, ref data, i); break; case Invalid: // Tone mark has no valid syllable to attach to, so insert a dotted circle. - i = this.InsertDottedCircle(substitutionCollection, data, i, compositionBuffer); + i = this.InsertDottedCircle(buffer, ref data, i, compositionBuffer); break; } } @@ -202,38 +273,63 @@ protected override void AssignFeatures(IGlyphShapingCollection collection, int i // GPos // Simply loop and enable based on type. // Glyph substitution has handled [de]composition. + // The three Jamo masks are invariant for the run, so resolving them + // before the loop avoids searching the plan for every glyph. + uint ljmoMask = this.Features.GetMask(LjmoTag); + uint vjmoMask = this.Features.GetMask(VjmoTag); + uint tjmoMask = this.Features.GetMask(TjmoTag); + for (int i = 0; i < count; i++) { - if (i + index >= collection.Count) + if (i + index >= buffer.Count) { break; } - GlyphShapingData data = collection[i + index]; + ref GlyphShapingData data = ref buffer[i + index]; CodePoint codePoint = data.CodePoint; switch (GetSyllableType(codePoint)) { case L: - collection.EnableShapingFeature(i, LjmoTag); + buffer.EnableShapingFeature(i, ljmoMask); break; case V: - collection.EnableShapingFeature(i, VjmoTag); + buffer.EnableShapingFeature(i, vjmoMask); break; case T: - collection.EnableShapingFeature(i, TjmoTag); + buffer.EnableShapingFeature(i, tjmoMask); break; case LV: - collection.EnableShapingFeature(i, LjmoTag); - collection.EnableShapingFeature(i, VjmoTag); + buffer.EnableShapingFeature(i, ljmoMask); + buffer.EnableShapingFeature(i, vjmoMask); break; case LVT: - collection.EnableShapingFeature(i, LjmoTag); - collection.EnableShapingFeature(i, VjmoTag); - collection.EnableShapingFeature(i, TjmoTag); + buffer.EnableShapingFeature(i, ljmoMask); + buffer.EnableShapingFeature(i, vjmoMask); + buffer.EnableShapingFeature(i, tjmoMask); break; } } } + + // Keep contextual alternates away from jamo, running after composition + // and decomposition so the check sees the segment's final code points: + // composed syllables keep the feature while the jamo lookups some fonts + // hide behind it can never fire on the jamo themselves. + count += buffer.Count - entryCount; + int end = index + count; + + // The contextual-alternates mask is likewise invariant while this run is + // scanned for decomposed Jamo. + uint caltMask = this.Features.GetMask(CaltTag); + for (int i = index; i < end && i < buffer.Count; i++) + { + int type = GetSyllableType(buffer[i].CodePoint); + if (type is L or V or T) + { + buffer.DisableShapingFeature(i, caltMask); + } + } } /// @@ -277,12 +373,12 @@ private static int GetSyllableLength(CodePoint codePoint) /// /// Decomposes a precomposed Hangul syllable into its constituent Jamo glyphs. /// - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The shaping data for the composed syllable. /// The index of the glyph to decompose. /// A buffer for temporary glyph ID storage. /// The updated index after decomposition. - private int DecomposeGlyph(GlyphSubstitutionCollection collection, GlyphShapingData data, int index, Span compositinoBuffer) + private int DecomposeGlyph(ShapingBuffer buffer, ref GlyphShapingData data, int index, Span compositinoBuffer) { // Decompose the syllable into a sequence of glyphs. int s = data.CodePoint.Value - HangulBase; @@ -309,9 +405,9 @@ private int DecomposeGlyph(GlyphSubstitutionCollection collection, GlyphShapingD ii[1] = vjmo; ii[0] = ljmo; - collection.Replace(index, ii, KnownFeatureTags.GlyphCompositionDecomposition); - collection.EnableShapingFeature(index, LjmoTag); - collection.EnableShapingFeature(index + 1, VjmoTag); + buffer.Replace(index, ii, KnownFeatureTags.GlyphCompositionDecomposition); + buffer.EnableShapingFeature(index, this.Features.GetMask(LjmoTag)); + buffer.EnableShapingFeature(index + 1, this.Features.GetMask(VjmoTag)); return index + 1; } @@ -320,29 +416,29 @@ private int DecomposeGlyph(GlyphSubstitutionCollection collection, GlyphShapingD iii[1] = vjmo; iii[0] = ljmo; - collection.Replace(index, iii, KnownFeatureTags.GlyphCompositionDecomposition); - collection.EnableShapingFeature(index, LjmoTag); - collection.EnableShapingFeature(index + 1, VjmoTag); - collection.EnableShapingFeature(index + 2, TjmoTag); + buffer.Replace(index, iii, KnownFeatureTags.GlyphCompositionDecomposition); + buffer.EnableShapingFeature(index, this.Features.GetMask(LjmoTag)); + buffer.EnableShapingFeature(index + 1, this.Features.GetMask(VjmoTag)); + buffer.EnableShapingFeature(index + 2, this.Features.GetMask(TjmoTag)); return index + 2; } /// /// Attempts to compose decomposed Jamo into a precomposed Hangul syllable. /// - /// The glyph substitution collection. - /// The current index in the collection. + /// The glyph substitution buffer. + /// The current index in the buffer. /// The syllable type of the current glyph. /// A buffer for glyph IDs during composition. /// The updated index after composition. - private int ComposeGlyph(GlyphSubstitutionCollection collection, int index, int type, Span compositionBuffer) + private int ComposeGlyph(ShapingBuffer buffer, int index, int type, Span compositionBuffer) { if (index == 0) { return index; } - GlyphShapingData prev = collection[index - 1]; + ref GlyphShapingData prev = ref buffer[index - 1]; CodePoint prevCodePoint = prev.CodePoint; int prevType = GetSyllableType(prevCodePoint); @@ -372,8 +468,8 @@ private int ComposeGlyph(GlyphSubstitutionCollection collection, int index, int tjmo = index; } - CodePoint l = collection[ljmo].CodePoint; - CodePoint v = collection[vjmo].CodePoint; + CodePoint l = buffer[ljmo].CodePoint; + CodePoint v = buffer[vjmo].CodePoint; // Make sure L and V are combining characters if (IsCombiningL(l) && IsCombiningV(v)) @@ -382,7 +478,7 @@ private int ComposeGlyph(GlyphSubstitutionCollection collection, int index, int } } - CodePoint t = tjmo >= 0 ? collection[tjmo].CodePoint : new CodePoint(TBase); + CodePoint t = tjmo >= 0 ? buffer[tjmo].CodePoint : new CodePoint(TBase); if ((lv != default) && (t.Value == TBase || IsCombiningT(t))) { CodePoint s = new(lv.Value + (t.Value - TBase)); @@ -393,8 +489,8 @@ private int ComposeGlyph(GlyphSubstitutionCollection collection, int index, int { int del = prevType == V ? 3 : 2; int idx = index - del + 1; - collection.Replace(idx, del - 1, id, KnownFeatureTags.GlyphCompositionDecomposition); - collection[idx].CodePoint = s; + buffer.Replace(idx, del - 1, id, KnownFeatureTags.GlyphCompositionDecomposition); + buffer[idx].CodePoint = s; return idx; } } @@ -402,17 +498,18 @@ private int ComposeGlyph(GlyphSubstitutionCollection collection, int index, int // Didn't compose (either a non-combining component or unsupported by font). if (ljmo >= 0) { - collection.EnableShapingFeature(ljmo, LjmoTag); + buffer.CombineInputStarts(ljmo, (tjmo >= 0 ? tjmo : vjmo) + 1); + buffer.EnableShapingFeature(ljmo, this.Features.GetMask(LjmoTag)); } if (vjmo >= 0) { - collection.EnableShapingFeature(vjmo, VjmoTag); + buffer.EnableShapingFeature(vjmo, this.Features.GetMask(VjmoTag)); } if (tjmo >= 0) { - collection.EnableShapingFeature(tjmo, TjmoTag); + buffer.EnableShapingFeature(tjmo, this.Features.GetMask(TjmoTag)); } if (prevType == LV) @@ -420,7 +517,7 @@ private int ComposeGlyph(GlyphSubstitutionCollection collection, int index, int // Sequence was originally , which got combined earlier. // Either the T was non-combining, or the LVT glyph wasn't supported. // Decompose the glyph again and apply OT features. - this.DecomposeGlyph(collection, collection[index - 1], index - 1, compositionBuffer); + this.DecomposeGlyph(buffer, ref buffer[index - 1], index - 1, compositionBuffer); return index + 1; } @@ -430,10 +527,10 @@ private int ComposeGlyph(GlyphSubstitutionCollection collection, int index, int /// /// Reorders a tone mark to the beginning of the preceding syllable. /// - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The shaping data of the tone mark glyph. - /// The index of the tone mark in the collection. - private void ReOrderToneMark(GlyphSubstitutionCollection collection, GlyphShapingData data, int index) + /// The index of the tone mark in the buffer. + private void ReOrderToneMark(ShapingBuffer buffer, ref GlyphShapingData data, int index) { if (index == 0) { @@ -443,40 +540,45 @@ private void ReOrderToneMark(GlyphSubstitutionCollection collection, GlyphShapin // Move tone mark to the beginning of the previous syllable, unless it is zero width // We don't have access to the glyphs metrics as an array when substituting so we have to loop. FontMetrics fontMetrics = this.fontMetrics; - TextAttributes textAttributes = data.TextRun.TextAttributes; - TextDecorations textDecorations = data.TextRun.TextDecorations; - LayoutMode layoutMode = collection.TextOptions.LayoutMode; - ColorFontSupport colorFontSupport = collection.TextOptions.ColorFontSupport; + TextRun textRun = buffer.TextRuns[data.TextRunIndex]; + TextAttributes textAttributes = textRun.TextAttributes; + TextDecorations textDecorations = textRun.TextDecorations; + LayoutMode layoutMode = buffer.TextOptions.LayoutMode; + ColorFontSupport colorFontSupport = buffer.TextOptions.ColorFontSupport; if (fontMetrics.TryGetGlyphMetrics(data.CodePoint, textAttributes, textDecorations, layoutMode, colorFontSupport, out FontGlyphMetrics? metrics) && metrics.AdvanceWidth == 0) { return; } - GlyphShapingData prev = collection[index - 1]; + ref GlyphShapingData prev = ref buffer[index - 1]; int len = GetSyllableLength(prev.CodePoint); - collection.MoveGlyph(index, index - len); + int syllableStart = index - len; + + buffer.CombineInputStarts(syllableStart, index + 1); + buffer.MoveGlyph(index, syllableStart); } /// /// Inserts a dotted circle glyph as a placeholder for an invalid tone mark that has no syllable to attach to. /// - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The shaping data of the invalid tone mark glyph. - /// The index of the tone mark in the collection. + /// The index of the tone mark in the buffer. /// A buffer for glyph IDs during insertion. /// The updated index after insertion. - private int InsertDottedCircle(GlyphSubstitutionCollection collection, GlyphShapingData data, int index, Span compositionBuffer) + private int InsertDottedCircle(ShapingBuffer buffer, ref GlyphShapingData data, int index, Span compositionBuffer) { bool after = false; FontMetrics fontMetrics = this.fontMetrics; if (fontMetrics.TryGetGlyphId(new(DottedCircle), out ushort id)) { - TextAttributes textAttributes = data.TextRun.TextAttributes; - TextDecorations textDecorations = data.TextRun.TextDecorations; - LayoutMode layoutMode = collection.TextOptions.LayoutMode; - ColorFontSupport colorFontSupport = collection.TextOptions.ColorFontSupport; + TextRun textRun = buffer.TextRuns[data.TextRunIndex]; + TextAttributes textAttributes = textRun.TextAttributes; + TextDecorations textDecorations = textRun.TextDecorations; + LayoutMode layoutMode = buffer.TextOptions.LayoutMode; + ColorFontSupport colorFontSupport = buffer.TextOptions.ColorFontSupport; if (fontMetrics.TryGetGlyphMetrics(data.CodePoint, textAttributes, textDecorations, layoutMode, colorFontSupport, out FontGlyphMetrics? metrics) && metrics.AdvanceWidth != 0) { @@ -496,7 +598,7 @@ private int InsertDottedCircle(GlyphSubstitutionCollection collection, GlyphShap glyphs[0] = id; } - collection.Replace(index, glyphs, KnownFeatureTags.GlyphCompositionDecomposition); + buffer.Replace(index, glyphs, KnownFeatureTags.GlyphCompositionDecomposition); return index + 1; } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/HebrewShaper.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/HebrewShaper.cs index ab4fd13d3..f4d04bcef 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/HebrewShaper.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/HebrewShaper.cs @@ -47,10 +47,14 @@ internal class HebrewShaper : DefaultShaper 0xFB4A, // TAV ]; - /// The font metrics used for glyph lookups during composition. + /// + /// The font metrics used for glyph lookups during composition. + /// private readonly FontMetrics fontMetrics; - /// Whether the font has GSUB features for Hebrew. + /// + /// Whether the font has GSUB features for Hebrew. + /// private readonly bool hasGsub; /// @@ -68,11 +72,11 @@ public HebrewShaper(ScriptClass script, TextOptions textOptions, FontMetrics fon } /// - protected override void AssignFeatures(IGlyphShapingCollection collection, int index, int count) + protected override void AssignFeatures(ShapingBuffer buffer, int index, int count) { - base.AssignFeatures(collection, index, count); + base.AssignFeatures(buffer, index, count); - if (collection is not GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role != ShapingBufferRole.Substitution) { return; } @@ -80,13 +84,13 @@ protected override void AssignFeatures(IGlyphShapingCollection collection, int i // Step 1: Reorder Hebrew marks. // Swap SHEVA/HIRIQ with following METEG when preceded by PATAH/QAMATS. // https://bugzilla.mozilla.org/show_bug.cgi?id=728866 - ReorderMarks(substitutionCollection, index, count); + ReorderMarks(buffer, index, count); // Step 2: Compose Hebrew presentation forms for legacy fonts. // Only applied when the font lacks GSUB features (proxy for lacking GPOS mark). if (!this.hasGsub) { - ComposeHebrewForms(substitutionCollection, this.fontMetrics, index, count); + ComposeHebrewForms(buffer, this.fontMetrics, index, count); } } @@ -98,17 +102,17 @@ protected override void AssignFeatures(IGlyphShapingCollection collection, int i /// and the meteg stress mark. /// /// - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The zero-based start index. /// The number of elements to process. - private static void ReorderMarks(GlyphSubstitutionCollection collection, int index, int count) + private static void ReorderMarks(ShapingBuffer buffer, int index, int count) { int end = index + count; for (int i = index + 2; i < end; i++) { - int c0 = collection[i - 2].CodePoint.Value; - int c1 = collection[i - 1].CodePoint.Value; - int c2 = collection[i].CodePoint.Value; + int c0 = buffer[i - 2].CodePoint.Value; + int c1 = buffer[i - 1].CodePoint.Value; + int c2 = buffer[i].CodePoint.Value; // c0: PATAH (U+05B7) or QAMATS (U+05B8) // c1: SHEVA (U+05B0) or HIRIQ (U+05B4) @@ -116,12 +120,12 @@ private static void ReorderMarks(GlyphSubstitutionCollection collection, int ind if (IsPatahOrQamats(c0) && IsShevaOrHiriq(c1) && IsMetegOrBelow(c2)) { // Swap positions i-1 and i. - GlyphShapingData data1 = collection[i - 1]; - GlyphShapingData data2 = collection[i]; + ref GlyphShapingData data1 = ref buffer[i - 1]; + ref GlyphShapingData data2 = ref buffer[i]; // Swap codepoints and glyph IDs. - (collection[i - 1].CodePoint, collection[i].CodePoint) = (data2.CodePoint, data1.CodePoint); - (collection[i - 1].GlyphId, collection[i].GlyphId) = (data2.GlyphId, data1.GlyphId); + (buffer[i - 1].CodePoint, buffer[i].CodePoint) = (data2.CodePoint, data1.CodePoint); + (buffer[i - 1].GlyphId, buffer[i].GlyphId) = (data2.GlyphId, data1.GlyphId); break; } } @@ -131,23 +135,23 @@ private static void ReorderMarks(GlyphSubstitutionCollection collection, int ind /// Composes Hebrew base + mark sequences into precomposed presentation forms. /// This is a fallback for legacy fonts that lack GPOS mark-to-base positioning. /// - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The font metrics for glyph lookups. /// The zero-based start index. /// The number of elements to process. - private static void ComposeHebrewForms(GlyphSubstitutionCollection collection, FontMetrics fontMetrics, int index, int count) + private static void ComposeHebrewForms(ShapingBuffer buffer, FontMetrics fontMetrics, int index, int count) { int end = index + count; for (int i = index + 1; i < end; i++) { - int a = collection[i - 1].CodePoint.Value; - int b = collection[i].CodePoint.Value; + int a = buffer[i - 1].CodePoint.Value; + int b = buffer[i].CodePoint.Value; int composed = TryCompose(a, b); if (composed != 0 && fontMetrics.TryGetGlyphId(new CodePoint(composed), out ushort composedGlyphId)) { // Replace the two glyphs with the composed form. - collection.Replace(i - 1, 2, composedGlyphId, KnownFeatureTags.GlyphCompositionDecomposition); + buffer.Replace(i - 1, 2, composedGlyphId, KnownFeatureTags.GlyphCompositionDecomposition); end--; i--; } @@ -284,13 +288,17 @@ private static int TryCompose(int a, int b) return 0; } - /// Returns if the codepoint is PATAH (U+05B7) or QAMATS (U+05B8). + /// + /// Returns if the codepoint is PATAH (U+05B7) or QAMATS (U+05B8). + /// /// The codepoint value to test. /// if the codepoint is PATAH or QAMATS. private static bool IsPatahOrQamats(int codepoint) => codepoint is 0x05B7 or 0x05B8; - /// Returns if the codepoint is SHEVA (U+05B0) or HIRIQ (U+05B4). + /// + /// Returns if the codepoint is SHEVA (U+05B0) or HIRIQ (U+05B4). + /// /// The codepoint value to test. /// if the codepoint is SHEVA or HIRIQ. private static bool IsShevaOrHiriq(int codepoint) diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/IndicShaper.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/IndicShaper.cs index cfbe1a169..c300b58b8 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/IndicShaper.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/IndicShaper.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Globalization; +using SixLabors.Fonts.Tables.AdvancedTypographic.GSub; using SixLabors.Fonts.Unicode; using SixLabors.Fonts.Unicode.Resources; using UnicodeTrieGenerator.StateAutomation; @@ -14,94 +15,241 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; /// internal sealed class IndicShaper : DefaultShaper { - /// The state machine for Indic syllable identification. + /// + /// Devanagari letter RRA. + /// + private const int DevanagariLetterRra = 0x0931; + + /// + /// Bengali letter RRA. + /// + private const int BengaliLetterRra = 0x09DC; + + /// + /// Bengali letter RHA. + /// + private const int BengaliLetterRha = 0x09DD; + + /// + /// Tamil letter AU. + /// + private const int TamilLetterAu = 0x0B94; + + /// + /// Bengali letter YA. + /// + private const int BengaliLetterYa = 0x09AF; + + /// + /// Bengali sign Nukta. + /// + private const int BengaliSignNukta = 0x09BC; + + /// + /// Bengali letter YYA. + /// + private const int BengaliLetterYya = 0x09DF; + + /// + /// The bit shift extracting the shaping category from a packed Indic shaping + /// property word; the category occupies the upper byte. + /// + private const int IndicCategoryShift = 8; + + /// + /// The mask extracting the zero-based shaping position from a packed Indic + /// shaping property word; the position occupies the lower byte. + /// + private const int IndicPositionMask = 0xFF; + + /// + /// The state machine for Indic syllable identification. + /// private static readonly StateMachine StateMachine = new(StateTable, AcceptingStates, Tags); - /// Maps Indic shaping category codes to compact DFA symbol indices. + /// + /// The syllable type for each machine state, translated from the tag rows once so + /// match handling never maps rule name strings. + /// + private static readonly SyllableType[] StateSyllableTypes = + SyllableTypeMap.FromMachineTags(Tags); + + /// + /// Maps Indic shaping category codes to compact DFA symbol indices. + /// private static readonly int[] CategoryToSymbolId = BuildCategoryToSymbolId(); - /// The 'rphf' (reph forms) feature tag. + /// + /// The 'rphf' (reph forms) feature tag. + /// private static readonly Tag RphfTag = Tag.Parse("rphf"); - /// The 'nukt' (nukta forms) feature tag. + /// + /// The 'nukt' (nukta forms) feature tag. + /// private static readonly Tag NuktTag = Tag.Parse("nukt"); - /// The 'akhn' (akhands) feature tag. + /// + /// The 'akhn' (akhands) feature tag. + /// private static readonly Tag AkhnTag = Tag.Parse("akhn"); - /// The 'pref' (pre-base forms) feature tag. + /// + /// The 'pref' (pre-base forms) feature tag. + /// private static readonly Tag PrefTag = Tag.Parse("pref"); - /// The 'rkrf' (rakar forms) feature tag. + /// + /// The 'rkrf' (rakar forms) feature tag. + /// private static readonly Tag RkrfTag = Tag.Parse("rkrf"); - /// The 'abvf' (above-base forms) feature tag. + /// + /// The 'abvf' (above-base forms) feature tag. + /// private static readonly Tag AbvfTag = Tag.Parse("abvf"); - /// The 'blwf' (below-base forms) feature tag. + /// + /// The 'blwf' (below-base forms) feature tag. + /// private static readonly Tag BlwfTag = Tag.Parse("blwf"); - /// The 'half' (half forms) feature tag. + /// + /// The 'half' (half forms) feature tag. + /// private static readonly Tag HalfTag = Tag.Parse("half"); - /// The 'pstf' (post-base forms) feature tag. + /// + /// The 'pstf' (post-base forms) feature tag. + /// private static readonly Tag PstfTag = Tag.Parse("pstf"); - /// The 'vatu' (vattu variants) feature tag. + /// + /// The 'vatu' (vattu variants) feature tag. + /// private static readonly Tag VatuTag = Tag.Parse("vatu"); - /// The 'cjct' (conjunct forms) feature tag. + /// + /// The 'cjct' (conjunct forms) feature tag. + /// private static readonly Tag CjctTag = Tag.Parse("cjct"); - /// The 'cfar' (conjunct form after Ra) feature tag. + /// + /// The 'cfar' (conjunct form after Ra) feature tag. + /// private static readonly Tag CfarTag = Tag.Parse("cfar"); - /// The 'init' (initial forms) feature tag. + /// + /// The 'init' (initial forms) feature tag. + /// private static readonly Tag InitTag = Tag.Parse("init"); - /// The 'abvs' (above-base substitutions) feature tag. + /// + /// The 'abvs' (above-base substitutions) feature tag. + /// private static readonly Tag AbvsTag = Tag.Parse("abvs"); - /// The 'blws' (below-base substitutions) feature tag. + /// + /// The 'blws' (below-base substitutions) feature tag. + /// private static readonly Tag BlwsTag = Tag.Parse("blws"); - /// The 'pres' (pre-base substitutions) feature tag. + /// + /// The 'pres' (pre-base substitutions) feature tag. + /// private static readonly Tag PresTag = Tag.Parse("pres"); - /// The 'psts' (post-base substitutions) feature tag. + /// + /// The 'psts' (post-base substitutions) feature tag. + /// private static readonly Tag PstsTag = Tag.Parse("psts"); - /// The 'haln' (halant forms) feature tag. + /// + /// The 'haln' (halant forms) feature tag. + /// private static readonly Tag HalnTag = Tag.Parse("haln"); - /// The 'dist' (distances) feature tag. - private static readonly Tag DistTag = Tag.Parse("dist"); - - /// The 'abvm' (above-base mark positioning) feature tag. - private static readonly Tag AbvmTag = Tag.Parse("abvm"); - - /// The 'blwm' (below-base mark positioning) feature tag. - private static readonly Tag BlwmTag = Tag.Parse("blwm"); - - /// Dotted circle code point (U+25CC) used as a placeholder base. + /// + /// Dotted circle code point (U+25CC) used as a placeholder base. + /// private const int DottedCircle = 0x25cc; - /// The text options. - private readonly TextOptions textOptions; - - /// The font metrics used for glyph lookups. + /// + /// The font metrics used for glyph lookups. + /// private readonly FontMetrics fontMetrics; - /// The script-specific shaping configuration for this Indic script. + /// + /// The script-specific shaping configuration for this Indic script. + /// private ShapingConfiguration indicConfiguration; - /// Whether this font uses old-spec Indic script tags. + /// + /// Whether this font uses old-spec Indic script tags. + /// private readonly bool isOldSpec; - /// Whether any broken clusters were detected during syllable setup. + /// + /// Whether feature probes disallow matching context outside the probed glyph + /// sequence. New-spec scripts other than Malayalam match with zero context. + /// + private readonly bool zeroContext; + + /// + /// Whether the substitution lookups used by Indic feature probes have been + /// captured from the owning shaping plan. + /// + private bool probeLookupsResolved; + + /// + /// The reph-form lookups used to test whether an initial Ra forms a reph. + /// + private List<(Tag Feature, ushort Index, LookupTable LookupTable)>? rphfProbeLookups; + + /// + /// The pre-base-form lookups used to test consonant positioning. + /// + private List<(Tag Feature, ushort Index, LookupTable LookupTable)>? prefProbeLookups; + + /// + /// The below-base-form lookups used to test consonant positioning. + /// + private List<(Tag Feature, ushort Index, LookupTable LookupTable)>? blwfProbeLookups; + + /// + /// The post-base-form lookups used to test consonant positioning. + /// + private List<(Tag Feature, ushort Index, LookupTable LookupTable)>? pstfProbeLookups; + + /// + /// The vattu-form lookups used to test consonant positioning. + /// + private List<(Tag Feature, ushort Index, LookupTable LookupTable)>? vatuProbeLookups; + + /// + /// Whether any broken clusters were detected during syllable setup. + /// private bool hasBrokenClusters; + /// + /// The syllable setup pause, converted to a delegate once so per-pass feature + /// planning never allocates for the conversion. + /// + private readonly Action setupSyllablesAction; + + /// + /// The initial reorder pause, converted to a delegate once so per-pass feature + /// planning never allocates for the conversion. + /// + private readonly Action initialReorderAction; + + /// + /// The final reorder pause, converted to a delegate once so per-pass feature + /// planning never allocates for the conversion. + /// + private readonly Action finalReorderAction; + /// /// Initializes a new instance of the class. /// @@ -112,8 +260,18 @@ internal sealed class IndicShaper : DefaultShaper public IndicShaper(ScriptClass script, Tag unicodeScriptTag, TextOptions textOptions, FontMetrics fontMetrics) : base(script, MarkZeroingMode.None, textOptions) { - this.textOptions = textOptions; + this.FallbackMarkPositioning = false; this.fontMetrics = fontMetrics; + this.setupSyllablesAction = this.SetupSyllables; + this.initialReorderAction = this.InitialReorder; + this.finalReorderAction = this.FinalReorder; + + // Every character comes apart first, even one the font already draws whole. + // This shaper reads a syllable by its pieces and moves them about, so a vowel + // written as one character has to be split into the pieces that are reordered; + // were it left whole because the font can draw it, the reordering would never + // see it. + this.NormalizationMode = NormalizationMode.ComposedDiacriticsNoShortCircuit; if (IndicConfigurations.TryGetValue(script, out ShapingConfiguration value)) { @@ -125,93 +283,99 @@ public IndicShaper(ScriptClass script, Tag unicodeScriptTag, TextOptions textOpt } this.isOldSpec = this.indicConfiguration.HasOldSpec && !unicodeScriptTag.ToString().EndsWith("2", StringComparison.OrdinalIgnoreCase); + this.zeroContext = !this.isOldSpec && script != ScriptClass.Malayalam; } /// - protected override void PlanFeatures(IGlyphShapingCollection collection, int index, int count) + protected override void PreprocessText(ShapingBuffer buffer, int index, int count) + => VowelConstraints.Insert(buffer, this.fontMetrics, this.ScriptClass, index, count); + + /// + public override bool TryDecompose(CodePoint codePoint, out CodePoint first, out CodePoint second) { - this.AddFeature(collection, index, count, LoclTag, preAction: this.SetupSyllables); - this.AddFeature(collection, index, count, CcmpTag); - - this.AddFeature(collection, index, count, NuktTag, preAction: this.InitialReorder); - this.AddFeature(collection, index, count, AkhnTag); - - this.AddFeature(collection, index, count, RphfTag, false); - this.AddFeature(collection, index, count, RkrfTag); - this.AddFeature(collection, index, count, PrefTag, false); - this.AddFeature(collection, index, count, BlwfTag, false); - this.AddFeature(collection, index, count, AbvfTag, false); - this.AddFeature(collection, index, count, HalfTag, false); - this.AddFeature(collection, index, count, PstfTag, false); - this.AddFeature(collection, index, count, VatuTag); - this.AddFeature(collection, index, count, CjctTag); - this.AddFeature(collection, index, count, CfarTag, false, postAction: this.FinalReorder); - - this.AddFeature(collection, index, count, InitTag, false); - this.AddFeature(collection, index, count, PresTag); - this.AddFeature(collection, index, count, AbvsTag); - this.AddFeature(collection, index, count, BlwsTag); - this.AddFeature(collection, index, count, PstsTag); - this.AddFeature(collection, index, count, HalnTag); - this.AddFeature(collection, index, count, DistTag); - this.AddFeature(collection, index, count, AbvmTag); - this.AddFeature(collection, index, count, BlwmTag); + // Indic normalization keeps these four letters atomic even though canonical + // decomposition data contains pairs for them. Fonts expect the original + // letters during script-specific substitution. + if (codePoint.Value is DevanagariLetterRra or BengaliLetterRra or BengaliLetterRha or TamilLetterAu) + { + first = default; + second = default; + return false; + } + + return base.TryDecompose(codePoint, out first, out second); } /// - protected override void AssignFeatures(IGlyphShapingCollection collection, int index, int count) + public override bool TryCompose(CodePoint first, CodePoint second, out CodePoint composed) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + // A split vowel begins with a mark and must remain decomposed for reordering. + if (CodePoint.IsMark(first)) { - return; + composed = default; + return false; } - FontMetrics fontMetrics = this.fontMetrics; - - // Decompose split matras - Span buffer = stackalloc ushort[16]; - int end = index + count; - for (int i = end - 1; i >= index; i--) + if (first.Value == BengaliLetterYa && second.Value == BengaliSignNukta) { - GlyphShapingData data = substitutionCollection[i]; - if ((Decompositions.TryGetValue(data.CodePoint.Value, out int[]? decompositions) || - UniversalShapingData.Decompositions.TryGetValue(data.CodePoint.Value, out decompositions)) && - decompositions != null) - { - Span ids = buffer[..decompositions.Length]; - bool shouldDecompose = true; - for (int j = 0; j < decompositions.Length; j++) - { - if (!fontMetrics.TryGetGlyphId(new CodePoint(decompositions[j]), out ushort id)) - { - shouldDecompose = false; - break; - } + // This excluded canonical pair is intentionally restored to Bengali YYA + // so fonts can address the letter as one substitution input. + composed = new CodePoint(BengaliLetterYya); + return true; + } - ids[j] = id; - } + return base.TryCompose(first, second, out composed); + } - if (shouldDecompose) - { - substitutionCollection.Replace(i, ids, KnownFeatureTags.GlyphCompositionDecomposition); - for (int j = 0; j < decompositions.Length; j++) - { - substitutionCollection[i + j].CodePoint = new(decompositions[j]); - } - } - } - } + /// + protected override void PlanFeatures(ShapingBuffer buffer, int index, int count) + { + this.EnableFeature(buffer, index, count, LoclTag, ShapingFeatureFlags.PerSyllable, this.setupSyllablesAction, null); + this.EnableFeature(buffer, index, count, CcmpTag, ShapingFeatureFlags.PerSyllable); + + this.EnableFeature(buffer, index, count, NuktTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable, this.initialReorderAction, null); + this.EnableFeature(buffer, index, count, AkhnTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable); + + this.AddFeature(buffer, index, count, RphfTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable, false, null, null); + this.EnableFeature(buffer, index, count, RkrfTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable); + this.AddFeature(buffer, index, count, PrefTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable, false, null, null); + this.AddFeature(buffer, index, count, BlwfTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable, false, null, null); + this.AddFeature(buffer, index, count, AbvfTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable, false, null, null); + this.AddFeature(buffer, index, count, HalfTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable, false, null, null); + this.AddFeature(buffer, index, count, PstfTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable, false, null, null); + this.EnableFeature(buffer, index, count, VatuTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, CjctTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable); + this.AddFeature(buffer, index, count, CfarTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable, false, null, this.finalReorderAction); + + this.AddFeature(buffer, index, count, InitTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable, false, null, null); + this.EnableFeature(buffer, index, count, PresTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, AbvsTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, BlwsTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, PstsTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, HalnTag, ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable); + } + + /// + protected override void PlanPostprocessingFeatures(ShapingBuffer buffer, int index, int count) + { + base.PlanPostprocessingFeatures(buffer, index, count); + + // Standard ligature substitution interferes with the conjunct forms these + // scripts build through their dedicated features, so the feature is + // disabled for the whole plan and its lookups are never collected. + this.Features.DisableFeature(LigaTag); } /// /// Identifies Indic syllables using the state machine and assigns shaping info to each glyph. /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based start index. /// The number of elements to process. - private void SetupSyllables(IGlyphShapingCollection collection, int index, int count) + private void SetupSyllables(ShapePlan plan, ShapingBuffer buffer, int index, int count) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role != ShapingBufferRole.Substitution) { return; } @@ -219,6 +383,7 @@ private void SetupSyllables(IGlyphShapingCollection collection, int index, int c this.hasBrokenClusters = false; Span values = count <= 64 ? stackalloc int[count] : new int[count]; + Span shapingProps = count <= 64 ? stackalloc ushort[count] : new ushort[count]; for (int i = index; i < index + count; i++) { @@ -230,47 +395,51 @@ private void SetupSyllables(IGlyphShapingCollection collection, int index, int c // machine expects its input alphabet to be dense 0..N-1, matching the // sequential IDs assigned in GenerateIndicShapingDataTrie. // - // CategoryToSymbolId[IndicShapingCategory(codePoint)] performs this mapping, ensuring that - // every codepoint is presented to the DFA using the correct compact - // symbol index. - CodePoint codePoint = substitutionCollection[i].CodePoint; - values[i - index] = CategoryToSymbolId[IndicShapingCategory(codePoint)]; + // The property word is fetched once per glyph and stashed: the match + // loop below derives both the category and position lanes from it + // rather than walking the trie again. + CodePoint codePoint = buffer[i].CodePoint; + ushort props = (ushort)UnicodeData.GetIndicShapingProperties((uint)codePoint.Value); + shapingProps[i - index] = props; + values[i - index] = CategoryToSymbolId[props >> IndicCategoryShift]; } int syllable = 0; int last = 0; - foreach (StateMatch match in StateMachine.Match(values)) + StateMachine.MatchEnumerator match = StateMachine.EnumerateMatches(values); + while (match.MoveNext()) { if (match.StartIndex > last) { ++syllable; for (int i = last; i < match.StartIndex; i++) { - GlyphShapingData data = substitutionCollection[i + index]; - data.IndicShapingEngineInfo = new(Categories.X, Positions.End, "non_indic_cluster", syllable); + ref GlyphShapingData data = ref buffer[i + index]; + data.Syllable.IndicCategory = Categories.X; + data.Syllable.IndicPosition = Positions.End; + data.Syllable.Type = SyllableType.NonIndicCluster; + data.Syllable.Number = syllable; } } ++syllable; + SyllableType syllableType = StateSyllableTypes[match.TagState]; + if (syllableType == SyllableType.BrokenCluster) + { + this.hasBrokenClusters = true; + } + // Create shaper info. for (int i = match.StartIndex; i <= match.EndIndex; i++) { - GlyphShapingData data = substitutionCollection[i + index]; - CodePoint codePoint = data.CodePoint; - - string syllableType = match.Tags[0]; - - if (syllableType == "broken_cluster") - { - this.hasBrokenClusters = true; - } + ref GlyphShapingData data = ref buffer[i + index]; + ushort props = shapingProps[i]; - data.IndicShapingEngineInfo = new( - (Categories)IndicShapingCategory(codePoint), - (Positions)IndicShapingPosition(codePoint), - syllableType, - syllable); + data.Syllable.IndicCategory = (Categories)(props >> IndicCategoryShift); + data.Syllable.IndicPosition = (Positions)((props & IndicPositionMask) + 1); + data.Syllable.Type = syllableType; + data.Syllable.Number = syllable; } last = match.EndIndex + 1; @@ -281,8 +450,11 @@ private void SetupSyllables(IGlyphShapingCollection collection, int index, int c ++syllable; for (int i = last; i < count; i++) { - GlyphShapingData data = substitutionCollection[i + index]; - data.IndicShapingEngineInfo = new(Categories.X, Positions.End, "non_indic_cluster", syllable); + ref GlyphShapingData data = ref buffer[i + index]; + data.Syllable.IndicCategory = Categories.X; + data.Syllable.IndicPosition = Positions.End; + data.Syllable.Type = SyllableType.NonIndicCluster; + data.Syllable.Number = syllable; } } } @@ -293,34 +465,61 @@ private void SetupSyllables(IGlyphShapingCollection collection, int index, int c /// The code point. /// The shaping category value. private static int IndicShapingCategory(CodePoint codePoint) - => UnicodeData.GetIndicShapingProperties((uint)codePoint.Value) >> 8; + => UnicodeData.GetIndicShapingProperties((uint)codePoint.Value) >> IndicCategoryShift; /// - /// Gets the Indic shaping position for a code point (lower 8 bits as a bit flag). + /// Gets the Indic shaping position for a code point. The trie stores the position + /// zero-based; adding one maps it onto the ordinal enum whose zero is the + /// unassigned sentinel. /// /// The code point. - /// The shaping position as a bit flag. + /// The shaping position ordinal. private static int IndicShapingPosition(CodePoint codePoint) - => 1 << (UnicodeData.GetIndicShapingProperties((uint)codePoint.Value) & 0xFF); + => (UnicodeData.GetIndicShapingProperties((uint)codePoint.Value) & IndicPositionMask) + 1; /// /// Performs the initial reordering pass for Indic syllables, including base consonant /// identification, reph handling, matra reordering, and feature assignment. /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based start index. /// The number of elements to process. - private void InitialReorder(IGlyphShapingCollection collection, int index, int count) + private void InitialReorder(ShapePlan plan, ShapingBuffer buffer, int index, int count) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role != ShapingBufferRole.Substitution) { return; } - // Create a reusable temporary substitution collection and buffer to allow checking whether - // certain combinations will be substituted. - GlyphSubstitutionCollection tempCollection = new(this.textOptions); - Span tempBuffer = new GlyphShapingData[3]; + if (!this.probeLookupsResolved) + { + // The shaper and plan have the same lifetime. Capture these resolved + // feature lists once because consonant classification probes them + // repeatedly for every Indic segment shaped by the cached plan. + this.rphfProbeLookups = plan.TryGetGSubFeatureLookups(in RphfTag, out List<(Tag Feature, ushort Index, LookupTable LookupTable)>? rphfLookups) ? rphfLookups : null; + this.prefProbeLookups = plan.TryGetGSubFeatureLookups(in PrefTag, out List<(Tag Feature, ushort Index, LookupTable LookupTable)>? prefLookups) ? prefLookups : null; + this.blwfProbeLookups = plan.TryGetGSubFeatureLookups(in BlwfTag, out List<(Tag Feature, ushort Index, LookupTable LookupTable)>? blwfLookups) ? blwfLookups : null; + this.pstfProbeLookups = plan.TryGetGSubFeatureLookups(in PstfTag, out List<(Tag Feature, ushort Index, LookupTable LookupTable)>? pstfLookups) ? pstfLookups : null; + this.vatuProbeLookups = plan.TryGetGSubFeatureLookups(in VatuTag, out List<(Tag Feature, ushort Index, LookupTable LookupTable)>? vatuLookups) ? vatuLookups : null; + this.probeLookupsResolved = true; + } + + // Feature assignment revisits these tags throughout every syllable. Resolve + // their plan masks once so the reorder loops do not repeatedly search the + // same small feature lists. + uint rphfMask = this.Features.GetMask(RphfTag); + uint halfMask = this.Features.GetMask(HalfTag); + uint blwfMask = this.Features.GetMask(BlwfTag); + uint abvfMask = this.Features.GetMask(AbvfTag); + uint pstfMask = this.Features.GetMask(PstfTag); + uint prefMask = this.Features.GetMask(PrefTag); + uint cfarMask = this.Features.GetMask(CfarTag); + + // Reusable glyph id span for feature probes. Hoisted out of the syllable loop + // because a stack allocation inside the loop body would grow the stack once + // per syllable for the lifetime of the call. + Span probeGlyphs = stackalloc ushort[3]; ShapingConfiguration indicConfiguration = this.indicConfiguration; FontMetrics fontMetrics = this.fontMetrics; @@ -330,29 +529,18 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c { for (int i = 0; i < count; i++) { - GlyphShapingData data = substitutionCollection[i + index]; - IndicShapingEngineInfo? info = data.IndicShapingEngineInfo; + ref GlyphShapingData data = ref buffer[i + index]; - if (info?.Position == Positions.Base_C) + if (data.Syllable.IndicPosition == Positions.Base_C) { - GlyphShapingData virama = new(data, false) - { - GlyphId = viramaId, - CodePoint = viramaPoint - }; - - tempBuffer[2] = virama; - tempBuffer[1] = data; - tempBuffer[0] = virama; - - info.Position = this.ConsonantPosition(tempCollection, tempBuffer); + data.Syllable.IndicPosition = this.ConsonantPosition(viramaId, data.GlyphId); } } } int max = index + count; int start = index; - int end = NextSyllable(substitutionCollection, index, max); + int end = NextSyllable(buffer, index, max); if (this.hasBrokenClusters) { @@ -361,34 +549,29 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c Span glyphs = stackalloc ushort[2]; while (start < max) { - GlyphShapingData data = substitutionCollection[start]; - IndicShapingEngineInfo? dataInfo = data.IndicShapingEngineInfo; - string? type = dataInfo?.SyllableType; - - if (type == "broken_cluster") + if (buffer[start].Syllable.Type == SyllableType.BrokenCluster) { // Insert after possible Repha. int i = start; for (i = start; i < end; i++) { - if (substitutionCollection[i].IndicShapingEngineInfo?.Category != Categories.Repha) + if (buffer[i].Syllable.IndicCategory != Categories.Repha) { break; } } - GlyphShapingData current = substitutionCollection[i]; - IndicShapingEngineInfo currentInfo = current.IndicShapingEngineInfo!; + ref GlyphShapingData current = ref buffer[i]; glyphs[0] = circleId; glyphs[1] = current.GlyphId; - substitutionCollection.Replace(i, glyphs, KnownFeatureTags.GlyphCompositionDecomposition); + buffer.Replace(i, glyphs, KnownFeatureTags.GlyphCompositionDecomposition); // The dotted circle is now at position i (inherits original shaping info). // Update it to be a dotted circle base. - GlyphShapingData dotted = substitutionCollection[i]; - dotted.IndicShapingEngineInfo!.Category = Categories.Dotted_Circle; - dotted.IndicShapingEngineInfo.Position = Positions.End; + ref GlyphShapingData dotted = ref buffer[i]; + dotted.Syllable.IndicCategory = Categories.Dotted_Circle; + dotted.Syllable.IndicPosition = Positions.End; // The original mark glyph is now at position i + 1 (copy of original info). // Its shaping info is already correct from the copy. @@ -397,26 +580,37 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c } start = end; - end = NextSyllable(substitutionCollection, start, max); + end = NextSyllable(buffer, start, max); } start = index; - end = NextSyllable(substitutionCollection, index, max); + end = NextSyllable(buffer, index, max); } } - _ = fontMetrics.TryGetGSubTable(out GSubTable? gSubTable); while (start < max) { - GlyphShapingData data = substitutionCollection[start]; - IndicShapingEngineInfo? dataInfo = data.IndicShapingEngineInfo; - string? type = dataInfo?.SyllableType; - - if (type is "symbol_cluster" or "non_indic_cluster") + if (buffer[start].Syllable.Type is SyllableType.SymbolCluster or SyllableType.NonIndicCluster) { goto Increment; } + // Kannada preserves a legacy spelling in which Ra + Halant + ZWJ + // behaves as Ra + ZWJ + Halant. Move only the shaping records; source + // offsets remain attached to their logical text slots. + if (this.ScriptClass == ScriptClass.Kannada + && start + 3 <= end + && !buffer[start].IsLigated + && buffer[start].Syllable.IndicCategory == Categories.Ra + && !buffer[start + 1].IsLigated + && buffer[start + 1].Syllable.IndicCategory == Categories.H + && !buffer[start + 2].IsLigated + && buffer[start + 2].Syllable.IndicCategory == Categories.ZWJ) + { + buffer.CombineInputStarts(start + 1, start + 3); + buffer.MoveGlyph(start + 2, start + 1); + } + // 1. Find base consonant: // // The shaping engine finds the base consonant of the syllable, using the @@ -434,20 +628,20 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c // base consonants. if (start + 3 <= end && indicConfiguration.RephPosition != Positions.Ra_To_Become_Reph && - gSubTable?.TryGetFeatureLookups(fontMetrics, in RphfTag, this.ScriptClass, out _) == true && - ((indicConfiguration.RephMode == RephMode.Implicit && !IsJoiner(substitutionCollection[start + 2])) || - (indicConfiguration.RephMode == RephMode.Explicit && substitutionCollection[start + 2].IndicShapingEngineInfo?.Category == Categories.ZWJ))) + this.rphfProbeLookups is not null && + ((indicConfiguration.RephMode == RephMode.Implicit && !IsJoiner(ref buffer[start + 2])) || + (indicConfiguration.RephMode == RephMode.Explicit && buffer[start + 2].Syllable.IndicCategory == Categories.ZWJ))) { // See if it matches the 'rphf' feature. - tempBuffer[2] = substitutionCollection[start + 2]; - tempBuffer[1] = substitutionCollection[start + 1]; - tempBuffer[0] = substitutionCollection[start]; + probeGlyphs[0] = buffer[start].GlyphId; + probeGlyphs[1] = buffer[start + 1].GlyphId; + probeGlyphs[2] = buffer[start + 2].GlyphId; - if ((indicConfiguration.RephMode == RephMode.Explicit && this.WouldSubstitute(tempCollection, in RphfTag, tempBuffer)) || - this.WouldSubstitute(tempCollection, in RphfTag, tempBuffer[..2])) + if ((indicConfiguration.RephMode == RephMode.Explicit && this.WouldSubstitute(this.rphfProbeLookups, probeGlyphs)) || + this.WouldSubstitute(this.rphfProbeLookups, probeGlyphs[..2])) { limit += 2; - while (limit < end && IsJoiner(substitutionCollection[limit])) + while (limit < end && IsJoiner(ref buffer[limit])) { limit++; } @@ -457,10 +651,10 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c } } else if (indicConfiguration.RephMode == RephMode.Log_Repha && - substitutionCollection[start].IndicShapingEngineInfo?.Category == Categories.Repha) + buffer[start].Syllable.IndicCategory == Categories.Repha) { limit++; - while (limit < end && IsJoiner(substitutionCollection[limit])) + while (limit < end && IsJoiner(ref buffer[limit])) { limit++; } @@ -479,14 +673,14 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c do { - IndicShapingEngineInfo? prevInfo = substitutionCollection[--i].IndicShapingEngineInfo; + ref GlyphShapingData prev = ref buffer[--i]; // Until a consonant is found - if (IsConsonant(substitutionCollection[i])) + if (IsConsonant(ref prev)) { // that does not have a below-base or post-base form // (post-base forms have to follow below-base forms), - if (prevInfo?.Position != Positions.Below_C && (prevInfo?.Position != Positions.Post_C || seenBelow)) + if (prev.Syllable.IndicPosition != Positions.Below_C && (prev.Syllable.IndicPosition != Positions.Post_C || seenBelow)) { basePosition = i; break; @@ -502,15 +696,15 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c // or arrive at the first consonant. The consonant stopped at will // be the base. - if (prevInfo?.Position == Positions.Below_C) + if (prev.Syllable.Type != SyllableType.None && prev.Syllable.IndicPosition == Positions.Below_C) { seenBelow = true; } basePosition = i; } - else if (start < i && prevInfo?.Category == Categories.ZWJ && - substitutionCollection[i - 1].IndicShapingEngineInfo?.Category == Categories.H) + else if (start < i && prev.Syllable.IndicCategory == Categories.ZWJ && prev.Syllable.Type != SyllableType.None && + buffer[i - 1].Syllable.IndicCategory == Categories.H) { // A ZWJ after a Halant stops the base search, and requests an explicit // half form. @@ -532,10 +726,10 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c for (int i = basePosition + 1; i < end; i++) { - GlyphShapingData c = substitutionCollection[i]; - if (IsConsonant(c) && c.IndicShapingEngineInfo != null) + ref GlyphShapingData c = ref buffer[i]; + if (IsConsonant(ref c)) { - c.IndicShapingEngineInfo.Position = Positions.Below_C; + c.Syllable.IndicPosition = Positions.Below_C; } } @@ -584,19 +778,19 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c // Reorder characters for (int i = start; i < basePosition; i++) { - IndicShapingEngineInfo? info = substitutionCollection[i].IndicShapingEngineInfo; - if (info != null) + ref GlyphShapingData item = ref buffer[i]; + if (item.Syllable.Type != SyllableType.None) { - info.Position = (Positions)Math.Min((int)Positions.Pre_C, (int)info.Position); + item.Syllable.IndicPosition = (Positions)Math.Min((int)Positions.Pre_C, (int)item.Syllable.IndicPosition); } } if (basePosition < end) { - IndicShapingEngineInfo? info = substitutionCollection[basePosition].IndicShapingEngineInfo; - if (info != null) + ref GlyphShapingData item = ref buffer[basePosition]; + if (item.Syllable.Type != SyllableType.None) { - info.Position = Positions.Base_C; + item.Syllable.IndicPosition = Positions.Base_C; } } @@ -604,14 +798,14 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c // like in Khmer. for (int i = basePosition + 1; i < end; i++) { - if (substitutionCollection[i].IndicShapingEngineInfo?.Category == Categories.M) + if (buffer[i].Syllable.IndicCategory == Categories.M) { for (int j = i + 1; j < end; j++) { - GlyphShapingData c = substitutionCollection[j]; - if (IsConsonant(c) && c.IndicShapingEngineInfo != null) + ref GlyphShapingData c = ref buffer[j]; + if (IsConsonant(ref c)) { - c.IndicShapingEngineInfo.Position = Positions.Final_C; + c.Syllable.IndicPosition = Positions.Final_C; break; } } @@ -623,50 +817,43 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c // Handle beginning Ra if (hasReph) { - GlyphShapingData c = substitutionCollection[start]; - if (c.IndicShapingEngineInfo != null) + ref GlyphShapingData c = ref buffer[start]; + if (c.Syllable.Type != SyllableType.None) { - c.IndicShapingEngineInfo.Position = Positions.Ra_To_Become_Reph; + c.Syllable.IndicPosition = Positions.Ra_To_Become_Reph; } } // For old-style Indic script tags, move the first post-base Halant after - // last consonant. - // - // Reports suggest that in some scripts Uniscribe does this only if there - // is *not* a Halant after last consonant already (eg. Kannada), while it - // does it unconditionally in other scripts (eg. Malayalam). We don't - // currently know about other scripts, so we single out Malayalam for now. - // - // Kannada test case: - // U+0C9A,U+0CCD,U+0C9A,U+0CCD - // With some versions of Lohit Kannada. - // https://bugs.freedesktop.org/show_bug.cgi?id=59118 - // - // Malayalam test case: - // U+0D38,U+0D4D,U+0D31,U+0D4D,U+0D31,U+0D4D - // With lohit-ttf-20121122/Lohit-Malayalam.ttf + // the last consonant. Kannada alone blocks the move when another halant + // already terminates the sequence; the other old-style scripts move it + // unconditionally. if (this.isOldSpec) { - bool disallowDoubleHalants = this.ScriptClass != ScriptClass.Malayalam; + bool disallowDoubleHalants = this.ScriptClass == ScriptClass.Kannada; for (int i = basePosition + 1; i < end; i++) { - if (substitutionCollection[i].IndicShapingEngineInfo?.Category == Categories.H) + if (buffer[i].Syllable.IndicCategory == Categories.H) { int j; for (j = end - 1; j > i; j--) { - GlyphShapingData c = substitutionCollection[j]; - if (IsConsonant(c) || (disallowDoubleHalants && c.IndicShapingEngineInfo?.Category == Categories.H)) + ref GlyphShapingData c = ref buffer[j]; + if (IsConsonant(ref c) || (disallowDoubleHalants && c.Syllable.IndicCategory == Categories.H)) { break; } } - if (j > i && substitutionCollection[j].IndicShapingEngineInfo?.Category != Categories.H) + if (j > i) { - // Move Halant to after last consonant. - substitutionCollection.MoveGlyph(i, j); + // The old-spec sequence is one shaping input range even + // when Kannada retains a final halant to avoid doubling it. + buffer.CombineInputStarts(i, j + 1); + if (buffer[j].Syllable.IndicCategory != Categories.H) + { + buffer.MoveGlyph(i, j); + } } break; @@ -678,13 +865,14 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c Positions lastPosition = Positions.Start; for (int i = start; i < end; i++) { - IndicShapingEngineInfo? info = substitutionCollection[i].IndicShapingEngineInfo; - if (info != null) + ref GlyphShapingData item = ref buffer[i]; + if (item.Syllable.Type != SyllableType.None) { - if ((FlagUnsafe(info.Category) & (JoinerFlags | Flag(Categories.N) | Flag(Categories.RS) | Flag(Categories.CM) | (HalantOrCoengFlags & FlagUnsafe(info.Category)))) != 0) + Categories category = item.Syllable.IndicCategory; + if ((FlagUnsafe(category) & (JoinerFlags | Flag(Categories.N) | Flag(Categories.RS) | Flag(Categories.CM) | (HalantFlags & FlagUnsafe(category)))) != 0) { - info.Position = lastPosition; - if (info.Category == Categories.H && info.Position == Positions.Pre_M) + item.Syllable.IndicPosition = lastPosition; + if (category == Categories.H && item.Syllable.IndicPosition == Positions.Pre_M) { // Uniscribe doesn't move the Halant with Left Matra. // TEST: U+092B,U+093F,U+094DE @@ -695,27 +883,29 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c // TEST: U+0D9A,U+0DDA for (int j = i; j > start; j--) { - Positions? pos = substitutionCollection[j - 1].IndicShapingEngineInfo?.Position; - if (pos is not null and not Positions.Pre_M) + // An unassigned record reads as position zero, matching + // the previous null semantics: keep scanning. + Positions pos = buffer[j - 1].Syllable.IndicPosition; + if (pos is not 0 and not Positions.Pre_M) { - info.Position = pos.Value; + item.Syllable.IndicPosition = pos; break; } } } } - else if (info.Position != Positions.SMVD) + else if (item.Syllable.IndicPosition != Positions.SMVD) { // If an MPst follows an SM, update the SM's position to match // so they move together during reordering. - if (info.Category == Categories.MPst + if (category == Categories.MPst && i > start - && substitutionCollection[i - 1].IndicShapingEngineInfo?.Category == Categories.SM) + && buffer[i - 1].Syllable.IndicCategory == Categories.SM) { - substitutionCollection[i - 1].IndicShapingEngineInfo!.Position = info.Position; + buffer[i - 1].Syllable.IndicPosition = item.Syllable.IndicPosition; } - lastPosition = info.Position; + lastPosition = item.Syllable.IndicPosition; } } } @@ -725,45 +915,76 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c int last = basePosition; for (int i = basePosition + 1; i < end; i++) { - GlyphShapingData current = substitutionCollection[i]; - IndicShapingEngineInfo? info = current.IndicShapingEngineInfo; - if (info != null) + ref GlyphShapingData current = ref buffer[i]; + if (current.Syllable.Type != SyllableType.None) { - if (IsConsonant(current)) + if (IsConsonant(ref current)) { for (int j = last + 1; j < i; j++) { - IndicShapingEngineInfo? jInfo = substitutionCollection[j].IndicShapingEngineInfo; - if (jInfo?.Position < Positions.SMVD) + ref GlyphShapingData between = ref buffer[j]; + if (between.Syllable.Type != SyllableType.None && between.Syllable.IndicPosition < Positions.SMVD) { - jInfo.Position = info.Position; + between.Syllable.IndicPosition = current.Syllable.IndicPosition; } } last = i; } - else if ((FlagUnsafe(info.Category) & (Flag(Categories.M) | Flag(Categories.MPst))) != 0) + else if ((FlagUnsafe(current.Syllable.IndicCategory) & (Flag(Categories.M) | Flag(Categories.MPst))) != 0) { last = i; } } } - substitutionCollection.Sort(start, end, (a, b) => + buffer.Sort(start, end, (a, b) => { - int pa = a.IndicShapingEngineInfo?.Position != null ? (int)a.IndicShapingEngineInfo.Position : 0; - int pb = b.IndicShapingEngineInfo?.Position != null ? (int)b.IndicShapingEngineInfo.Position : 0; + int pa = (int)a.Syllable.IndicPosition; + int pb = (int)b.Syllable.IndicPosition; return pa - pb; }); - // Find base again + // Stable position sorting groups every pre-base matra at the front but + // leaves adjacent split-matra pieces in logical order. Reverse the full + // pre-base range, then reverse each matra-led piece back independently; + // this reverses the pieces without reversing the marks within a piece. + int firstLeftMatra = end; + int lastLeftMatra = end; + basePosition = end; for (int i = start; i < end; i++) { - if (substitutionCollection[i].IndicShapingEngineInfo?.Position == Positions.Base_C) + if (buffer[i].Syllable.IndicPosition == Positions.Base_C) { basePosition = i; break; } + + if (buffer[i].Syllable.IndicPosition == Positions.Pre_M) + { + if (firstLeftMatra == end) + { + firstLeftMatra = i; + } + + lastLeftMatra = i; + } + } + + if (firstLeftMatra < lastLeftMatra) + { + buffer.ReverseRange(firstLeftMatra, lastLeftMatra + 1); + + uint matraFlags = Flag(Categories.M) | Flag(Categories.MPst); + int pieceStart = firstLeftMatra; + for (int i = pieceStart; i <= lastLeftMatra; i++) + { + if ((FlagUnsafe(buffer[i].Syllable.IndicCategory) & matraFlags) != 0) + { + buffer.ReverseRange(pieceStart, i + 1); + pieceStart = i + 1; + } + } } // Setup features now. @@ -771,32 +992,31 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c // Reph. for (int i = start; i < end; i++) { - IndicShapingEngineInfo? info = substitutionCollection[i].IndicShapingEngineInfo; - if (info?.Position != Positions.Ra_To_Become_Reph) + if (buffer[i].Syllable.IndicPosition != Positions.Ra_To_Become_Reph) { break; } - substitutionCollection.EnableShapingFeature(i, RphfTag); + buffer.EnableShapingFeature(i, rphfMask); } // Pre-base bool blwf = !this.isOldSpec && indicConfiguration.BlwfMode == BlwfMode.Pre_And_Post; for (int i = start; i < basePosition; i++) { - substitutionCollection.EnableShapingFeature(i, HalfTag); + buffer.EnableShapingFeature(i, halfMask); if (blwf) { - substitutionCollection.EnableShapingFeature(i, BlwfTag); + buffer.EnableShapingFeature(i, blwfMask); } } // Post-base for (int i = basePosition + 1; i < end; i++) { - substitutionCollection.EnableShapingFeature(i, AbvfTag); - substitutionCollection.EnableShapingFeature(i, PstfTag); - substitutionCollection.EnableShapingFeature(i, BlwfTag); + buffer.EnableShapingFeature(i, abvfMask); + buffer.EnableShapingFeature(i, pstfMask); + buffer.EnableShapingFeature(i, blwfMask); } if (this.isOldSpec && this.ScriptClass == ScriptClass.Devanagari) @@ -813,37 +1033,35 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c // Test case: U+0924,U+094D,U+0930,U+094d,U+0915 // with Sanskrit 2003 font. // - // However, note that Ra,Halant,ZWJ is the correct way to - // request eyelash form of Ra, so we wouldn't inhibit it - // in that sequence. - // - // Test case: U+0924,U+094D,U+0930,U+094d,U+200D,U+0915 + // Ra + Halant immediately before the base receives the below-base + // feature. Earlier pairs also receive it unless ZWJ follows, because + // that explicit joiner requests the eyelash form instead. for (int i = start; i + 1 < basePosition; i++) { - if (substitutionCollection[i].IndicShapingEngineInfo?.Category == Categories.Ra && - substitutionCollection[i + 1].IndicShapingEngineInfo?.Category == Categories.H && - (i + 1 == basePosition || substitutionCollection[i + 2].IndicShapingEngineInfo?.Category == Categories.ZWJ)) + if (buffer[i].Syllable.IndicCategory == Categories.Ra + && buffer[i + 1].Syllable.IndicCategory == Categories.H + && (i + 2 == basePosition || buffer[i + 2].Syllable.IndicCategory != Categories.ZWJ)) { - substitutionCollection.EnableShapingFeature(i, BlwfTag); - substitutionCollection.EnableShapingFeature(i + 1, BlwfTag); + buffer.EnableShapingFeature(i, blwfMask); + buffer.EnableShapingFeature(i + 1, blwfMask); } } } const int prefLen = 2; if (basePosition + prefLen < end && - gSubTable?.TryGetFeatureLookups(fontMetrics, in PrefTag, this.ScriptClass, out _) == true) + this.prefProbeLookups is not null) { // Find a Halant,Ra sequence and mark it for pre-base reordering processing. for (int i = basePosition + 1; i + prefLen - 1 < end; i++) { - tempBuffer[1] = substitutionCollection[i + 1]; - tempBuffer[0] = substitutionCollection[i]; - if (this.WouldSubstitute(tempCollection, in PrefTag, tempBuffer[..2])) + probeGlyphs[0] = buffer[i].GlyphId; + probeGlyphs[1] = buffer[i + 1].GlyphId; + if (this.WouldSubstitute(this.prefProbeLookups, probeGlyphs[..2])) { for (int j = 0; j < prefLen; j++) { - substitutionCollection.EnableShapingFeature(i++, PrefTag); + buffer.EnableShapingFeature(i++, prefMask); } // Mark the subsequent stuff with 'cfar'. Used in Khmer. @@ -851,11 +1069,11 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c // This allows distinguishing the following cases with MS Khmer fonts: // U+1784,U+17D2,U+179A,U+17D2,U+1782 // U+1784,U+17D2,U+1782,U+17D2,U+179A - if (gSubTable.TryGetFeatureLookups(fontMetrics, in CfarTag, this.ScriptClass, out _)) + if (plan.TryGetGSubFeatureLookups(in CfarTag, out _)) { while (i < end) { - substitutionCollection.EnableShapingFeature(i, CfarTag); + buffer.EnableShapingFeature(i, cfarMask); i++; } } @@ -868,10 +1086,10 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c // Apply ZWJ/ZWNJ effects for (int i = start + 1; i < end; i++) { - GlyphShapingData current = substitutionCollection[i]; - if (IsJoiner(current)) + ref GlyphShapingData current = ref buffer[i]; + if (IsJoiner(ref current)) { - bool nonJoiner = current.IndicShapingEngineInfo?.Category == Categories.ZWNJ; + bool nonJoiner = current.Syllable.IndicCategory == Categories.ZWNJ; int j = i; do @@ -885,42 +1103,50 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c // A ZWNJ disables HALF. if (nonJoiner) { - substitutionCollection.DisableShapingFeature(j, HalfTag); + buffer.DisableShapingFeature(j, halfMask); } } - while (j > start && !IsConsonant(substitutionCollection[j])); + while (j > start && !IsConsonant(ref buffer[j])); } } Increment: start = end; - end = NextSyllable(substitutionCollection, start, max); + end = NextSyllable(buffer, start, max); } } /// - /// Determines the positional class of a consonant by testing whether it would be - /// substituted by below-base, post-base, or pre-base features. + /// Determines the positional class of a consonant by testing whether the + /// virama-consonant and consonant-virama pairs would be substituted by the + /// below-base, vattu, post-base, or pre-base forming features. /// - /// A temporary substitution collection for testing. - /// The consonant and virama glyph data to test. + /// The virama glyph id. + /// The consonant glyph id. /// The consonant's positional class. - private Positions ConsonantPosition(GlyphSubstitutionCollection collection, ReadOnlySpan data) + private Positions ConsonantPosition(ushort virama, ushort consonant) { - if (this.WouldSubstitute(collection, in BlwfTag, data[..2]) || - this.WouldSubstitute(collection, in BlwfTag, data.Slice(1, 2))) + Span glyphs = stackalloc ushort[3]; + glyphs[0] = virama; + glyphs[1] = consonant; + glyphs[2] = virama; + + if (this.WouldSubstitute(this.blwfProbeLookups, glyphs[..2]) || + this.WouldSubstitute(this.blwfProbeLookups, glyphs.Slice(1, 2)) || + this.WouldSubstitute(this.vatuProbeLookups, glyphs[..2]) || + this.WouldSubstitute(this.vatuProbeLookups, glyphs.Slice(1, 2))) { return Positions.Below_C; } - if (this.WouldSubstitute(collection, in PstfTag, data[..2]) || - this.WouldSubstitute(collection, in PstfTag, data.Slice(1, 2))) + if (this.WouldSubstitute(this.pstfProbeLookups, glyphs[..2]) || + this.WouldSubstitute(this.pstfProbeLookups, glyphs.Slice(1, 2))) { return Positions.Post_C; } - if (this.WouldSubstitute(collection, in PrefTag, data[..2]) || - this.WouldSubstitute(collection, in PrefTag, data.Slice(1, 2))) + if (this.WouldSubstitute(this.prefProbeLookups, glyphs[..2]) || + this.WouldSubstitute(this.prefProbeLookups, glyphs.Slice(1, 2))) { return Positions.Post_C; } @@ -929,51 +1155,25 @@ private Positions ConsonantPosition(GlyphSubstitutionCollection collection, Read } /// - /// Tests whether applying a specific feature to the given glyphs would produce a substitution. + /// Tests whether any lookup for a shaping feature would substitute the given + /// glyph sequence without running the substitution. /// - /// A temporary substitution collection for testing. - /// The feature tag to test. - /// The glyph data to test. + /// The feature's resolved substitution lookups. + /// The glyph id sequence to test. /// if a substitution would occur. - private bool WouldSubstitute(GlyphSubstitutionCollection collection, in Tag featureTag, ReadOnlySpan buffer) + private bool WouldSubstitute(List<(Tag Feature, ushort Index, LookupTable LookupTable)>? lookups, ReadOnlySpan glyphs) { - collection.Clear(); - for (int i = 0; i < buffer.Length; i++) + if (lookups is null) { - collection.AddGlyph(buffer[i], i); - collection.EnableShapingFeature(i, featureTag); + return false; } - FontMetrics fontMetrics = this.fontMetrics; - if (fontMetrics.TryGetGSubTable(out GSubTable? gSubTable)) + for (int i = 0; i < lookups.Count; i++) { - const int index = 0; - SkippingGlyphIterator iterator = new(fontMetrics, collection, index, default, 0); - int initialCount = collection.Count; - int collectionCount = initialCount; - int count = initialCount - index; - int i = index; - - // Set max constraints to prevent OutOfMemoryException or infinite loops from attacks. - int maxCount = AdvancedTypographicUtils.GetMaxAllowableShapingCollectionCount(collection.Count); - int maxOperationsCount = AdvancedTypographicUtils.GetMaxAllowableShapingOperationsCount(collection.Count); - int currentOperations = 0; - - gSubTable.ApplyFeature( - fontMetrics, - collection, - ref iterator, - in featureTag, - this.ScriptClass, - index, - ref count, - ref i, - ref collectionCount, - maxCount, - maxOperationsCount, - ref currentOperations); - - return collection.Count != initialCount; + if (lookups[i].LookupTable.WouldApply(glyphs, this.zeroContext)) + { + return true; + } } return false; @@ -984,43 +1184,43 @@ private bool WouldSubstitute(GlyphSubstitutionCollection collection, in Tag feat /// /// The glyph shaping data. /// if the glyph is a consonant. - private static bool IsConsonant(GlyphShapingData data) - => data.IndicShapingEngineInfo != null && (FlagUnsafe(data.IndicShapingEngineInfo.Category) & ConsonantFlags) != 0; + private static bool IsConsonant(ref GlyphShapingData data) + => (FlagUnsafe(data.Syllable.IndicCategory) & ConsonantFlags) != 0; /// /// Determines whether the glyph data represents a joiner (ZWJ or ZWNJ). /// /// The glyph shaping data. /// if the glyph is a joiner. - private static bool IsJoiner(GlyphShapingData data) - => data.IndicShapingEngineInfo != null && (FlagUnsafe(data.IndicShapingEngineInfo.Category) & JoinerFlags) != 0; + private static bool IsJoiner(ref GlyphShapingData data) + => (FlagUnsafe(data.Syllable.IndicCategory) & JoinerFlags) != 0; /// /// Determines whether the glyph data represents a halant or coeng character. /// /// The glyph shaping data. /// if the glyph is a halant or coeng. - private static bool IsHalantOrCoeng(GlyphShapingData data) - => data.IndicShapingEngineInfo != null && (FlagUnsafe(data.IndicShapingEngineInfo.Category) & HalantOrCoengFlags) != 0; + private static bool IsHalant(ref GlyphShapingData data) + => (FlagUnsafe(data.Syllable.IndicCategory) & HalantFlags) != 0; /// - /// Finds the start index of the next syllable in the collection. + /// Finds the start index of the next syllable in the buffer. /// - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The current index. /// The maximum index bound. /// The start index of the next syllable. - private static int NextSyllable(GlyphSubstitutionCollection collection, int index, int count) + private static int NextSyllable(ShapingBuffer buffer, int index, int count) { if (index >= count) { return index; } - int? syllable = collection[index].IndicShapingEngineInfo?.Syllable; + int? syllable = buffer[index].Syllable.Number; while (++index < count) { - if (collection[index].IndicShapingEngineInfo?.Syllable != syllable) + if (buffer[index].Syllable.Number != syllable) { break; } @@ -1033,21 +1233,24 @@ private static int NextSyllable(GlyphSubstitutionCollection collection, int inde /// Performs the final reordering pass for Indic syllables, repositioning reph, /// pre-base consonants, and pre-base matras after basic shaping. /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based start index. /// The number of elements to process. - private void FinalReorder(IGlyphShapingCollection collection, int index, int count) + private void FinalReorder(ShapePlan plan, ShapingBuffer buffer, int index, int count) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role != ShapingBufferRole.Substitution) { return; } int max = index + count; int start = index; - int end = NextSyllable(substitutionCollection, index, max); + int end = NextSyllable(buffer, index, max); FontMetrics fontMetrics = this.fontMetrics; - _ = fontMetrics.TryGetGSubTable(out GSubTable? gSubTable); + uint prefMask = this.Features.GetMask(PrefTag); + uint initMask = this.Features.GetMask(InitTag); + while (start < max) { // 4. Final reordering: @@ -1056,35 +1259,36 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou // applied (see below), the shaping engine performs some final glyph // reordering before applying all the remaining font features to the entire // cluster. - bool tryPref = gSubTable?.TryGetFeatureLookups(fontMetrics, in PrefTag, this.ScriptClass, out _) == true; + bool tryPref = this.prefProbeLookups is not null; // Find base consonant again. int basePosition = start; for (; basePosition < end; basePosition++) { - if (substitutionCollection[basePosition].IndicShapingEngineInfo?.Position >= Positions.Base_C) + if (buffer[basePosition].Syllable.IndicPosition >= Positions.Base_C) { if (tryPref && basePosition + 1 < end) { for (int i = basePosition + 1; i < end; i++) { - GlyphShapingData current = substitutionCollection[i]; - if (current.Features.FindIndex(x => x.Tag == PrefTag && x.Enabled) >= 0) + ref GlyphShapingData current = ref buffer[i]; + if ((current.FeatureMask & prefMask) != 0) { - if (!current.IsSubstituted && current.IsLigated && !current.IsDecomposed) + // A pre-base candidate that did not finish as a + // ligature did not form. Treat the following glyph as + // the base so later matra movement respects the block. + if (!(current.IsSubstituted && current.IsLigated && !current.IsDecomposed)) { - // Ok, this was a 'pref' candidate but didn't form any. - // Base is around here... basePosition = i; - while (basePosition < end && IsHalantOrCoeng(substitutionCollection[basePosition])) + while (basePosition < end && IsHalant(ref buffer[basePosition])) { basePosition++; } - IndicShapingEngineInfo? info = substitutionCollection[basePosition].IndicShapingEngineInfo; - if (info != null) + ref GlyphShapingData newBase = ref buffer[basePosition]; + if (newBase.Syllable.Type != SyllableType.None) { - info.Position = Positions.Base_C; + newBase.Syllable.IndicPosition = Positions.Base_C; tryPref = false; } } @@ -1099,39 +1303,39 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou { for (int i = basePosition + 1; i < end; i++) { - while (i < end && IsJoiner(substitutionCollection[i])) + while (i < end && IsJoiner(ref buffer[i])) { i++; } - if (i == end || !IsHalantOrCoeng(substitutionCollection[i])) + if (i == end || !IsHalant(ref buffer[i])) { break; } i++; // Skip halant. - while (i < end && IsJoiner(substitutionCollection[i])) + while (i < end && IsJoiner(ref buffer[i])) { i++; } if (i < end) { - GlyphShapingData current = substitutionCollection[i]; - if (IsConsonant(current) && current.IndicShapingEngineInfo?.Position == Positions.Below_C) + ref GlyphShapingData current = ref buffer[i]; + if (IsConsonant(ref current) && current.Syllable.IndicPosition == Positions.Below_C) { basePosition = i; - IndicShapingEngineInfo? info = substitutionCollection[basePosition].IndicShapingEngineInfo; - if (info != null) + ref GlyphShapingData newBase = ref buffer[basePosition]; + if (newBase.Syllable.Type != SyllableType.None) { - info.Position = Positions.Base_C; + newBase.Syllable.IndicPosition = Positions.Base_C; } } } } } - if (start < basePosition && substitutionCollection[basePosition].IndicShapingEngineInfo?.Position > Positions.Base_C) + if (start < basePosition && buffer[basePosition].Syllable.IndicPosition > Positions.Base_C) { basePosition--; } @@ -1140,14 +1344,14 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou } } - if (basePosition == end && start < basePosition && substitutionCollection[basePosition - 1].IndicShapingEngineInfo?.Category == Categories.ZWJ) + if (basePosition == end && start < basePosition && buffer[basePosition - 1].Syllable.IndicCategory == Categories.ZWJ) { basePosition--; } if (basePosition < end) { - while (start < basePosition && (FlagUnsafe(substitutionCollection[basePosition].IndicShapingEngineInfo?.Category) & (Flag(Categories.N) | HalantOrCoengFlags)) != 0) + while (start < basePosition && (FlagUnsafe(buffer[basePosition].Syllable.IndicCategory) & (Flag(Categories.N) | HalantFlags)) != 0) { basePosition--; } @@ -1159,8 +1363,10 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou // features, the glyph can be moved closer to the main consonant based on // whether half-forms had been formed. Actual position for the matra is // defined as "after last standalone halant glyph, after initial matra - // position and before the main consonant". If ZWJ or ZWNJ follow this - // halant, position is moved after it. + // position and before the main consonant". A halant followed by ZWJ is + // not a valid destination, so the search continues toward the original + // matra position. Halant followed by ZWNJ terminates the syllable in the + // state machine and needs no special handling here. // // Otherwise there can't be any pre-base matra characters. if (start + 1 < end && start < basePosition) @@ -1173,35 +1379,49 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou // We want to position matra after them. if (this.ScriptClass is not ScriptClass.Malayalam and not ScriptClass.Tamil) { - while (newPos > start && (FlagUnsafe(substitutionCollection[newPos].IndicShapingEngineInfo?.Category) & (Flag(Categories.M) | HalantOrCoengFlags)) == 0) + bool searchAgain; + do { - newPos--; - } + searchAgain = false; - // If we found no Halant we are done. - // Otherwise only proceed if the Halant does - // not belong to the Matra itself! - GlyphShapingData current = substitutionCollection[newPos]; - if (IsHalantOrCoeng(current) && current.IndicShapingEngineInfo?.Position != Positions.Pre_M) - { - // If ZWJ or ZWNJ follow this halant, position is moved after it. - if (newPos + 1 < end && IsJoiner(substitutionCollection[newPos + 1])) + // Post-base matras also delimit the search even though their + // category is distinct from ordinary matras. + uint destinationFlags = Flag(Categories.M) | Flag(Categories.MPst) | HalantFlags; + while (newPos > start && (FlagUnsafe(buffer[newPos].Syllable.IndicCategory) & destinationFlags) == 0) { - newPos++; + newPos--; + } + + ref GlyphShapingData current = ref buffer[newPos]; + if (IsHalant(ref current) && current.Syllable.IndicPosition != Positions.Pre_M) + { + // A ZWJ preserves the half-form request and prevents this + // halant from pulling the matra inward. Continue searching + // before the halant instead of moving past the joiner. + if (newPos + 1 < end + && buffer[newPos + 1].Syllable.IndicCategory == Categories.ZWJ + && newPos > start) + { + newPos--; + searchAgain = true; + } + } + else + { + // No standalone halant was found, or this halant belongs + // to the pre-base matra itself, so retain the initial order. + newPos = start; } } - else - { - newPos = start; // No move. - } + while (searchAgain); } - if (start < newPos && substitutionCollection[newPos].IndicShapingEngineInfo?.Position != Positions.Pre_M) + if (start < newPos && buffer[newPos].Syllable.IndicPosition != Positions.Pre_M) { // Now go see if there's actually any matras... for (int i = newPos; i > start; i--) { - if (substitutionCollection[i - 1].IndicShapingEngineInfo?.Position == Positions.Pre_M) + if (buffer[i - 1].Syllable.IndicPosition == Positions.Pre_M) { int oldPos = i - 1; if (oldPos < basePosition && basePosition <= newPos) @@ -1210,11 +1430,23 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou basePosition--; } - substitutionCollection.MoveGlyph(oldPos, newPos); + buffer.MoveGlyph(oldPos, newPos); + buffer.CombineInputStarts(newPos, Math.Min(end, basePosition + 1)); newPos--; } } } + else + { + for (int i = start; i < basePosition; i++) + { + if (buffer[i].Syllable.IndicPosition == Positions.Pre_M) + { + buffer.CombineInputStarts(i, Math.Min(end, basePosition + 1)); + break; + } + } + } } // o Reorder reph: @@ -1233,10 +1465,10 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou // - If repha is encoded separately and in the logical position, we should only // move it if it did NOT ligate. If it ligated, it's probably the font trying // to make it work without the reordering. - GlyphShapingData original = substitutionCollection[start]; + ref GlyphShapingData original = ref buffer[start]; if (start + 1 < end && - original.IndicShapingEngineInfo?.Position == Positions.Ra_To_Become_Reph && - (original.IndicShapingEngineInfo?.Category == Categories.Repha != (original.IsLigated && !original.IsDecomposed))) + original.Syllable.IndicPosition == Positions.Ra_To_Become_Reph && + (original.Syllable.IndicCategory == Categories.Repha != (original.IsLigated && !original.IsDecomposed))) { int newRephPos = start; Positions rephPos = this.indicConfiguration.RephPosition; @@ -1257,15 +1489,15 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou // fixed in shaping engine, there was no case where reph position // will be found on this step. newRephPos = start + 1; - while (newRephPos < basePosition && !IsHalantOrCoeng(substitutionCollection[newRephPos])) + while (newRephPos < basePosition && !IsHalant(ref buffer[newRephPos])) { newRephPos++; } - if (newRephPos < basePosition && IsHalantOrCoeng(substitutionCollection[newRephPos])) + if (newRephPos < basePosition && IsHalant(ref buffer[newRephPos])) { // ->If ZWJ or ZWNJ are following this halant, position is moved after it. - if (newRephPos + 1 < basePosition && IsJoiner(substitutionCollection[newRephPos + 1])) + if (newRephPos + 1 < basePosition && IsJoiner(ref buffer[newRephPos + 1])) { newRephPos++; } @@ -1279,7 +1511,7 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou if (!found && rephPos == Positions.After_Main) { newRephPos = basePosition; - while (newRephPos + 1 < end && substitutionCollection[newRephPos + 1].IndicShapingEngineInfo?.Position <= Positions.After_Main) + while (newRephPos + 1 < end && buffer[newRephPos + 1].Syllable.IndicPosition <= Positions.After_Main) { newRephPos++; } @@ -1296,7 +1528,8 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou if (!found && rephPos == Positions.After_Sub) { newRephPos = basePosition; - while (newRephPos + 1 < end && (substitutionCollection[newRephPos + 1].IndicShapingEngineInfo?.Position & (Positions.Post_C | Positions.After_Post | Positions.SMVD)) == 0) + while (newRephPos + 1 < end + && buffer[newRephPos + 1].Syllable.IndicPosition is not Positions.Post_C and not Positions.After_Post and not Positions.SMVD) { newRephPos++; } @@ -1315,15 +1548,15 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou { // Copied from step 2. newRephPos = start + 1; - while (newRephPos < basePosition && !IsHalantOrCoeng(substitutionCollection[newRephPos])) + while (newRephPos < basePosition && !IsHalant(ref buffer[newRephPos])) { newRephPos++; } - if (newRephPos < basePosition && IsHalantOrCoeng(substitutionCollection[newRephPos])) + if (newRephPos < basePosition && IsHalant(ref buffer[newRephPos])) { // ->If ZWJ or ZWNJ are following this halant, position is moved after it. - if (newRephPos + 1 < basePosition && IsJoiner(substitutionCollection[newRephPos + 1])) + if (newRephPos + 1 < basePosition && IsJoiner(ref buffer[newRephPos + 1])) { newRephPos++; } @@ -1336,7 +1569,7 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou if (!found) { newRephPos = end - 1; - while (newRephPos > start && substitutionCollection[newRephPos].IndicShapingEngineInfo?.Position == Positions.SMVD) + while (newRephPos > start && buffer[newRephPos].Syllable.IndicPosition == Positions.SMVD) { newRephPos--; } @@ -1346,11 +1579,11 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou // However, if it's a plain Consonant,Halant we shouldn't do that. // Uniscribe doesn't do this. // TEST: U+0930,U+094D,U+0915,U+094B,U+094D - if (IsHalantOrCoeng(substitutionCollection[newRephPos])) + if (IsHalant(ref buffer[newRephPos])) { for (int i = basePosition + 1; i < newRephPos; i++) { - if ((FlagUnsafe(substitutionCollection[i].IndicShapingEngineInfo?.Category) & Flag(Categories.M)) != 0) + if ((FlagUnsafe(buffer[i].Syllable.IndicCategory) & Flag(Categories.M)) != 0) { newRephPos--; } @@ -1360,7 +1593,8 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou if (newRephPos != start) { - substitutionCollection.MoveGlyph(start, newRephPos); + buffer.CombineInputStarts(start, newRephPos + 1); + buffer.MoveGlyph(start, newRephPos); } if (start < basePosition && basePosition <= newRephPos) @@ -1377,8 +1611,8 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou { for (int i = basePosition + 1; i < end; i++) { - GlyphShapingData current = substitutionCollection[i]; - if (current.Features.FindIndex(x => x.Tag == PrefTag && x.Enabled) >= 0) + ref GlyphShapingData current = ref buffer[i]; + if ((current.FeatureMask & prefMask) != 0) { // 1. Only reorder a glyph produced by substitution during application // of the feature. (Note that a font may shape a Ra consonant with @@ -1402,7 +1636,7 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou // We want to position matra after them. if (this.ScriptClass is not ScriptClass.Malayalam and not ScriptClass.Tamil) { - while (newPos > start && (FlagUnsafe(substitutionCollection[newPos - 1].IndicShapingEngineInfo?.Category) & (Flag(Categories.M) | HalantOrCoengFlags)) == 0) + while (newPos > start && (FlagUnsafe(buffer[newPos - 1].Syllable.IndicCategory) & (Flag(Categories.M) | HalantFlags)) == 0) { newPos--; } @@ -1410,12 +1644,12 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou // TODO: Remove once we have Kmher shaper. // In Khmer coeng model, a H,Ra can go *after* matras. If it goes after a // split matra, it should be reordered to *before* the left part of such matra. - if (newPos > start && substitutionCollection[newPos - 1].IndicShapingEngineInfo?.Category == Categories.M) + if (newPos > start && buffer[newPos - 1].Syllable.IndicCategory == Categories.M) { int oldPos = i; for (int j = basePosition + 1; j < oldPos; j++) { - if (substitutionCollection[j].IndicShapingEngineInfo?.Category == Categories.M) + if (buffer[j].Syllable.IndicCategory == Categories.M) { newPos--; break; @@ -1424,16 +1658,17 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou } } - if (newPos > start && IsHalantOrCoeng(substitutionCollection[newPos - 1])) + if (newPos > start && IsHalant(ref buffer[newPos - 1])) { // -> If ZWJ or ZWNJ follow this halant, position is moved after it. - if (newPos < end && IsJoiner(substitutionCollection[newPos])) + if (newPos < end && IsJoiner(ref buffer[newPos])) { newPos++; } } - substitutionCollection.MoveGlyph(i, newPos); + buffer.CombineInputStarts(newPos, i + 1); + buffer.MoveGlyph(i, newPos); if (newPos <= basePosition && basePosition < i) { @@ -1446,15 +1681,36 @@ private void FinalReorder(IGlyphShapingCollection collection, int index, int cou } } - // Apply 'init' to the Left Matra if it's a word start. - if (substitutionCollection[start].IndicShapingEngineInfo?.Position == Positions.Pre_M && - (start == 0 || CodePoint.GetGeneralCategory(substitutionCollection[start - 1].CodePoint) is not UnicodeCategory.NonSpacingMark and not UnicodeCategory.Format)) + // Apply 'init' to a left matra only at the start of a word. Letters, + // marks, format controls, and the non-public character categories all + // continue the preceding word for this feature. + bool isInitialMatra = buffer[start].Syllable.IndicPosition == Positions.Pre_M; + bool isWordStart = start == 0; + if (isInitialMatra && !isWordStart) + { + UnicodeCategory previousCategory = CodePoint.GetGeneralCategory(buffer[start - 1].CodePoint); + isWordStart = previousCategory is not ( + UnicodeCategory.Format + or UnicodeCategory.OtherNotAssigned + or UnicodeCategory.PrivateUse + or UnicodeCategory.Surrogate + or UnicodeCategory.LowercaseLetter + or UnicodeCategory.ModifierLetter + or UnicodeCategory.OtherLetter + or UnicodeCategory.TitlecaseLetter + or UnicodeCategory.UppercaseLetter + or UnicodeCategory.SpacingCombiningMark + or UnicodeCategory.EnclosingMark + or UnicodeCategory.NonSpacingMark); + } + + if (isInitialMatra && isWordStart) { - substitutionCollection.EnableShapingFeature(start, InitTag); + buffer.EnableShapingFeature(start, initMask); } start = end; - end = NextSyllable(substitutionCollection, start, max); + end = NextSyllable(buffer, start, max); } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/KhmerShaper.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/KhmerShaper.cs new file mode 100644 index 000000000..8c3ddd3f6 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/KhmerShaper.cs @@ -0,0 +1,452 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using UnicodeTrieGenerator.StateAutomation; +using static SixLabors.Fonts.Unicode.Resources.IndicShapingData; + +namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; + +/// +/// Shapes Khmer syllables and assigns the script's required substitution features. +/// +/// +/// The behavior is transcribed from HarfBuzz 14.2.1, src/hb-ot-shaper-khmer.cc, symbols collect_features_khmer, override_features_khmer, setup_syllables_khmer, reorder_khmer, reorder_consonant_syllable, decompose_khmer, and compose_khmer, and src/hb-ot-shaper-khmer-machine.rl, symbol khmer_syllable_machine. The syllable grammar, feature assignment rules, and split-vowel decompositions are not derivable from the Unicode Character Database. +/// +internal sealed class KhmerShaper : DefaultShaper +{ + /// + /// The bit shift extracting the shaping category from the packed property word. + /// + private const int CategoryShift = 8; + + /// + /// The first split vowel whose leading piece is U+17C1. + /// + private const int FirstSplitVowel = 0x17BE; + + /// + /// The second split vowel whose leading piece is U+17C1. + /// + private const int SecondSplitVowel = 0x17BF; + + /// + /// The third split vowel whose leading piece is U+17C1. + /// + private const int ThirdSplitVowel = 0x17C0; + + /// + /// The fourth split vowel whose leading piece is U+17C1. + /// + private const int FourthSplitVowel = 0x17C4; + + /// + /// The fifth split vowel whose leading piece is U+17C1. + /// + private const int FifthSplitVowel = 0x17C5; + + /// + /// The pre-base piece inserted before each split vowel. + /// + private const int SplitVowelLeadingPiece = 0x17C1; + + /// + /// The dotted circle inserted as the missing base of a broken syllable. + /// + private const int DottedCircle = 0x25CC; + + /// + /// The state machine used to identify Khmer syllables. + /// + private static readonly StateMachine StateMachine = new(Unicode.Resources.KhmerShapingData.StateTable, Unicode.Resources.KhmerShapingData.AcceptingStates, Unicode.Resources.KhmerShapingData.Tags); + + /// + /// The syllable type assigned by each accepting state. + /// + private static readonly SyllableType[] StateSyllableTypes = SyllableTypeMap.FromMachineTags(Unicode.Resources.KhmerShapingData.Tags); + + /// + /// The pre-base forms feature. + /// + private static readonly Tag PrefTag = Tag.Parse("pref"); + + /// + /// The below-base forms feature. + /// + private static readonly Tag BlwfTag = Tag.Parse("blwf"); + + /// + /// The above-base forms feature. + /// + private static readonly Tag AbvfTag = Tag.Parse("abvf"); + + /// + /// The post-base forms feature. + /// + private static readonly Tag PstfTag = Tag.Parse("pstf"); + + /// + /// The conjunct form after Ro feature. + /// + private static readonly Tag CfarTag = Tag.Parse("cfar"); + + /// + /// The pre-base substitutions feature. + /// + private static readonly Tag PresTag = Tag.Parse("pres"); + + /// + /// The above-base substitutions feature. + /// + private static readonly Tag AbvsTag = Tag.Parse("abvs"); + + /// + /// The below-base substitutions feature. + /// + private static readonly Tag BlwsTag = Tag.Parse("blws"); + + /// + /// The post-base substitutions feature. + /// + private static readonly Tag PstsTag = Tag.Parse("psts"); + + /// + /// The font metrics used to resolve the dotted-circle glyph. + /// + private readonly FontMetrics fontMetrics; + + /// + /// The combined syllable setup and initial reordering stage action. + /// + private readonly Action setupAndReorderAction; + + /// + /// The action that clears syllable state after the basic features. + /// + private readonly Action clearSyllablesAction; + + /// + /// Whether the current segment contains a syllable missing its base. + /// + private bool hasBrokenSyllables; + + /// + /// Initializes a new instance of the class. + /// + /// The script classification. + /// The text options. + /// The font metrics used for glyph lookup. + public KhmerShaper(ScriptClass script, TextOptions textOptions, FontMetrics fontMetrics) + : base(script, MarkZeroingMode.None, textOptions) + { + this.FallbackMarkPositioning = false; + this.fontMetrics = fontMetrics; + this.setupAndReorderAction = this.SetupAndReorder; + this.clearSyllablesAction = ClearSyllables; + this.NormalizationMode = NormalizationMode.ComposedDiacriticsNoShortCircuit; + } + + /// + /// + /// The five split-vowel pairs are transcribed from HarfBuzz 14.2.1, src/hb-ot-shaper-khmer.cc, symbol decompose_khmer. They are not canonical decompositions and are not derivable from the Unicode Character Database. + /// + public override bool TryDecompose(CodePoint codePoint, out CodePoint first, out CodePoint second) + { + switch (codePoint.Value) + { + case FirstSplitVowel: + case SecondSplitVowel: + case ThirdSplitVowel: + case FourthSplitVowel: + case FifthSplitVowel: + first = new CodePoint(SplitVowelLeadingPiece); + second = codePoint; + return true; + default: + return base.TryDecompose(codePoint, out first, out second); + } + } + + /// + /// + /// The leading-mark exclusion is transcribed from HarfBuzz 14.2.1, src/hb-ot-shaper-khmer.cc, symbol compose_khmer. It is shaping behavior and is not derivable from the Unicode Character Database. + /// + public override bool TryCompose(CodePoint first, CodePoint second, out CodePoint composed) + { + if (CodePoint.IsMark(first)) + { + composed = default; + return false; + } + + return base.TryCompose(first, second, out composed); + } + + /// + protected override void PlanFeatures(ShapingBuffer buffer, int index, int count) + { + ShapingFeatureFlags basicFlags = ShapingFeatureFlags.ManualJoiners | ShapingFeatureFlags.PerSyllable; + + // Both source pauses precede the first lookup group and have no lookups between them, so one stage callback preserves their ordering without manufacturing an empty feature stage. + this.EnableFeature(buffer, index, count, LoclTag, ShapingFeatureFlags.PerSyllable, this.setupAndReorderAction, null); + this.EnableFeature(buffer, index, count, CcmpTag, ShapingFeatureFlags.PerSyllable); + + this.AddFeature(buffer, index, count, PrefTag, basicFlags, false, null, null); + this.AddFeature(buffer, index, count, BlwfTag, basicFlags, false, null, null); + this.AddFeature(buffer, index, count, AbvfTag, basicFlags, false, null, null); + this.AddFeature(buffer, index, count, PstfTag, basicFlags, false, null, null); + this.AddFeature(buffer, index, count, CfarTag, basicFlags, false, null, this.clearSyllablesAction); + + this.EnableFeature(buffer, index, count, PresTag, ShapingFeatureFlags.ManualJoiners); + this.EnableFeature(buffer, index, count, AbvsTag, ShapingFeatureFlags.ManualJoiners); + this.EnableFeature(buffer, index, count, BlwsTag, ShapingFeatureFlags.ManualJoiners); + this.EnableFeature(buffer, index, count, PstsTag, ShapingFeatureFlags.ManualJoiners); + } + + /// + protected override void PlanPostprocessingFeatures(ShapingBuffer buffer, int index, int count) + { + base.PlanPostprocessingFeatures(buffer, index, count); + + this.EnableFeature(buffer, index, count, CligTag); + this.Features.DisableFeature(LigaTag); + } + + /// + protected override void AssignFeatures(ShapingBuffer buffer, int index, int count) + { + } + + /// + /// Identifies syllables and performs their initial reordering before substitution lookups run. + /// + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. + /// The zero-based start index. + /// The number of elements. + private void SetupAndReorder(ShapePlan plan, ShapingBuffer buffer, int index, int count) + { + if (buffer.Role != ShapingBufferRole.Substitution) + { + return; + } + + this.SetupSyllables(buffer, index, count); + this.Reorder(plan, buffer, index, count); + } + + /// + /// Assigns the syllable and category information consumed by feature matching and reordering. + /// + /// + /// The grammar is transcribed from HarfBuzz 14.2.1, src/hb-ot-shaper-khmer-machine.rl, symbol khmer_syllable_machine. It is not derivable from the Unicode Character Database. + /// + /// The glyph shaping buffer. + /// The zero-based start index. + /// The number of elements. + private void SetupSyllables(ShapingBuffer buffer, int index, int count) + { + this.hasBrokenSyllables = false; + + Span values = count <= 64 ? stackalloc int[count] : new int[count]; + Span categories = count <= 64 ? stackalloc byte[count] : new byte[count]; + ReadOnlySpan categoryToSymbolIds = Unicode.Resources.KhmerShapingData.CategoryToSymbolIds; + for (int i = 0; i < count; i++) + { + int category = UnicodeData.GetIndicShapingProperties((uint)buffer[index + i].CodePoint.Value) >> CategoryShift; + categories[i] = (byte)category; + values[i] = categoryToSymbolIds[category]; + } + + int syllable = 0; + int last = 0; + StateMachine.MatchEnumerator match = StateMachine.EnumerateMatches(values); + while (match.MoveNext()) + { + // The fallback category is a one-character rule in the source machine, so any unmatched input also receives its own syllable number. + while (last < match.StartIndex) + { + syllable++; + ref GlyphShapingData unmatched = ref buffer[index + last]; + unmatched.Syllable.IndicCategory = Categories.X; + unmatched.Syllable.IndicPosition = Positions.End; + unmatched.Syllable.Type = SyllableType.NonIndicCluster; + unmatched.Syllable.Number = syllable; + last++; + } + + syllable++; + SyllableType syllableType = StateSyllableTypes[match.TagState]; + this.hasBrokenSyllables |= syllableType == SyllableType.BrokenCluster; + + for (int i = match.StartIndex; i <= match.EndIndex; i++) + { + ref GlyphShapingData data = ref buffer[index + i]; + data.Syllable.IndicCategory = (Categories)categories[i]; + data.Syllable.IndicPosition = Positions.End; + data.Syllable.Type = syllableType; + data.Syllable.Number = syllable; + } + + last = match.EndIndex + 1; + } + + while (last < count) + { + syllable++; + ref GlyphShapingData unmatched = ref buffer[index + last]; + unmatched.Syllable.IndicCategory = Categories.X; + unmatched.Syllable.IndicPosition = Positions.End; + unmatched.Syllable.Type = SyllableType.NonIndicCluster; + unmatched.Syllable.Number = syllable; + last++; + } + } + + /// + /// Inserts missing bases and reorders each consonant or broken syllable. + /// + /// + /// This pass is transcribed from HarfBuzz 14.2.1, src/hb-ot-shaper-khmer.cc, symbols reorder_khmer, reorder_syllable_khmer, and reorder_consonant_syllable. Its feature-mask assignment and movement rules are not derivable from the Unicode Character Database. + /// + /// The plan whose feature masks are assigned. + /// The glyph shaping buffer. + /// The zero-based start index. + /// The number of elements. + private void Reorder(ShapePlan plan, ShapingBuffer buffer, int index, int count) + { + int max = index + count; + int start = index; + int end = NextSyllable(buffer, start, max); + + if (this.hasBrokenSyllables && this.fontMetrics.TryGetGlyphId(new CodePoint(DottedCircle), out ushort circleId)) + { + while (start < max) + { + if (buffer[start].Syllable.Type == SyllableType.BrokenCluster) + { + buffer.InsertDottedCircle(start, circleId); + buffer[start].Syllable.IndicCategory = Categories.Dotted_Circle; + buffer[start].Syllable.IndicPosition = Positions.End; + end++; + max++; + } + + start = end; + end = NextSyllable(buffer, start, max); + } + + start = index; + end = NextSyllable(buffer, start, max); + } + + uint postBaseMask = plan.Features.GetMask(BlwfTag) | plan.Features.GetMask(AbvfTag) | plan.Features.GetMask(PstfTag); + uint prefMask = plan.Features.GetMask(PrefTag); + uint cfarMask = plan.Features.GetMask(CfarTag); + + while (start < max) + { + SyllableType type = buffer[start].Syllable.Type; + if (type is SyllableType.ConsonantSyllable or SyllableType.BrokenCluster) + { + ReorderConsonantSyllable(buffer, start, end, postBaseMask, prefMask, cfarMask); + } + + start = end; + end = NextSyllable(buffer, start, max); + } + } + + /// + /// Assigns basic feature masks and moves pre-base pieces within one syllable. + /// + /// The glyph shaping buffer. + /// The first record in the syllable. + /// The exclusive end of the syllable. + /// The combined below-, above-, and post-base forms mask. + /// The pre-base forms mask. + /// The conjunct form after Ro mask. + private static void ReorderConsonantSyllable(ShapingBuffer buffer, int start, int end, uint postBaseMask, uint prefMask, uint cfarMask) + { + for (int i = start + 1; i < end; i++) + { + buffer.EnableShapingFeature(i, postBaseMask); + } + + int coengCount = 0; + for (int i = start + 1; i < end; i++) + { + if (buffer[i].Syllable.IndicCategory == Categories.H && coengCount <= 2 && i + 1 < end) + { + coengCount++; + + if (buffer[i + 1].Syllable.IndicCategory == Categories.Ra) + { + buffer.EnableShapingFeature(i, prefMask); + buffer.EnableShapingFeature(i + 1, prefMask); + + buffer.CombineInputStarts(start, i + 2); + + // Move the two records independently so their order remains H, Ra at the start. + buffer.MoveGlyph(i, start); + buffer.MoveGlyph(i + 1, start + 1); + + for (int j = i + 2; j < end; j++) + { + buffer.EnableShapingFeature(j, cfarMask); + } + + coengCount = 2; + } + } + else if (buffer[i].Syllable.IndicCategory == Categories.VPre) + { + buffer.CombineInputStarts(start, i + 1); + buffer.MoveGlyph(i, start); + } + } + } + + /// + /// Clears syllable state once the features constrained by it have run. + /// + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. + /// The zero-based start index. + /// The number of elements. + private static void ClearSyllables(ShapePlan plan, ShapingBuffer buffer, int index, int count) + { + if (buffer.Role != ShapingBufferRole.Substitution) + { + return; + } + + int end = index + count; + for (int i = index; i < end; i++) + { + buffer[i].Syllable = default; + } + } + + /// + /// Finds the exclusive end of the syllable beginning at the given index. + /// + /// The glyph shaping buffer. + /// The first record in the syllable. + /// The exclusive segment end. + /// The exclusive end of the syllable. + private static int NextSyllable(ShapingBuffer buffer, int index, int end) + { + if (index >= end) + { + return index; + } + + int syllable = buffer[index].Syllable.Number; + while (++index < end && buffer[index].Syllable.Number == syllable) + { + } + + return index; + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/MyanmarShaper.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/MyanmarShaper.cs index 99b3cdf94..c15e6f880 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/MyanmarShaper.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/MyanmarShaper.cs @@ -13,52 +13,121 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; /// internal sealed class MyanmarShaper : DefaultShaper { - /// The state machine for Myanmar syllable identification. + /// + /// The bit shift extracting the shaping category from a packed Indic shaping + /// property word; the category occupies the upper byte. + /// + private const int MyanmarCategoryShift = 8; + + /// + /// The mask extracting the zero-based shaping position from a packed Indic + /// shaping property word; the position occupies the lower byte. + /// + private const int MyanmarPositionMask = 0xFF; + + /// + /// The state machine for Myanmar syllable identification. + /// private static readonly StateMachine StateMachine = new( Unicode.Resources.MyanmarShapingData.StateTable, Unicode.Resources.MyanmarShapingData.AcceptingStates, Unicode.Resources.MyanmarShapingData.Tags); - /// Maps Myanmar shaping category codes to compact DFA symbol indices. + /// + /// The syllable type for each machine state, translated from the tag rows once so + /// match handling never maps rule name strings. + /// + private static readonly SyllableType[] StateSyllableTypes = + SyllableTypeMap.FromMachineTags(Unicode.Resources.MyanmarShapingData.Tags); + + /// + /// Maps Myanmar shaping category codes to compact DFA symbol indices. + /// private static readonly int[] CategoryToSymbolId = BuildCategoryToSymbolId(); - /// The 'rphf' (reph forms) feature tag. + /// + /// The 'rphf' (reph forms) feature tag. + /// private static readonly Tag RphfTag = Tag.Parse("rphf"); - /// The 'pref' (pre-base forms) feature tag. + /// + /// The 'pref' (pre-base forms) feature tag. + /// private static readonly Tag PrefTag = Tag.Parse("pref"); - /// The 'blwf' (below-base forms) feature tag. + /// + /// The 'blwf' (below-base forms) feature tag. + /// private static readonly Tag BlwfTag = Tag.Parse("blwf"); - /// The 'pstf' (post-base forms) feature tag. + /// + /// The 'pstf' (post-base forms) feature tag. + /// private static readonly Tag PstfTag = Tag.Parse("pstf"); - /// The 'pres' (pre-base substitutions) feature tag. + /// + /// The 'pres' (pre-base substitutions) feature tag. + /// private static readonly Tag PresTag = Tag.Parse("pres"); - /// The 'abvs' (above-base substitutions) feature tag. + /// + /// The 'abvs' (above-base substitutions) feature tag. + /// private static readonly Tag AbvsTag = Tag.Parse("abvs"); - /// The 'blws' (below-base substitutions) feature tag. + /// + /// The 'blws' (below-base substitutions) feature tag. + /// private static readonly Tag BlwsTag = Tag.Parse("blws"); - /// The 'psts' (post-base substitutions) feature tag. + /// + /// The 'psts' (post-base substitutions) feature tag. + /// private static readonly Tag PstsTag = Tag.Parse("psts"); - /// Dotted circle code point (U+25CC) used as a placeholder base. + /// + /// Dotted circle code point (U+25CC) used as a placeholder base. + /// private const int DottedCircle = 0x25cc; - /// The text options. + /// + /// The text options. + /// private readonly TextOptions textOptions; - /// The font metrics used for glyph lookups. + /// + /// The font metrics used for glyph lookups. + /// private readonly FontMetrics fontMetrics; - /// Whether any broken clusters were detected during syllable setup. + /// + /// Whether any broken clusters were detected during syllable setup. + /// private bool hasBrokenClusters; + /// + /// The syllable setup pause, converted to a delegate once so per-pass feature + /// planning never allocates for the conversion. + /// + private readonly Action setupSyllablesAction; + + /// + /// The initial reorder pause, converted to a delegate once so per-pass feature + /// planning never allocates for the conversion. + /// + private readonly Action initialReorderAction; + + /// + /// The no-op action that separates basic features into individual lookup stages. + /// + private readonly Action pauseAction; + + /// + /// The action that clears syllable state after the basic features. + /// + private readonly Action clearSyllablesAction; + /// /// Initializes a new instance of the class. /// @@ -68,36 +137,53 @@ internal sealed class MyanmarShaper : DefaultShaper public MyanmarShaper(ScriptClass script, TextOptions textOptions, FontMetrics fontMetrics) : base(script, MarkZeroingMode.PreGPos, textOptions) { + this.FallbackMarkPositioning = false; this.textOptions = textOptions; this.fontMetrics = fontMetrics; + this.setupSyllablesAction = this.SetupSyllables; + this.initialReorderAction = this.InitialReorder; + this.pauseAction = Pause; + this.clearSyllablesAction = ClearSyllables; + + // Every character comes apart first, even one the font already draws whole. + // This shaper divides a run into syllables and moves their pieces about, so a + // character standing for several pieces must be split before the division can + // see them. + this.NormalizationMode = NormalizationMode.ComposedDiacriticsNoShortCircuit; } /// - protected override void PlanFeatures(IGlyphShapingCollection collection, int index, int count) + protected override void PlanFeatures(ShapingBuffer buffer, int index, int count) { - this.AddFeature(collection, index, count, LoclTag, preAction: this.SetupSyllables); - this.AddFeature(collection, index, count, CcmpTag); - - this.AddFeature(collection, index, count, RphfTag, preAction: this.InitialReorder); - this.AddFeature(collection, index, count, PrefTag); - this.AddFeature(collection, index, count, BlwfTag); - this.AddFeature(collection, index, count, PstfTag); - - this.AddFeature(collection, index, count, PresTag); - this.AddFeature(collection, index, count, AbvsTag); - this.AddFeature(collection, index, count, BlwsTag); - this.AddFeature(collection, index, count, PstsTag); + this.EnableFeature(buffer, index, count, LoclTag, ShapingFeatureFlags.PerSyllable, this.setupSyllablesAction, null); + this.EnableFeature(buffer, index, count, CcmpTag, ShapingFeatureFlags.PerSyllable); + + // Each basic feature consumes the previous feature's output. Explicit + // boundaries preserve that order even when the font stores their lookups + // in a different numerical order. + this.EnableFeature(buffer, index, count, RphfTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable, this.initialReorderAction, this.pauseAction); + this.EnableFeature(buffer, index, count, PrefTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable, null, this.pauseAction); + this.EnableFeature(buffer, index, count, BlwfTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable, null, this.pauseAction); + this.EnableFeature(buffer, index, count, PstfTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable, null, this.clearSyllablesAction); + + // Syllable scoping ends with the basic forms. The presentation features + // below apply together over the resulting glyph stream. + this.EnableFeature(buffer, index, count, PresTag, ShapingFeatureFlags.ManualZwj); + this.EnableFeature(buffer, index, count, AbvsTag, ShapingFeatureFlags.ManualZwj); + this.EnableFeature(buffer, index, count, BlwsTag, ShapingFeatureFlags.ManualZwj); + this.EnableFeature(buffer, index, count, PstsTag, ShapingFeatureFlags.ManualZwj); } /// /// Identifies Myanmar syllables using the state machine and assigns shaping info to each glyph. /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based start index. /// The number of elements to process. - private void SetupSyllables(IGlyphShapingCollection collection, int index, int count) + private void SetupSyllables(ShapePlan plan, ShapingBuffer buffer, int index, int count) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role != ShapingBufferRole.Substitution) { return; } @@ -105,6 +191,7 @@ private void SetupSyllables(IGlyphShapingCollection collection, int index, int c this.hasBrokenClusters = false; Span values = count <= 64 ? stackalloc int[count] : new int[count]; + Span shapingProps = count <= 64 ? stackalloc ushort[count] : new ushort[count]; for (int i = index; i < index + count; i++) { @@ -116,48 +203,51 @@ private void SetupSyllables(IGlyphShapingCollection collection, int index, int c // machine expects its input alphabet to be dense 0..N-1, matching the // sequential IDs assigned in GenerateMyanmarShapingData. // - // CategoryToSymbolId[(int)my] performs this mapping, ensuring that - // every codepoint is presented to the DFA using the correct compact - // symbol index. - CodePoint codePoint = substitutionCollection[i].CodePoint; - MyanmarCategories my = (MyanmarCategories)IndicShapingCategory(codePoint); - values[i - index] = CategoryToSymbolId[(int)my]; + // The property word is fetched once per glyph and stashed: the match + // loop below derives both the category and position lanes from it + // rather than walking the trie again. + CodePoint codePoint = buffer[i].CodePoint; + ushort props = (ushort)UnicodeData.GetIndicShapingProperties((uint)codePoint.Value); + shapingProps[i - index] = props; + values[i - index] = CategoryToSymbolId[props >> MyanmarCategoryShift]; } int syllable = 0; int last = 0; - foreach (StateMatch match in StateMachine.Match(values)) + StateMachine.MatchEnumerator match = StateMachine.EnumerateMatches(values); + while (match.MoveNext()) { if (match.StartIndex > last) { ++syllable; for (int i = last; i < match.StartIndex; i++) { - GlyphShapingData data = substitutionCollection[i + index]; - data.IndicShapingEngineInfo = new(Categories.X, Positions.End, "non_indic_cluster", syllable); + ref GlyphShapingData data = ref buffer[i + index]; + data.Syllable.IndicCategory = Categories.X; + data.Syllable.IndicPosition = Positions.End; + data.Syllable.Type = SyllableType.NonIndicCluster; + data.Syllable.Number = syllable; } } ++syllable; + SyllableType syllableType = StateSyllableTypes[match.TagState]; + if (syllableType == SyllableType.BrokenCluster) + { + this.hasBrokenClusters = true; + } + // Create shaper info. for (int i = match.StartIndex; i <= match.EndIndex; i++) { - GlyphShapingData data = substitutionCollection[i + index]; - CodePoint codePoint = data.CodePoint; - - string syllableType = match.Tags[0]; - - if (syllableType == "broken_cluster") - { - this.hasBrokenClusters = true; - } + ref GlyphShapingData data = ref buffer[i + index]; + ushort props = shapingProps[i]; - data.IndicShapingEngineInfo = new( - (Categories)IndicShapingCategory(codePoint), - (Positions)IndicShapingPosition(codePoint), - syllableType, - syllable); + data.Syllable.IndicCategory = (Categories)(props >> MyanmarCategoryShift); + data.Syllable.IndicPosition = (Positions)((props & MyanmarPositionMask) + 1); + data.Syllable.Type = syllableType; + data.Syllable.Number = syllable; } last = match.EndIndex + 1; @@ -168,8 +258,11 @@ private void SetupSyllables(IGlyphShapingCollection collection, int index, int c ++syllable; for (int i = last; i < count; i++) { - GlyphShapingData data = substitutionCollection[i + index]; - data.IndicShapingEngineInfo = new(Categories.X, Positions.End, "non_indic_cluster", syllable); + ref GlyphShapingData data = ref buffer[i + index]; + data.Syllable.IndicCategory = Categories.X; + data.Syllable.IndicPosition = Positions.End; + data.Syllable.Type = SyllableType.NonIndicCluster; + data.Syllable.Number = syllable; } } } @@ -178,12 +271,13 @@ private void SetupSyllables(IGlyphShapingCollection collection, int index, int c /// Performs the initial reordering pass for Myanmar consonant syllables, including /// dotted circle insertion for broken clusters. /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based start index. /// The number of elements to process. - private void InitialReorder(IGlyphShapingCollection collection, int index, int count) + private void InitialReorder(ShapePlan plan, ShapingBuffer buffer, int index, int count) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role != ShapingBufferRole.Substitution) { return; } @@ -191,7 +285,7 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c FontMetrics fontMetrics = this.fontMetrics; int max = index + count; int start = index; - int end = NextSyllable(substitutionCollection, index, max); + int end = NextSyllable(buffer, index, max); if (this.hasBrokenClusters) { @@ -200,83 +294,75 @@ private void InitialReorder(IGlyphShapingCollection collection, int index, int c Span glyphs = stackalloc ushort[2]; while (start < max) { - GlyphShapingData data = substitutionCollection[start]; - IndicShapingEngineInfo? dataInfo = data.IndicShapingEngineInfo; - string? type = dataInfo?.SyllableType; - - if (type == "broken_cluster") + if (buffer[start].Syllable.Type == SyllableType.BrokenCluster) { // Insert after possible Repha. int i = start; for (i = start; i < end; i++) { - if (substitutionCollection[i].IndicShapingEngineInfo?.Category != Categories.Repha) + if (buffer[i].Syllable.IndicCategory != Categories.Repha) { break; } } - GlyphShapingData current = substitutionCollection[i]; + ref GlyphShapingData current = ref buffer[i]; glyphs[0] = current.GlyphId; glyphs[1] = circleId; - substitutionCollection.Replace(i, glyphs, KnownFeatureTags.GlyphCompositionDecomposition); + buffer.Replace(i, glyphs, KnownFeatureTags.GlyphCompositionDecomposition); // Update shaping info for newly inserted data. - GlyphShapingData dotted = substitutionCollection[i + 1]; - dotted.IndicShapingEngineInfo!.Category = Categories.Dotted_Circle; + ref GlyphShapingData dotted = ref buffer[i + 1]; + dotted.Syllable.IndicCategory = Categories.Dotted_Circle; end++; max++; } start = end; - end = NextSyllable(substitutionCollection, start, max); + end = NextSyllable(buffer, start, max); } start = index; - end = NextSyllable(substitutionCollection, index, max); + end = NextSyllable(buffer, index, max); } } while (start < max) { - GlyphShapingData data = substitutionCollection[start]; - IndicShapingEngineInfo? dataInfo = data.IndicShapingEngineInfo; - string? type = dataInfo?.SyllableType; - - switch (type) + switch (buffer[start].Syllable.Type) { // We already inserted dotted-circles, so just call the consonant_syllable. - case "broken_cluster": - case "consonant_syllable": - ReorderConsonantSyllable(substitutionCollection, start, end); + case SyllableType.BrokenCluster: + case SyllableType.ConsonantSyllable: + ReorderConsonantSyllable(buffer, start, end); break; default: break; } start = end; - end = NextSyllable(substitutionCollection, start, max); + end = NextSyllable(buffer, start, max); } } /// /// Reorders glyphs within a single Myanmar consonant syllable according to the Myanmar shaping spec. /// - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The start index of the syllable. /// The exclusive end index of the syllable. - private static void ReorderConsonantSyllable(GlyphSubstitutionCollection substitutionCollection, int start, int end) + private static void ReorderConsonantSyllable(ShapingBuffer buffer, int start, int end) { int basePosition = end; bool hasReph = false; { int limit = start; if (start + 3 <= end && - substitutionCollection[start].IndicShapingEngineInfo?.MyanmarCategory == MyanmarCategories.Ra && - substitutionCollection[start + 1].IndicShapingEngineInfo?.MyanmarCategory == MyanmarCategories.As && - substitutionCollection[start + 2].IndicShapingEngineInfo?.MyanmarCategory == MyanmarCategories.H) + buffer[start].Syllable.MyanmarCategory == MyanmarCategories.Ra && + buffer[start + 1].Syllable.MyanmarCategory == MyanmarCategories.As && + buffer[start + 2].Syllable.MyanmarCategory == MyanmarCategories.H) { limit += 3; basePosition = start; @@ -291,7 +377,7 @@ private static void ReorderConsonantSyllable(GlyphSubstitutionCollection substit for (int i = limit; i < end; i++) { - if (IsConsonant(substitutionCollection[i])) + if (IsConsonant(ref buffer[i])) { basePosition = i; break; @@ -305,17 +391,17 @@ private static void ReorderConsonantSyllable(GlyphSubstitutionCollection substit int i = start; for (; i < start + (hasReph ? 3 : 0); i++) { - substitutionCollection[i].IndicShapingEngineInfo!.Position = Positions.After_Main; + buffer[i].Syllable.IndicPosition = Positions.After_Main; } for (; i < basePosition; i++) { - substitutionCollection[i].IndicShapingEngineInfo!.Position = Positions.Pre_C; + buffer[i].Syllable.IndicPosition = Positions.Pre_C; } if (i < end) { - substitutionCollection[i].IndicShapingEngineInfo!.Position = Positions.Base_C; + buffer[i].Syllable.IndicPosition = Positions.Base_C; i++; } @@ -324,65 +410,77 @@ private static void ReorderConsonantSyllable(GlyphSubstitutionCollection substit // The following loop may be ugly, but it implements all of Myanmar reordering! for (; i < end; i++) { - GlyphShapingData data = substitutionCollection[i]; - IndicShapingEngineInfo info = data.IndicShapingEngineInfo!; + ref GlyphShapingData data = ref buffer[i]; // Pre-base reordering - if (info.MyanmarCategory == MyanmarCategories.MR) + if (data.Syllable.MyanmarCategory == MyanmarCategories.MR) { - info.Position = Positions.Pre_C; + data.Syllable.IndicPosition = Positions.Pre_C; continue; } // Left matra - if (info.MyanmarCategory == MyanmarCategories.VPre) + if (data.Syllable.MyanmarCategory == MyanmarCategories.VPre) { - info.Position = Positions.Pre_M; + data.Syllable.IndicPosition = Positions.Pre_M; continue; } - if (info.MyanmarCategory == MyanmarCategories.VS) + if (data.Syllable.MyanmarCategory == MyanmarCategories.VS) { - info.Position = substitutionCollection[i - 1].IndicShapingEngineInfo!.Position; + data.Syllable.IndicPosition = buffer[i - 1].Syllable.IndicPosition; continue; } - if (pos == Positions.After_Main && info.MyanmarCategory == MyanmarCategories.VBlw) + if (pos == Positions.After_Main && data.Syllable.MyanmarCategory == MyanmarCategories.VBlw) { pos = Positions.Below_C; - info.Position = pos; + data.Syllable.IndicPosition = pos; continue; } - if (pos == Positions.Below_C && info.MyanmarCategory == MyanmarCategories.A) + if (pos == Positions.Below_C && data.Syllable.MyanmarCategory == MyanmarCategories.A) { - info.Position = Positions.Before_Sub; + data.Syllable.IndicPosition = Positions.Before_Sub; continue; } - if (pos == Positions.Below_C && info.MyanmarCategory == MyanmarCategories.VBlw) + if (pos == Positions.Below_C && data.Syllable.MyanmarCategory == MyanmarCategories.VBlw) { - info.Position = pos; + data.Syllable.IndicPosition = pos; continue; } - if (pos == Positions.Below_C && info.MyanmarCategory != MyanmarCategories.A) + if (pos == Positions.Below_C && data.Syllable.MyanmarCategory != MyanmarCategories.A) { pos = Positions.After_Sub; - info.Position = pos; + data.Syllable.IndicPosition = pos; continue; } - info.Position = pos; + data.Syllable.IndicPosition = pos; } } - substitutionCollection.Sort(start, end, (a, b) => + // HarfBuzz's Myanmar stable sort merges the complete source range crossed + // by each moved char. Keep that merge beside the movement: the general + // ShapingBuffer sort deliberately preserves separate codepoint identities + // for shapers whose reordering rules do not merge every moved range. + for (int i = start + 1; i < end; i++) { - int pa = a.IndicShapingEngineInfo?.Position != null ? (int)a.IndicShapingEngineInfo.Position : 0; - int pb = b.IndicShapingEngineInfo?.Position != null ? (int)b.IndicShapingEngineInfo.Position : 0; - return pa - pb; - }); + int j = i; + Positions position = buffer[i].Syllable.IndicPosition; + while (j > start && buffer[j - 1].Syllable.IndicPosition > position) + { + j--; + } + + if (i != j) + { + buffer.CombineInputStarts(j, i + 1); + buffer.MoveGlyph(i, j); + } + } // Flip left-matra sequence. int firstLeftMatra = end; @@ -390,7 +488,7 @@ private static void ReorderConsonantSyllable(GlyphSubstitutionCollection substit for (int i = start; i < end; i++) { - if (substitutionCollection[i].IndicShapingEngineInfo?.Position == Positions.Pre_M) + if (buffer[i].Syllable.IndicPosition == Positions.Pre_M) { if (firstLeftMatra == end) { @@ -405,15 +503,15 @@ private static void ReorderConsonantSyllable(GlyphSubstitutionCollection substit if (firstLeftMatra < lastLeftMatra) { // No need to merge clusters, done already? - substitutionCollection.ReverseRange(firstLeftMatra, lastLeftMatra + 1); + buffer.ReverseRange(firstLeftMatra, lastLeftMatra + 1); // Reverse back VS, etc. int i = firstLeftMatra; for (int j = i; j <= lastLeftMatra; j++) { - if (substitutionCollection[j].IndicShapingEngineInfo?.MyanmarCategory == MyanmarCategories.VPre) + if (buffer[j].Syllable.MyanmarCategory == MyanmarCategories.VPre) { - substitutionCollection.ReverseRange(i, j + 1); + buffer.ReverseRange(i, j + 1); i = j + 1; } } @@ -425,27 +523,59 @@ private static void ReorderConsonantSyllable(GlyphSubstitutionCollection substit /// /// The glyph shaping data. /// if the glyph is a consonant. - private static bool IsConsonant(GlyphShapingData data) - => data.IndicShapingEngineInfo != null && (FlagUnsafe(data.IndicShapingEngineInfo.MyanmarCategory) & MyanmarConsonantFlags) != 0; + private static bool IsConsonant(ref GlyphShapingData data) + => data.Syllable.Type != SyllableType.None && (FlagUnsafe(data.Syllable.MyanmarCategory) & MyanmarConsonantFlags) != 0; + + /// + /// Separates two basic feature stages without mutating the shaping buffer. + /// + /// The shaping plan. + /// The shaping buffer. + /// The zero-based index of the first record. + /// The number of records in the segment. + private static void Pause(ShapePlan plan, ShapingBuffer buffer, int index, int count) + { + } + + /// + /// Clears syllable state once the syllable-scoped basic features have run. + /// + /// The shaping plan. + /// The shaping buffer. + /// The zero-based index of the first record. + /// The number of records in the segment. + private static void ClearSyllables(ShapePlan plan, ShapingBuffer buffer, int index, int count) + { + if (buffer.Role != ShapingBufferRole.Substitution) + { + return; + } + + int end = index + count; + for (int i = index; i < end; i++) + { + buffer[i].Syllable = default; + } + } /// - /// Finds the start index of the next syllable in the collection. + /// Finds the start index of the next syllable in the buffer. /// - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The current index. /// The maximum index bound. /// The start index of the next syllable. - private static int NextSyllable(GlyphSubstitutionCollection collection, int index, int count) + private static int NextSyllable(ShapingBuffer buffer, int index, int count) { if (index >= count) { return index; } - int? syllable = collection[index].IndicShapingEngineInfo?.Syllable; + int syllable = buffer[index].Syllable.Number; while (++index < count) { - if (collection[index].IndicShapingEngineInfo?.Syllable != syllable) + if (buffer[index].Syllable.Number != syllable) { break; } @@ -460,15 +590,17 @@ private static int NextSyllable(GlyphSubstitutionCollection collection, int inde /// The code point. /// The shaping category value. private static int IndicShapingCategory(CodePoint codePoint) - => UnicodeData.GetIndicShapingProperties((uint)codePoint.Value) >> 8; + => UnicodeData.GetIndicShapingProperties((uint)codePoint.Value) >> MyanmarCategoryShift; /// - /// Gets the Indic shaping position for a code point (lower 8 bits as a bit flag). + /// Gets the Indic shaping position for a code point. The trie stores the position + /// zero-based; adding one maps it onto the ordinal enum whose zero is the + /// unassigned sentinel. /// /// The code point. - /// The shaping position as a bit flag. + /// The shaping position ordinal. private static int IndicShapingPosition(CodePoint codePoint) - => 1 << (UnicodeData.GetIndicShapingProperties((uint)codePoint.Value) & 0xFF); + => (UnicodeData.GetIndicShapingProperties((uint)codePoint.Value) & MyanmarPositionMask) + 1; /// /// Builds a lookup table mapping Myanmar shaping category codes to compact DFA symbol indices. diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/MyanmarZawgyiShaper.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/MyanmarZawgyiShaper.cs new file mode 100644 index 000000000..78d251ddf --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/MyanmarZawgyiShaper.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; + +namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; + +/// +/// Shapes text encoded using the legacy Zawgyi convention. +/// +internal sealed class MyanmarZawgyiShaper : DefaultShaper +{ + /// + /// Initializes a new instance of the class. + /// + /// The script classification. + /// The text options. + public MyanmarZawgyiShaper(ScriptClass script, TextOptions textOptions) + : base(script, MarkZeroingMode.None, textOptions) + { + // Zawgyi assigns meaning to its encoded character sequence directly. + // Canonical normalization would rewrite that sequence as Unicode text. + this.NormalizationMode = NormalizationMode.None; + + // Retain the font's encoded advances and positioning. Zawgyi shaping does + // not apply the mark-zeroing or fallback-positioning conventions used by + // Unicode script engines. + this.FallbackMarkPositioning = false; + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/NormalizationMode.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/NormalizationMode.cs new file mode 100644 index 000000000..3f3f1925d --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/NormalizationMode.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; + +/// +/// States how far a shaper wants its text taken apart and put back together before +/// it is shaped. +/// +internal enum NormalizationMode +{ + /// + /// Leaves the text as it was written. Used by a script whose own engine reads + /// the characters as they stand. + /// + None, + + /// + /// Takes the text apart as far as the font allows and leaves it apart. + /// + Decomposed, + + /// + /// Takes the text apart, orders the marks, then joins the marks back onto the + /// character they follow wherever the font offers the joined form. A character + /// standing on its own that the font already draws is left untouched. + /// + ComposedDiacritics, + + /// + /// As , but every character is taken apart + /// first, including one standing on its own that the font already draws. Used by + /// a script whose engine needs the parts of a character it would otherwise + /// never see. + /// + ComposedDiacriticsNoShortCircuit +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ShaperFactory.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ShaperFactory.cs index 96aed0d1d..19bfddcfd 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ShaperFactory.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ShaperFactory.cs @@ -13,6 +13,32 @@ internal static class ShaperFactory /// The 'mym2' (Myanmar v2) script tag used to distinguish Myanmar shaper versions. private static readonly Tag Mym2Tag = Tag.Parse("mym2"); + /// The 'latn' (Latin) script tag, which a font can carry in place of the one asked for. + private static readonly Tag LatnTag = Tag.Parse("latn"); + + /// + /// The last character of a script tag naming the third revision of its + /// script, the revision the universal engine describes. + /// + private const uint ThirdRevisionTagSuffix = (byte)'3'; + + /// + /// Determines whether the font offers nothing designed for the script, so + /// that the tag settled on is the default one or a Latin one. + /// + /// The script tag found in the font. + /// when the font was not designed for the script. + private static bool IsDefaultDesign(Tag unicodeScriptTag) + => unicodeScriptTag == default || unicodeScriptTag == LatnTag; + + /// + /// Determines whether the tag names the third revision of its script. + /// + /// The script tag found in the font. + /// when the tag names the third revision. + private static bool IsThirdRevision(Tag unicodeScriptTag) + => (unicodeScriptTag.Value & 0xFF) == ThirdRevisionTagSuffix; + /// /// Creates a shaper based on the given script language. /// @@ -20,23 +46,33 @@ internal static class ShaperFactory /// The unicode script tag found in the font matching the script. /// The current font metrics. /// The global text options. + /// The language system candidates used to resolve feature availability. /// A shaper for the given script. public static BaseShaper Create( ScriptClass script, Tag unicodeScriptTag, FontMetrics fontMetrics, - TextOptions textOptions) + TextOptions textOptions, + Tag[] languageTags) => script switch { - // Arabic + // Arabic and Syriac ScriptClass.Arabic - or ScriptClass.Mongolian or ScriptClass.Syriac - or ScriptClass.Nko - or ScriptClass.PhagsPa - or ScriptClass.Mandaic - or ScriptClass.Manichaean - or ScriptClass.PsalterPahlavi => new ArabicShaper(script, textOptions), + + // Arabic keeps its script shaper when the font has no matching script + // system because Arabic alone has presentation-form fallback shaping. + // Syriac requires a non-default script system selected from the font. + // + // The script shaper assigns joining forms along the horizontal inline + // axis. Forced vertical layout keeps glyphs upright and stacks them, so + // generic shaping applies common and vertical features without contextual + // joining. Mixed vertical layout rotates Arabic and Syriac glyphs, keeping + // their horizontal RTL inline direction and script-specific joining. + => (unicodeScriptTag != default || script == ScriptClass.Arabic) + && !textOptions.LayoutMode.IsVertical() + ? new ArabicShaper(script, textOptions, fontMetrics, languageTags) + : new DefaultShaper(script, textOptions), // Hebrew ScriptClass.Hebrew => new HebrewShaper(script, textOptions, fontMetrics, unicodeScriptTag != default), @@ -58,7 +94,19 @@ or ScriptClass.Malayalam or ScriptClass.Oriya or ScriptClass.Tamil or ScriptClass.Telugu - or ScriptClass.Khmer => new IndicShaper(script, unicodeScriptTag, textOptions, fontMetrics), + + // A font whose only script is the default one, or the Latin one we + // would otherwise pick arbitrarily, was not designed for the script, + // so it is shaped plainly. A font designed to the third revision of + // the script is shaped by the universal engine, which is what that + // revision describes. + => IsDefaultDesign(unicodeScriptTag) + ? new DefaultShaper(script, textOptions) + : IsThirdRevision(unicodeScriptTag) + ? new UniversalShaper(script, textOptions, fontMetrics) + : new IndicShaper(script, unicodeScriptTag, textOptions, fontMetrics), + + ScriptClass.Khmer => new KhmerShaper(script, textOptions, fontMetrics), // Myanmar ScriptClass.Myanmar @@ -74,6 +122,8 @@ or ScriptClass.Telugu ? new MyanmarShaper(script, textOptions, fontMetrics) : new DefaultShaper(script, textOptions), + ScriptClass.MyanmarZawgyi => new MyanmarZawgyiShaper(script, textOptions), + // Universal ScriptClass.Balinese or ScriptClass.Batak @@ -127,6 +177,46 @@ or ScriptClass.BeriaErfe or ScriptClass.Sidetic or ScriptClass.TaiYo or ScriptClass.TolongSiki + or ScriptClass.Ahom + or ScriptClass.Multani + or ScriptClass.Miao + or ScriptClass.Adlam + or ScriptClass.Bhaiksuki + or ScriptClass.Marchen + or ScriptClass.Newa + or ScriptClass.MasaramGondi + or ScriptClass.Soyombo + or ScriptClass.ZanabazarSquare + or ScriptClass.Dogra + or ScriptClass.GunjalaGondi + or ScriptClass.HanifiRohingya + or ScriptClass.Makasar + or ScriptClass.Medefaidrin + or ScriptClass.OldSogdian + or ScriptClass.Sogdian + or ScriptClass.Elymaic + or ScriptClass.Nandinagari + or ScriptClass.NyiakengPuachueHmong + or ScriptClass.Wancho + or ScriptClass.Chorasmian + or ScriptClass.DivesAkuru + or ScriptClass.KhitanSmallScript + or ScriptClass.Yezidi + or ScriptClass.CyproMinoan + or ScriptClass.OldUyghur + or ScriptClass.Tangsa + or ScriptClass.Toto + or ScriptClass.Vithkuqi + or ScriptClass.Mongolian + or ScriptClass.Nko + or ScriptClass.PhagsPa + or ScriptClass.Mandaic + or ScriptClass.Manichaean + or ScriptClass.PsalterPahlavi + + // Universal syllable analysis is selected from the Unicode script, + // independently of which layout tables the font provides. A font with + // no script system still needs broken-syllable repair and reordering. => new UniversalShaper(script, textOptions, fontMetrics), _ => new DefaultShaper(script, textOptions), }; diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ShapingStage.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ShapingStage.cs index 0300a1acd..006dd5bc5 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ShapingStage.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ShapingStage.cs @@ -12,11 +12,17 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; /// internal readonly struct ShapingStage : IEquatable { - /// The optional action to invoke before the feature is applied. - private readonly Action? preAction; + /// + /// The optional action to invoke before the feature is applied. Actions receive + /// the plan whose segment is being shaped, so pause work never depends on + /// ambient state. + /// + private readonly Action? preAction; - /// The optional action to invoke after the feature is applied. - private readonly Action? postAction; + /// + /// The optional action to invoke after the feature is applied. + /// + private readonly Action? postAction; /// /// Initializes a new instance of the struct. @@ -24,7 +30,7 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; /// The OpenType feature tag for this stage. /// An optional action to invoke before the feature is applied. /// An optional action to invoke after the feature is applied. - public ShapingStage(Tag featureTag, Action? preAction, Action? postAction) + public ShapingStage(Tag featureTag, Action? preAction, Action? postAction) { this.FeatureTag = featureTag; this.preAction = preAction; @@ -36,23 +42,39 @@ public ShapingStage(Tag featureTag, Action? p /// public Tag FeatureTag { get; } + /// + /// Gets a value indicating whether this stage runs an action before its feature. + /// A pre action opens a new application group: every lookup registered by earlier + /// stages must have applied before the action runs. + /// + public bool HasPreAction => this.preAction is not null; + + /// + /// Gets a value indicating whether this stage runs an action after its feature. + /// A post action closes its application group: the group's lookups must all have + /// applied before the action runs. + /// + public bool HasPostAction => this.postAction is not null; + /// /// Invokes the pre-processing action for this shaping stage, if one was provided. /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based index of the first element. /// The number of elements. - public void PreProcessFeature(IGlyphShapingCollection collection, int index, int count) - => this.preAction?.Invoke(collection, index, count); + public void PreProcessFeature(ShapePlan plan, ShapingBuffer buffer, int index, int count) + => this.preAction?.Invoke(plan, buffer, index, count); /// /// Invokes the post-processing action for this shaping stage, if one was provided. /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based index of the first element. /// The number of elements. - public void PostProcessFeature(IGlyphShapingCollection collection, int index, int count) - => this.postAction?.Invoke(collection, index, count); + public void PostProcessFeature(ShapePlan plan, ShapingBuffer buffer, int index, int count) + => this.postAction?.Invoke(plan, buffer, index, count); /// public override bool Equals(object? obj) diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/TextNormalizer.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/TextNormalizer.cs new file mode 100644 index 000000000..c9898ce88 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/TextNormalizer.cs @@ -0,0 +1,478 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.Fonts.Unicode.Resources; + +namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; + +/// +/// Takes the characters of a run apart, orders the marks, and joins them back +/// together, so that text written in any of the ways the standard calls equivalent +/// is shaped the same way. +/// +/// +/// This follows the Unicode normalization algorithm but departs from it in one +/// respect that matters for shaping: a character comes apart, and a pair joins, +/// only when the font can draw the result. A font offering the joined form gets it, +/// because a joined form usually carries better mark positioning than a font's own +/// mark attachment would give; a font offering only the parts gets the parts. +/// +internal static class TextNormalizer +{ + /// + /// The hyphen used when a font cannot draw the non-breaking form. + /// + private const int HyphenCodePoint = 0x2010; + + /// + /// The non-breaking hyphen, whose visible fallback is the ordinary hyphen. + /// + private const int NonBreakingHyphenCodePoint = 0x2011; + + /// + /// The longest run of marks that is ordered. A run longer than this is left + /// alone, because ordering it costs more than the ordering is worth. + /// + private const int MaxOrderedMarkRun = 32; + + /// + /// The most parts one character is taken apart into. A canonical chain reaches + /// three at its longest, so this leaves room to spare and bounds the gather. + /// + private const int MaxDecompositionParts = 8; + + /// + /// The combining grapheme joiner, whose match transparency depends on whether it prevented mark reordering. + /// + private const int CombiningGraphemeJoinerCodePoint = 0x034F; + + /// + /// Orders two records by the class that places their marks. + /// + private static readonly Comparison MarkOrder = + static (a, b) => a.MarkOrderingClass - b.MarkOrderingClass; + + /// + /// Normalizes the given run of the buffer. + /// + /// The shaper whose preference and joining rules apply. + /// The font metrics, which decide what the font can draw. + /// The glyph shaping buffer. + /// The zero-based index of the run's first record. + /// The number of records in the run. + /// The number of records the run gained or lost. + public static int Normalize(BaseShaper shaper, FontMetrics fontMetrics, ShapingBuffer buffer, int index, int count) + { + NormalizationMode mode = shaper.NormalizationMode; + if (mode == NormalizationMode.None || count == 0) + { + return 0; + } + + int end = index + count; + int candidate = index; + while (candidate < end && (uint)buffer[candidate].CodePoint.Value < NormalizationData.FirstDecompositionCodePoint) + { + candidate++; + } + + if (candidate == end) + { + // The generated lower bound precedes every canonical decomposition, + // combining mark, and shaping control handled below. A run entirely + // below it cannot change in any of the normalization rounds. + return 0; + } + + int before = buffer.Count; + + // A character standing on its own that the font already draws is left + // untouched, unless the shaper asked for everything to come apart. + bool mayShortCircuit = mode != NormalizationMode.ComposedDiacriticsNoShortCircuit; + bool allMarksSeenAlone = Decompose(shaper, fontMetrics, buffer, index, ref count, mayShortCircuit); + + if (!allMarksSeenAlone) + { + OrderMarks(shaper, buffer, index, count); + + if (mode is NormalizationMode.ComposedDiacritics or NormalizationMode.ComposedDiacriticsNoShortCircuit) + { + Compose(shaper, fontMetrics, buffer, index, ref count); + } + } + + end = index + count; + for (int i = index + 1; i + 1 < end; i++) + { + ref GlyphShapingData data = ref buffer[i]; + if (data.CodePoint.Value != CombiningGraphemeJoinerCodePoint) + { + continue; + } + + int previousOrder = buffer[i - 1].MarkOrderingClass; + int nextOrder = buffer[i + 1].MarkOrderingClass; + if (nextOrder == 0 || previousOrder <= nextOrder) + { + // A joiner that did not block an otherwise-required mark swap may + // be skipped by substitution matching. One that did block a swap + // remains matchable so the text's explicit ordering barrier survives. + data.IsHiddenIgnorable = false; + } + } + + return buffer.Count - before; + } + + /// + /// Takes the characters of the run apart. Characters that stand without a mark + /// after them are passed over when the mode allows it; a character followed by + /// marks always comes apart, so that the marks can be ordered against the parts. + /// + /// The shaper whose joining rules apply. + /// The font metrics. + /// The glyph shaping buffer. + /// The zero-based index of the run's first record. + /// The number of records in the run, updated as it changes. + /// Whether a character the font draws may be passed over. + /// + /// when no character of the run was followed by a mark, so + /// there is nothing to order and nothing to join. + /// + private static bool Decompose(BaseShaper shaper, FontMetrics fontMetrics, ShapingBuffer buffer, int index, ref int count, bool mayShortCircuit) + { + bool allSimple = true; + int i = index; + int end = index + count; + + while (i < end) + { + // Find where the marks following this character begin. One character is + // left ahead of them to carry them. + int markStart = i + 1; + while (markStart < end && !CodePoint.IsMark(buffer[markStart].CodePoint)) + { + markStart++; + } + + if (markStart < end) + { + markStart--; + } + + // Up to that point the characters stand alone. + while (i < markStart) + { + int delta = DecomposeOne(shaper, fontMetrics, buffer, i, mayShortCircuit, out int produced); + end += delta; + i += produced; + } + + if (i >= end) + { + break; + } + + allSimple = false; + + // The character and the marks after it come apart together. + int markEnd = i + 1; + while (markEnd < end && CodePoint.IsMark(buffer[markEnd].CodePoint)) + { + markEnd++; + } + + while (i < markEnd) + { + int delta = DecomposeOne(shaper, fontMetrics, buffer, i, false, out int produced); + end += delta; + markEnd += delta; + i += produced; + } + } + + count = end - index; + return allSimple; + } + + /// + /// Takes one character apart, replacing it with its parts when the font can draw + /// them. + /// + /// The shaper whose joining rules apply. + /// The font metrics. + /// The glyph shaping buffer. + /// The zero-based index of the record. + /// Whether a character the font draws may be passed over. + /// + /// When this method returns, contains the number of records now standing where the + /// character stood, which is how far the caller advances. + /// + /// The number of records the buffer gained. + private static int DecomposeOne(BaseShaper shaper, FontMetrics fontMetrics, ShapingBuffer buffer, int i, bool mayShortCircuit, out int produced) + { + CodePoint codePoint = buffer[i].CodePoint; + + if (codePoint.Value == NonBreakingHyphenCodePoint && !fontMetrics.TryGetGlyphId(codePoint, out _)) + { + // The non-breaking character changes line-breaking behavior, not its + // visible form. When the font omits it, use the ordinary hyphen glyph + // while retaining the original text bookkeeping on the record. + CodePoint hyphen = new(HyphenCodePoint); + if (fontMetrics.TryGetGlyphId(hyphen, out ushort hyphenGlyph)) + { + buffer.SetGlyphId(i, hyphenGlyph); + produced = 1; + return 0; + } + } + + // A character that cannot come apart is kept exactly as it stands, whether or + // not the font draws it, so the font is never asked about it. Testing that + // first matters: it is a search of a table held in read-only data, while asking + // the font costs a dictionary probe, and for a run of text with nothing to take + // apart the font would be asked once per character to no purpose. + if (!shaper.TryDecompose(codePoint, out CodePoint _, out CodePoint _)) + { + produced = 1; + return 0; + } + + if (mayShortCircuit && fontMetrics.TryGetGlyphId(codePoint, out _)) + { + produced = 1; + return 0; + } + + if (TryWriteDecomposition(shaper, fontMetrics, buffer, i, codePoint, mayShortCircuit, out produced)) + { + return produced - 1; + } + + produced = 1; + return 0; + } + + /// + /// Writes the parts of a character over it, walking down the chain of pairs until + /// it reaches parts the font can draw. Nothing is written unless the whole chain + /// resolves, so a character the font cannot draw and cannot take apart is left as + /// it stands for the substitution passes to deal with. + /// + /// The shaper whose joining rules apply. + /// The font metrics. + /// The glyph shaping buffer. + /// The zero-based index of the record. + /// The character to take apart. + /// Whether a drawable leading part may end recursive decomposition. + /// When this method returns, contains the number of records written. + /// when the character came apart. + private static bool TryWriteDecomposition(BaseShaper shaper, FontMetrics fontMetrics, ShapingBuffer buffer, int i, CodePoint codePoint, bool mayShortCircuit, out int produced) + { + produced = 0; + + // The chain is gathered whole before anything is written, so a chain that + // turns out not to resolve leaves the record exactly as it was. + Span parts = stackalloc CodePoint[MaxDecompositionParts]; + Span glyphs = stackalloc ushort[MaxDecompositionParts]; + + int gathered = 0; + if (!TryGather(shaper, fontMetrics, codePoint, parts, glyphs, mayShortCircuit, ref gathered)) + { + return false; + } + + // One part is a character standing for a single other one, so the record keeps + // its place and only changes what it is. + if (gathered == 1) + { + buffer.SetGlyphId(i, glyphs[0]); + buffer[i].CodePoint = parts[0]; + produced = 1; + return true; + } + + buffer.Replace(i, glyphs[..gathered], KnownFeatureTags.GlyphCompositionDecomposition); + for (int part = 0; part < gathered; part++) + { + buffer[i + part].CodePoint = parts[part]; + } + + produced = gathered; + return true; + } + + /// + /// Gathers the parts a character comes apart into, in the order they are written, + /// following the leading part down while the font cannot draw it. + /// + /// The shaper whose joining rules apply. + /// The font metrics. + /// The character to take apart. + /// The characters gathered so far. + /// The glyphs of those characters. + /// Whether a drawable leading part may end recursive decomposition. + /// The number gathered so far, advanced as parts are added. + /// when the whole chain resolved to drawable parts. + private static bool TryGather(BaseShaper shaper, FontMetrics fontMetrics, CodePoint codePoint, Span parts, Span glyphs, bool mayShortCircuit, ref int gathered) + { + if (!shaper.TryDecompose(codePoint, out CodePoint first, out CodePoint second)) + { + return false; + } + + // The trailing part has to be drawable for the pair to be usable at all, and + // there has to be room left to record it. + bool hasSecond = second.Value != 0; + ushort secondId = 0; + if (hasSecond && (!fontMetrics.TryGetGlyphId(second, out secondId) || gathered + 2 > parts.Length)) + { + return false; + } + + // The composed-diacritics mode keeps the shortest drawable leading part. + // Indic and related shapers instead follow that part's decomposition to its + // end even when the font can already draw it. + bool hasFirst = fontMetrics.TryGetGlyphId(first, out ushort firstId); + if (mayShortCircuit && hasFirst) + { + if (gathered + 1 > parts.Length) + { + return false; + } + + parts[gathered] = first; + glyphs[gathered] = firstId; + gathered++; + } + else + { + // Recursive gathering writes into shared stack storage. Remember the + // starting length so a leading decomposition that cannot be completed + // leaves no partial parts before the drawable leading fallback is used. + int checkpoint = gathered; + if (!TryGather(shaper, fontMetrics, first, parts, glyphs, mayShortCircuit, ref gathered)) + { + gathered = checkpoint; + if (!hasFirst || gathered + 1 > parts.Length) + { + return false; + } + + parts[gathered] = first; + glyphs[gathered] = firstId; + gathered++; + } + } + + if (hasSecond) + { + if (gathered + 1 > parts.Length) + { + return false; + } + + parts[gathered] = second; + glyphs[gathered] = secondId; + gathered++; + } + + return true; + } + + /// + /// Orders each run of marks by the class that places them, leaving the marks of + /// every script in the order they are drawn. + /// + /// The shaper whose script-specific mark ordering applies. + /// The glyph shaping buffer. + /// The zero-based index of the run's first record. + /// The number of records in the run. + private static void OrderMarks(BaseShaper shaper, ShapingBuffer buffer, int index, int count) + { + int end = index + count; + for (int i = index; i < end; i++) + { + if (buffer[i].MarkOrderingClass == 0) + { + continue; + } + + int runEnd = i + 1; + while (runEnd < end && buffer[runEnd].MarkOrderingClass != 0) + { + runEnd++; + } + + if (runEnd - i <= MaxOrderedMarkRun) + { + buffer.Sort(i, runEnd, MarkOrder); + shaper.ReorderNormalizedMarks(buffer, i, runEnd); + } + + i = runEnd; + } + } + + /// + /// Joins each mark onto the character it follows wherever the pair has a joined + /// form the font can draw. A mark only joins the character that starts its run, + /// and only when nothing between them outranks it, so the order settled by + /// is never broken. + /// + /// The shaper whose joining rules apply. + /// The font metrics. + /// The glyph shaping buffer. + /// The zero-based index of the run's first record. + /// The number of records in the run, updated as it changes. + private static void Compose(BaseShaper shaper, FontMetrics fontMetrics, ShapingBuffer buffer, int index, ref int count) + { + int end = index + count; + int starter = index; + + for (int i = index + 1; i < end; i++) + { + CodePoint codePoint = buffer[i].CodePoint; + + // A character that is not a mark never joins the character before it. + // Beyond sparing every neighbouring pair a lookup, this is what keeps a + // font's own syllables and the letters they are built from apart. + if (!CodePoint.IsMark(codePoint)) + { + if (buffer[i].MarkOrderingClass == 0) + { + starter = i; + } + + continue; + } + + int order = buffer[i].MarkOrderingClass; + bool reachesStarter = starter == i - 1 + || buffer[i - 1].MarkOrderingClass < order; + + if (reachesStarter + && shaper.TryCompose(buffer[starter].CodePoint, codePoint, out CodePoint composed) + && fontMetrics.TryGetGlyphId(composed, out ushort composedId)) + { + // The joined form takes the starter's place and carries the text of + // both. Only the mark goes: any marks standing between the two + // outrank it and keep both their place and their order. + buffer.MergeGlyph(starter, i, composedId, KnownFeatureTags.GlyphCompositionDecomposition); + buffer[starter].CodePoint = composed; + + end--; + i--; + continue; + } + + if (order == 0) + { + starter = i; + } + } + + count = end - index; + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ThaiShaper.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ThaiShaper.cs index e1d197ea5..3809e5d53 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ThaiShaper.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/ThaiShaper.cs @@ -6,9 +6,13 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; /// -/// Thai and Lao shaper. Handles SARA AM decomposition, NIKHAHIT/NIGGAHITA reordering, -/// and PUA-based fallback mark positioning for legacy fonts. -/// Based on HarfBuzz: +/// The shaper for Thai and Lao. +/// +/// SARA AM is decomposed into NIKHAHIT and SARA AA and the NIKHAHIT reordered +/// before the above-base marks it follows. A Thai font offering no features of +/// its own then has its marks positioned by substituting the private-use +/// characters a legacy font supplies for the purpose. +/// /// #pragma warning disable SA1201 // Nested types are grouped with the static data they define. internal class ThaiShaper : DefaultShaper @@ -44,7 +48,9 @@ internal class ThaiShaper : DefaultShaper { new(PuaAction.NOP, 2), new(PuaAction.SD, 2), new(PuaAction.NOP, 2) }, }; - /// Shift-Down PUA mappings for tone marks and below-vowel marks. + /// + /// Shift-Down PUA mappings for tone marks and below-vowel marks. + /// private static readonly PuaMapping[] SdMappings = [ new(0x0E48, 0xF70A, 0xF88B), // MAI EK @@ -57,7 +63,9 @@ internal class ThaiShaper : DefaultShaper new(0x0E3A, 0xF71A, 0xF89D), // PHINTHU ]; - /// Shift-Down-Left PUA mappings for tone marks. + /// + /// Shift-Down-Left PUA mappings for tone marks. + /// private static readonly PuaMapping[] SdlMappings = [ new(0x0E48, 0xF705, 0xF88C), // MAI EK @@ -67,7 +75,9 @@ internal class ThaiShaper : DefaultShaper new(0x0E4C, 0xF709, 0xF898), // THANTHAKHAT ]; - /// Shift-Left PUA mappings for tone marks and above-vowel marks. + /// + /// Shift-Left PUA mappings for tone marks and above-vowel marks. + /// private static readonly PuaMapping[] SlMappings = [ new(0x0E48, 0xF713, 0xF88A), // MAI EK @@ -84,17 +94,23 @@ internal class ThaiShaper : DefaultShaper new(0x0E4D, 0xF711, 0xF899), // NIKHAHIT ]; - /// Remove-Descender PUA mappings for consonants with removable descenders. + /// + /// Remove-Descender PUA mappings for consonants with removable descenders. + /// private static readonly PuaMapping[] RdMappings = [ new(0x0E0D, 0xF70F, 0xF89A), // YO YING new(0x0E10, 0xF700, 0xF89E), // THO THAN ]; - /// The font metrics used for glyph lookups and PUA shaping. + /// + /// The font metrics used for glyph lookups and PUA shaping. + /// private readonly FontMetrics fontMetrics; - /// Whether the font has GSUB features for Thai/Lao. + /// + /// Whether the font has GSUB features for Thai/Lao. + /// private readonly bool hasGsub; /// @@ -102,19 +118,29 @@ internal class ThaiShaper : DefaultShaper /// private enum ConsonantType { - /// Normal consonant. + /// + /// Normal consonant. + /// NC, - /// Ascending consonant (Thai: 0x0E1B, 0x0E1D, 0x0E1F). + /// + /// Ascending consonant (Thai: 0x0E1B, 0x0E1D, 0x0E1F). + /// AC, - /// Consonant with removable descender (Thai: 0x0E0D, 0x0E10). + /// + /// Consonant with removable descender (Thai: 0x0E0D, 0x0E10). + /// RC, - /// Consonant with strict descender (Thai: 0x0E0E, 0x0E0F). + /// + /// Consonant with strict descender (Thai: 0x0E0E, 0x0E0F). + /// DC, - /// Not a consonant. + /// + /// Not a consonant. + /// NotConsonant } @@ -123,16 +149,24 @@ private enum ConsonantType /// private enum MarkType { - /// Above-vowel mark. + /// + /// Above-vowel mark. + /// AV, - /// Below-vowel mark. + /// + /// Below-vowel mark. + /// BV, - /// Tone mark. + /// + /// Tone mark. + /// T, - /// Not a mark. + /// + /// Not a mark. + /// NotMark } @@ -141,19 +175,29 @@ private enum MarkType /// private enum PuaAction { - /// No operation. + /// + /// No operation. + /// NOP, - /// Shift combining-mark down. + /// + /// Shift combining-mark down. + /// SD, - /// Shift combining-mark left. + /// + /// Shift combining-mark left. + /// SL, - /// Shift combining-mark down-left. + /// + /// Shift combining-mark down-left. + /// SDL, - /// Remove descender from base consonant. + /// + /// Remove descender from base consonant. + /// RD } @@ -167,29 +211,24 @@ private enum PuaAction public ThaiShaper(ScriptClass script, TextOptions textOptions, FontMetrics fontMetrics, bool hasGsub) : base(script, MarkZeroingMode.PostGpos, textOptions) { + this.FallbackMarkPositioning = false; this.fontMetrics = fontMetrics; this.hasGsub = hasGsub; } /// - protected override void AssignFeatures(IGlyphShapingCollection collection, int index, int count) + protected override void PreprocessText(ShapingBuffer buffer, int index, int count) { - base.AssignFeatures(collection, index, count); - - if (collection is not GlyphSubstitutionCollection substitutionCollection) - { - return; - } + // SARA AM is always decomposed into NIKHAHIT + SARA AA and reordered, + // which a font carrying Thai or Lao features needs just as much as one + // that carries none. + count = PreprocessSaraAm(buffer, this.fontMetrics, index, count); - // Step 1: Always decompose SARA AM -> NIKHAHIT + SARA AA and reorder. - // This is needed even when the font has Thai/Lao GSUB tables. - count = PreprocessSaraAm(substitutionCollection, this.fontMetrics, index, count); - - // Step 2: PUA-based fallback mark positioning. - // Only applied for Thai (not Lao) when the font lacks Thai GSUB features. + // The private-use fallback positions marks for a Thai font that offers no + // features to position them with. Lao has no such fallback. if (this.ScriptClass == ScriptClass.Thai && !this.hasGsub) { - DoThaiPuaShaping(substitutionCollection, this.fontMetrics, index, count); + DoThaiPuaShaping(buffer, this.fontMetrics, index, count); } } @@ -201,12 +240,12 @@ protected override void AssignFeatures(IGlyphShapingCollection collection, int i /// /// /// - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The font metrics for glyph lookups. /// The zero-based start index. /// The number of elements to process. /// The updated count after decomposition. - private static int PreprocessSaraAm(GlyphSubstitutionCollection collection, FontMetrics fontMetrics, int index, int count) + private static int PreprocessSaraAm(ShapingBuffer buffer, FontMetrics fontMetrics, int index, int count) { // Characters of significance: // @@ -222,7 +261,7 @@ private static int PreprocessSaraAm(GlyphSubstitutionCollection collection, Font int end = index + count; for (int i = index; i < end; i++) { - GlyphShapingData data = collection[i]; + ref GlyphShapingData data = ref buffer[i]; int codepoint = data.CodePoint.Value; if (!IsSaraAm(codepoint)) @@ -241,23 +280,26 @@ private static int PreprocessSaraAm(GlyphSubstitutionCollection collection, Font // Decompose SARA AM into [NIKHAHIT, SARA AA]. // Replace puts NIKHAHIT at index i, SARA AA at index i+1. - collection.Replace(i, [nikhahitId, saraAAId], KnownFeatureTags.GlyphCompositionDecomposition); - collection[i].CodePoint = new CodePoint(nikhahitCodepoint); - collection[i + 1].CodePoint = new CodePoint(saraAACodepoint); + buffer.Replace(i, [nikhahitId, saraAAId], KnownFeatureTags.GlyphCompositionDecomposition); + buffer[i].CodePoint = new CodePoint(nikhahitCodepoint); + buffer[i + 1].CodePoint = new CodePoint(saraAACodepoint); end++; // Move NIKHAHIT backward over any above-base marks. int target = i; - while (target > index && IsAboveBaseMark(collection[target - 1].CodePoint.Value)) + while (target > index && IsAboveBaseMark(buffer[target - 1].CodePoint.Value)) { target--; } if (target < i) { - collection.MoveGlyph(i, target); + buffer.MoveGlyph(i, target); } + int sourceStart = target > index ? target - 1 : target; + buffer.CombineInputStarts(sourceStart, i + 2); + // Skip past SARA AA. i++; } @@ -269,11 +311,11 @@ private static int PreprocessSaraAm(GlyphSubstitutionCollection collection, Font /// Applies PUA-based fallback mark positioning using state machines. /// Only used for Thai fonts that lack GSUB features. /// - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The font metrics for glyph lookups. /// The zero-based start index. /// The number of elements to process. - private static void DoThaiPuaShaping(GlyphSubstitutionCollection collection, FontMetrics fontMetrics, int index, int count) + private static void DoThaiPuaShaping(ShapingBuffer buffer, FontMetrics fontMetrics, int index, int count) { int aboveState = AboveStartState[(int)ConsonantType.NotConsonant]; int belowState = BelowStartState[(int)ConsonantType.NotConsonant]; @@ -282,7 +324,7 @@ private static void DoThaiPuaShaping(GlyphSubstitutionCollection collection, Fon int end = index + count; for (int i = index; i < end; i++) { - int codepoint = collection[i].CodePoint.Value; + int codepoint = buffer[i].CodePoint.Value; MarkType mt = GetMarkType(codepoint); if (mt == MarkType.NotMark) @@ -304,12 +346,12 @@ private static void DoThaiPuaShaping(GlyphSubstitutionCollection collection, Fon if (action == PuaAction.RD) { - int baseCp = collection[baseIndex].CodePoint.Value; + int baseCp = buffer[baseIndex].CodePoint.Value; int puaCp = ThaiPuaShape(baseCp, action, fontMetrics); if (puaCp != baseCp && fontMetrics.TryGetGlyphId(new CodePoint(puaCp), out ushort puaId)) { - collection[baseIndex].CodePoint = new CodePoint(puaCp); - collection[baseIndex].GlyphId = puaId; + buffer[baseIndex].CodePoint = new CodePoint(puaCp); + buffer.SetGlyphId(baseIndex, puaId); } } else if (action != PuaAction.NOP) @@ -317,8 +359,8 @@ private static void DoThaiPuaShaping(GlyphSubstitutionCollection collection, Fon int puaCp = ThaiPuaShape(codepoint, action, fontMetrics); if (puaCp != codepoint && fontMetrics.TryGetGlyphId(new CodePoint(puaCp), out ushort puaId)) { - collection[i].CodePoint = new CodePoint(puaCp); - collection[i].GlyphId = puaId; + buffer[i].CodePoint = new CodePoint(puaCp); + buffer.SetGlyphId(i, puaId); } } } @@ -475,10 +517,14 @@ private static bool IsAboveBaseMark(int codepoint) /// private readonly struct StateTransition(PuaAction action, int nextState) { - /// Gets the PUA action to apply. + /// + /// Gets the PUA action to apply. + /// public PuaAction Action { get; } = action; - /// Gets the next state for the state machine. + /// + /// Gets the next state for the state machine. + /// public int NextState { get; } = nextState; } @@ -487,13 +533,19 @@ private readonly struct StateTransition(PuaAction action, int nextState) /// private readonly struct PuaMapping(ushort original, ushort winPua, ushort macPua) { - /// Gets the original Thai codepoint. + /// + /// Gets the original Thai codepoint. + /// public ushort Original { get; } = original; - /// Gets the Windows PUA replacement codepoint. + /// + /// Gets the Windows PUA replacement codepoint. + /// public ushort WinPua { get; } = winPua; - /// Gets the Mac PUA replacement codepoint. + /// + /// Gets the Mac PUA replacement codepoint. + /// public ushort MacPua { get; } = macPua; } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/UniversalShaper.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/UniversalShaper.cs index fda9bc50b..1f025b2ef 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/UniversalShaper.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/UniversalShaper.cs @@ -14,73 +14,167 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; /// internal sealed class UniversalShaper : DefaultShaper { - /// The state machine for Universal Shaping Engine syllable identification. + /// + /// The generated category name table, captured once: the generated property + /// allocates a fresh array on every access. + /// + private static readonly string[] CategoryNames = UniversalShapingData.Categories; + + /// + /// Symbol indices for the categories compared during reordering, resolved once from + /// the generated table so per-glyph category tests are integer comparisons. The + /// symbol index is the value the state machine consumes, so glyphs never + /// materialize category name strings. + /// + private static readonly int CategoryB = Array.IndexOf(CategoryNames, "B"); + + /// + /// The symbol index for characters omitted from syllable-machine input. + /// + private static readonly int CategoryCGJ = Array.IndexOf(CategoryNames, "CGJ"); + + private static readonly int CategoryH = Array.IndexOf(CategoryNames, "H"); + + private static readonly int CategoryHVM = Array.IndexOf(CategoryNames, "HVM"); + + private static readonly int CategoryIS = Array.IndexOf(CategoryNames, "IS"); + + private static readonly int CategoryR = Array.IndexOf(CategoryNames, "R"); + + private static readonly int CategoryVPre = Array.IndexOf(CategoryNames, "VPre"); + + private static readonly int CategoryVMPre = Array.IndexOf(CategoryNames, "VMPre"); + + /// + /// The symbol index for a non-joiner whose inclusion depends on the following character. + /// + private static readonly int CategoryZwnj = Array.IndexOf(CategoryNames, "ZWNJ"); + + /// + /// The state machine for Universal Shaping Engine syllable identification. + /// private static readonly StateMachine StateMachine = new(UniversalShapingData.StateTable, UniversalShapingData.AcceptingStates, UniversalShapingData.Tags); - /// The 'rphf' (reph forms) feature tag. + /// + /// The syllable type for each machine state, translated from the tag rows once so + /// match handling never maps rule name strings. + /// + private static readonly SyllableType[] StateSyllableTypes = + SyllableTypeMap.FromMachineTags(UniversalShapingData.Tags); + + /// + /// The 'rphf' (reph forms) feature tag. + /// private static readonly Tag RphfTag = Tag.Parse("rphf"); - /// The 'nukt' (nukta forms) feature tag. + /// + /// The 'nukt' (nukta forms) feature tag. + /// private static readonly Tag NuktTag = Tag.Parse("nukt"); - /// The 'akhn' (akhands) feature tag. + /// + /// The 'akhn' (akhands) feature tag. + /// private static readonly Tag AkhnTag = Tag.Parse("akhn"); - /// The 'pref' (pre-base forms) feature tag. + /// + /// The 'pref' (pre-base forms) feature tag. + /// private static readonly Tag PrefTag = Tag.Parse("pref"); - /// The 'rkrf' (rakar forms) feature tag. + /// + /// The 'rkrf' (rakar forms) feature tag. + /// private static readonly Tag RkrfTag = Tag.Parse("rkrf"); - /// The 'abvf' (above-base forms) feature tag. + /// + /// The 'abvf' (above-base forms) feature tag. + /// private static readonly Tag AbvfTag = Tag.Parse("abvf"); - /// The 'blwf' (below-base forms) feature tag. + /// + /// The 'blwf' (below-base forms) feature tag. + /// private static readonly Tag BlwfTag = Tag.Parse("blwf"); - /// The 'half' (half forms) feature tag. + /// + /// The 'half' (half forms) feature tag. + /// private static readonly Tag HalfTag = Tag.Parse("half"); - /// The 'pstf' (post-base forms) feature tag. + /// + /// The 'pstf' (post-base forms) feature tag. + /// private static readonly Tag PstfTag = Tag.Parse("pstf"); - /// The 'vatu' (vattu variants) feature tag. + /// + /// The 'vatu' (vattu variants) feature tag. + /// private static readonly Tag VatuTag = Tag.Parse("vatu"); - /// The 'cjct' (conjunct forms) feature tag. + /// + /// The 'cjct' (conjunct forms) feature tag. + /// private static readonly Tag CjctTag = Tag.Parse("cjct"); - /// The 'abvs' (above-base substitutions) feature tag. + /// + /// The 'abvs' (above-base substitutions) feature tag. + /// private static readonly Tag AbvsTag = Tag.Parse("abvs"); - /// The 'blws' (below-base substitutions) feature tag. + /// + /// The 'blws' (below-base substitutions) feature tag. + /// private static readonly Tag BlwsTag = Tag.Parse("blws"); - /// The 'pres' (pre-base substitutions) feature tag. + /// + /// The 'pres' (pre-base substitutions) feature tag. + /// private static readonly Tag PresTag = Tag.Parse("pres"); - /// The 'psts' (post-base substitutions) feature tag. + /// + /// The 'psts' (post-base substitutions) feature tag. + /// private static readonly Tag PstsTag = Tag.Parse("psts"); - /// The 'dist' (distances) feature tag. - private static readonly Tag DistTag = Tag.Parse("dist"); - - /// The 'abvm' (above-base mark positioning) feature tag. - private static readonly Tag AbvmTag = Tag.Parse("abvm"); - - /// The 'blwm' (below-base mark positioning) feature tag. - private static readonly Tag BlwmTag = Tag.Parse("blwm"); + /// + /// The 'haln' (halant forms) feature tag. + /// + private static readonly Tag HalnTag = Tag.Parse("haln"); - /// Dotted circle code point (U+25CC) used as a placeholder base. + /// + /// Dotted circle code point (U+25CC) used as a placeholder base. + /// private const int DottedCircle = 0x25cc; - /// The font metrics used for glyph lookups. + /// + /// The font metrics used for glyph lookups. + /// private readonly FontMetrics fontMetrics; - /// Whether any broken clusters were detected during syllable setup. + /// + /// Whether any broken clusters were detected during syllable setup. + /// private bool hasBrokenClusters; + /// + /// The syllable setup pause, converted to a delegate once so per-pass feature + /// planning never allocates for the conversion. + /// + private readonly Action setupSyllablesAction; + + /// + /// The reorder pause, converted to a delegate once so per-pass feature + /// planning never allocates for the conversion. + /// + private readonly Action reorderAction; + + /// + /// The pause separating topographical substitutions from later presentation substitutions. + /// + private readonly Action pauseAction; + /// /// Initializes a new instance of the class. /// @@ -89,155 +183,237 @@ internal sealed class UniversalShaper : DefaultShaper /// The font metrics for glyph lookups. public UniversalShaper(ScriptClass script, TextOptions textOptions, FontMetrics fontMetrics) : base(script, MarkZeroingMode.PreGPos, textOptions) - => this.fontMetrics = fontMetrics; + { + this.FallbackMarkPositioning = false; + this.fontMetrics = fontMetrics; + this.setupSyllablesAction = this.SetupSyllables; + this.reorderAction = this.Reorder; + this.pauseAction = Pause; + + // Every character comes apart first, even one the font already draws whole. + // This shaper divides a run into syllables and moves their pieces about, so a + // character standing for several pieces must be split before the division can + // see them. + this.NormalizationMode = NormalizationMode.ComposedDiacriticsNoShortCircuit; + } /// - protected override void PlanFeatures(IGlyphShapingCollection collection, int index, int count) + protected override void PreprocessText(ShapingBuffer buffer, int index, int count) + => VowelConstraints.Insert(buffer, this.fontMetrics, this.ScriptClass, index, count); + + /// + protected override void PlanFeatures(ShapingBuffer buffer, int index, int count) { - // Default glyph pre-processing group - this.AddFeature(collection, index, count, LoclTag, preAction: this.SetupSyllables); - this.AddFeature(collection, index, count, CcmpTag); - this.AddFeature(collection, index, count, NuktTag); - this.AddFeature(collection, index, count, AkhnTag); - - // Reordering group - this.AddFeature(collection, index, count, RphfTag, true, ClearSubstitutionFlags, RecordRhpf); - this.AddFeature(collection, index, count, PrefTag, true, ClearSubstitutionFlags, RecordPref); - - // Orthographic unit shaping group - this.AddFeature(collection, index, count, RkrfTag); - this.AddFeature(collection, index, count, AbvfTag); - this.AddFeature(collection, index, count, BlwfTag); - this.AddFeature(collection, index, count, HalfTag); - this.AddFeature(collection, index, count, PstfTag); - this.AddFeature(collection, index, count, VatuTag); - this.AddFeature(collection, index, count, CjctTag, postAction: this.Reorder); - - // Standard topographic presentation and positional feature application - this.AddFeature(collection, index, count, AbvsTag); - this.AddFeature(collection, index, count, BlwsTag); - this.AddFeature(collection, index, count, PresTag); - this.AddFeature(collection, index, count, PstsTag); - this.AddFeature(collection, index, count, DistTag); - this.AddFeature(collection, index, count, AbvmTag); - this.AddFeature(collection, index, count, BlwmTag); + // These language and composition features establish the glyph forms consumed + // by every later USE stage. Applying them per syllable prevents a contextual + // lookup from crossing an orthographic-unit boundary. + this.EnableFeature(buffer, index, count, LoclTag, ShapingFeatureFlags.PerSyllable, this.setupSyllablesAction, null); + this.EnableFeature(buffer, index, count, CcmpTag, ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, NuktTag, ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, AkhnTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable); + + // Reordering group. The repha feature varies per glyph: syllable setup + // enables it on each syllable's leading glyphs only, so a repha forms + // there and nowhere else. + this.AddFeature(buffer, index, count, RphfTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable, false, ClearSubstitutionFlags, RecordRhpf); + this.EnableFeature(buffer, index, count, PrefTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable, ClearSubstitutionFlags, RecordPref); + + // These substitutions form the internal pieces of an orthographic unit. + // Reordering runs only after the complete group because it depends on which + // repha, pre-base, half, below-base, and post-base forms actually substituted. + this.EnableFeature(buffer, index, count, RkrfTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, AbvfTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, BlwfTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, HalfTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, PstfTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, VatuTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable); + this.EnableFeature(buffer, index, count, CjctTag, ShapingFeatureFlags.ManualZwj | ShapingFeatureFlags.PerSyllable, null, this.reorderAction); + + // Each syllable receives exactly one topographical form. These features + // use varying masks and therefore remain disabled until mask setup. + this.AddFeature(buffer, index, count, ArabicJoining.IsolTag, false, null, null); + this.AddFeature(buffer, index, count, ArabicJoining.InitTag, false, null, null); + this.AddFeature(buffer, index, count, ArabicJoining.MediTag, false, null, null); + this.AddFeature(buffer, index, count, ArabicJoining.FinaTag, false, null, this.pauseAction); + + // The empty pause after the topographical group forces these presentation + // features into a later lookup stage; combining the groups would change + // feature ordering in fonts that implement both. + this.EnableFeature(buffer, index, count, AbvsTag, ShapingFeatureFlags.ManualZwj); + this.EnableFeature(buffer, index, count, BlwsTag, ShapingFeatureFlags.ManualZwj); + this.EnableFeature(buffer, index, count, HalnTag, ShapingFeatureFlags.ManualZwj); + this.EnableFeature(buffer, index, count, PresTag, ShapingFeatureFlags.ManualZwj); + this.EnableFeature(buffer, index, count, PstsTag, ShapingFeatureFlags.ManualZwj); } /// - protected override void AssignFeatures(IGlyphShapingCollection collection, int index, int count) - => this.DecomposeSplitVowels(collection, index, count); - - /// - /// Decomposes split vowels into their constituent parts if supported by the font. - /// - /// The glyph shaping collection. - /// The zero-based start index. - /// The number of elements to process. - private void DecomposeSplitVowels(IGlyphShapingCollection collection, int index, int count) + protected override void AssignFeatures(ShapingBuffer buffer, int index, int count) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + // Several of the scripts this engine shapes are cursive, and their + // characters take their form from the ones around them. + if (ArabicJoining.Joins(this.ScriptClass)) { - return; + ArabicJoining.Apply(buffer, index, count, this.ScriptClass, this.Features); } + } - FontMetrics fontMetrics = this.fontMetrics; - Span buffer = stackalloc ushort[16]; - int end = index + count; - for (int i = end - 1; i >= index; i--) + /// + public override bool TryCompose(CodePoint first, CodePoint second, out CodePoint composed) + { + // A split vowel begins with a mark. Joining that mark back onto the next + // part would undo the decomposition required by the shaping stages. + if (CodePoint.IsMark(first)) { - GlyphShapingData data = substitutionCollection[i]; - if (UniversalShapingData.Decompositions.TryGetValue(data.CodePoint.Value, out int[]? decompositions) && decompositions != null) - { - Span ids = buffer[..decompositions.Length]; - bool shouldDecompose = true; - for (int j = 0; j < decompositions.Length; j++) - { - if (!fontMetrics.TryGetGlyphId(new CodePoint(decompositions[j]), out ushort id)) - { - shouldDecompose = false; - break; - } - - ids[j] = id; - } - - if (shouldDecompose) - { - substitutionCollection.Replace(i, ids, KnownFeatureTags.GlyphCompositionDecomposition); - for (int j = 0; j < decompositions.Length; j++) - { - substitutionCollection[i + j].CodePoint = new(decompositions[j]); - } - } - } + composed = default; + return false; } + + return base.TryCompose(first, second, out composed); } /// /// Identifies syllables using the Universal Shaping Engine state machine and assigns shaping info to each glyph. /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based start index. /// The number of elements to process. - private void SetupSyllables(IGlyphShapingCollection collection, int index, int count) + private void SetupSyllables(ShapePlan plan, ShapingBuffer buffer, int index, int count) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role != ShapingBufferRole.Substitution) { return; } this.hasBrokenClusters = false; + // Most shaping segments fit on the stack. Larger segments use temporary + // managed storage rather than risking unbounded stack growth; both spans are + // reused in place for the filtered machine input. Span values = count <= 64 ? stackalloc int[count] : new int[count]; + Span sourceIndices = count <= 64 ? stackalloc int[count] : new int[count]; for (int i = index; i < index + count; i++) { - CodePoint codePoint = substitutionCollection[i].CodePoint; - values[i - index] = UnicodeData.GetUniversalShapingSymbolCount((uint)codePoint.Value); + CodePoint codePoint = buffer[i].CodePoint; + int sourceIndex = i - index; + int category = UnicodeData.GetUniversalShapingSymbolCount((uint)codePoint.Value); + + // Every record retains its category even when it is omitted from machine + // input, because feature setup and reordering still inspect that record. + values[sourceIndex] = category; + buffer[i].Syllable.UseCategory = category; } - int syllable = 0; - foreach (StateMatch match in StateMachine.Match(values)) + // The machine omits CGJ-class records. It also omits a non-joiner immediately + // before a Unicode mark, looking through any CGJ-class records. Retain each + // consumed record's original position so matches can be projected across the + // omitted records exactly as the source machine does. + int machineCount = 0; + for (int i = 0; i < count; i++) { - ++syllable; + int category = values[i]; + if (category == CategoryCGJ) + { + // A combining-grapheme-joiner category affects mark behavior but is + // transparent to the syllable grammar. + continue; + } - // Create shaper info - for (int i = match.StartIndex; i <= match.EndIndex; i++) + if (category == CategoryZwnj) { - GlyphShapingData data = substitutionCollection[i + index]; - CodePoint codePoint = data.CodePoint; - string category = UniversalShapingData.Categories[UnicodeData.GetUniversalShapingSymbolCount((uint)codePoint.Value)]; + // A non-joiner immediately before a mark is transparent to syllable + // recognition. Transparent CGJ-class records between the pair do not + // break that relationship; the first other character does. + for (int next = i + 1; next < count; next++) + { + if (values[next] == CategoryCGJ) + { + continue; + } + + if (CodePoint.IsMark(buffer[index + next].CodePoint)) + { + category = CategoryCGJ; + } - string syllableType = match.Tags[0]; + break; + } - if (syllableType == "broken_cluster") + if (category == CategoryCGJ) { - this.hasBrokenClusters = true; + continue; } + } + + // Compact accepted categories into the front of the existing spans. The + // source map projects each machine match back over any omitted records. + values[machineCount] = category; + sourceIndices[machineCount] = i; + machineCount++; + } + + int syllable = 0; + uint rphfMask = this.Features.GetMask(RphfTag); + StateMachine.MatchEnumerator match = StateMachine.EnumerateMatches(values[..machineCount]); + while (match.MoveNext()) + { + // A nonzero number distinguishes adjacent syllables even when they share + // the same type; callbacks use the number to recover exact boundaries. + ++syllable; + + // The next accepted category starts the next original range. Using it as + // the exclusive end assigns transparent records between matches to the + // syllable preceding them, preserving a complete projection. + int originalStart = sourceIndices[match.StartIndex]; + int originalEnd = match.EndIndex + 1 < machineCount ? sourceIndices[match.EndIndex + 1] : count; + + // Create shaper info. The symbol index is stored directly: it is the value + // the state machine consumes and the key into the generated name table. + SyllableType syllableType = StateSyllableTypes[match.TagState]; + if (syllableType == SyllableType.BrokenCluster) + { + this.hasBrokenClusters = true; + } - data.UniversalShapingEngineInfo = new(category, syllableType, syllable); + for (int i = originalStart; i < originalEnd; i++) + { + ref GlyphShapingData data = ref buffer[i + index]; + data.Syllable.Type = syllableType; + data.Syllable.Number = syllable; } - // Assign rphf feature - int limit = substitutionCollection[match.StartIndex + index].UniversalShapingEngineInfo!.Category == "R" + // Enable the repha feature on the syllable's leading glyphs only: a + // repha can form there and nowhere else, so the feature stays off for + // the rest of the syllable. An explicit repha needs only its own record; + // other syllables expose at most the first three candidates. + int limit = buffer[originalStart + index].Syllable.UseCategory == CategoryR ? 1 - : Math.Min(3, match.EndIndex - match.StartIndex); + : Math.Min(3, originalEnd - originalStart); - for (int i = match.StartIndex; i < match.StartIndex + limit; i++) + for (int i = originalStart; i < originalStart + limit; i++) { - substitutionCollection.AddShapingFeature(i + index, new TagEntry(RcltTag, true)); + buffer.EnableShapingFeature(i + index, rphfMask); } } + + if (!ArabicJoining.Joins(this.ScriptClass)) + { + // Cursive scripts already received character-level joining forms from + // ArabicJoining. Other USE scripts derive forms from adjacent syllables. + this.SetupTopographicalMasks(buffer, index, count); + } } /// /// Clears substitution flags on all glyphs in the range, preparing for the next substitution pass. /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based start index. /// The number of elements to process. - private static void ClearSubstitutionFlags(IGlyphShapingCollection collection, int index, int count) + private static void ClearSubstitutionFlags(ShapePlan plan, ShapingBuffer buffer, int index, int count) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role != ShapingBufferRole.Substitution) { return; } @@ -245,7 +421,7 @@ private static void ClearSubstitutionFlags(IGlyphShapingCollection collection, i int end = index + count; for (int i = index; i < end; i++) { - GlyphShapingData data = substitutionCollection[i]; + ref GlyphShapingData data = ref buffer[i]; data.IsSubstituted = false; } } @@ -253,56 +429,79 @@ private static void ClearSubstitutionFlags(IGlyphShapingCollection collection, i /// /// Records glyphs substituted by the 'rphf' feature by marking their category as repha ("R"). /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based start index. /// The number of elements to process. - private static void RecordRhpf(IGlyphShapingCollection collection, int index, int count) + private static void RecordRhpf(ShapePlan plan, ShapingBuffer buffer, int index, int count) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role != ShapingBufferRole.Substitution) + { + return; + } + + uint rphfMask = plan.Features.GetMask(RphfTag); + if (rphfMask == 0) { return; } int end = index + count; - for (int i = index; i < end; i++) + int start = index; + while (start < end) { - GlyphShapingData data = substitutionCollection[i]; - if (data.IsSubstituted && data.Features.Any(x => x.Tag == RphfTag)) + int syllableEnd = NextSyllable(buffer, start, end); + + // Only the leading mask-enabled region can form repha. Once a record has + // no repha mask, later records in this syllable are not candidates. + for (int i = start; i < syllableEnd && (buffer[i].FeatureMask & rphfMask) != 0; i++) { - // Mark a substituted repha. - if (data.UniversalShapingEngineInfo != null) + if (buffer[i].IsSubstituted) { - data.UniversalShapingEngineInfo.Category = "R"; + // Reordering consumes categories rather than feature history, so + // translate the first successful substitution into repha state. + buffer[i].Syllable.UseCategory = CategoryR; + break; } } + + start = syllableEnd; } } /// /// Records glyphs substituted by the 'pref' feature by marking their category as pre-base vowel ("VPre"). /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based start index. /// The number of elements to process. - private static void RecordPref(IGlyphShapingCollection collection, int index, int count) + private static void RecordPref(ShapePlan plan, ShapingBuffer buffer, int index, int count) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role != ShapingBufferRole.Substitution) { return; } int end = index + count; - for (int i = index; i < end; i++) + int start = index; + while (start < end) { - GlyphShapingData data = substitutionCollection[i]; - if (data.IsSubstituted) + int syllableEnd = NextSyllable(buffer, start, end); + + // The first successful pre-base-form substitution is the item later + // moved to the syllable's pre-base position. Other substitutions in the + // syllable retain their original categories. + for (int i = start; i < syllableEnd; i++) { - // Mark a substituted pref as VPre, as they behave the same way. - if (data.UniversalShapingEngineInfo != null) + if (buffer[i].IsSubstituted) { - data.UniversalShapingEngineInfo.Category = "VPre"; + buffer[i].Syllable.UseCategory = CategoryVPre; + break; } } + + start = syllableEnd; } } @@ -310,12 +509,13 @@ private static void RecordPref(IGlyphShapingCollection collection, int index, in /// Reorders glyphs within syllables, handling repha movement, pre-base vowel movement, /// and dotted circle insertion for broken clusters. /// - /// The glyph shaping collection. + /// The plan whose segment is being shaped. + /// The glyph shaping buffer. /// The zero-based start index. /// The number of elements to process. - private void Reorder(IGlyphShapingCollection collection, int index, int count) + private void Reorder(ShapePlan plan, ShapingBuffer buffer, int index, int count) { - if (collection is not GlyphSubstitutionCollection substitutionCollection) + if (buffer.Role != ShapingBufferRole.Substitution) { return; } @@ -323,144 +523,149 @@ private void Reorder(IGlyphShapingCollection collection, int index, int count) FontMetrics fontMetrics = this.fontMetrics; int max = index + count; int start = index; - int end = NextSyllable(substitutionCollection, index, max); + int end = NextSyllable(buffer, index, max); + + // The generated span is indexed by the same category symbols stored on each + // record. Keeping it as static data avoids constructing a runtime lookup. + ReadOnlySpan postBaseCategories = UniversalShapingData.PostBaseCategories; if (this.hasBrokenClusters) { if (fontMetrics.TryGetGlyphId(new(DottedCircle), out ushort circleId)) { - Span glyphs = stackalloc ushort[2]; + // Insert one base into each broken syllable. The bounds grow as + // records are inserted, so both the current syllable end and the + // overall end advance with each insertion. while (start < max) { - GlyphShapingData data = substitutionCollection[start]; - UniversalShapingEngineInfo? info = data.UniversalShapingEngineInfo; - string? type = info?.SyllableType; - - if (type == "broken_cluster") + if (buffer[start].Syllable.Type == SyllableType.BrokenCluster) { - // Insert after possible Repha. + // A leading repha must remain the first record even when its + // syllable needs a synthetic base. int i = start; for (i = start; i < end; i++) { - if (substitutionCollection[i].UniversalShapingEngineInfo?.Category != "R") + ref GlyphShapingData candidate = ref buffer[i]; + if (candidate.Syllable.Type == SyllableType.None || candidate.Syllable.UseCategory != CategoryR) { break; } } - GlyphShapingData current = substitutionCollection[i]; - UniversalShapingEngineInfo currentInfo = current.UniversalShapingEngineInfo!; - glyphs[0] = current.GlyphId; - glyphs[1] = circleId; - - substitutionCollection.Replace(i, glyphs, KnownFeatureTags.GlyphCompositionDecomposition); + buffer.InsertDottedCircle(i, circleId); - // Update shaping info for newly inserted data. - GlyphShapingData dotted = substitutionCollection[i + 1]; - dotted.UniversalShapingEngineInfo!.Category = "B"; - dotted.UniversalShapingEngineInfo.SyllableType = currentInfo.SyllableType; - dotted.UniversalShapingEngineInfo.Syllable = currentInfo.Syllable; + // The inserted record inherits the syllable and feature masks + // of the following record, while its category identifies it as a base. + buffer[i].Syllable.UseCategory = CategoryB; end++; max++; } start = end; - end = NextSyllable(substitutionCollection, start, max); + end = NextSyllable(buffer, start, max); } + // The insertion walk ends at the buffer boundary. Reset the cursors + // before applying the independent per-syllable reorder pass. start = index; - end = NextSyllable(substitutionCollection, index, max); + end = NextSyllable(buffer, index, max); } } while (start < max) { - GlyphShapingData data = substitutionCollection[start]; - UniversalShapingEngineInfo? info = data.UniversalShapingEngineInfo; - string? type = info?.SyllableType; + ref GlyphShapingData data = ref buffer[start]; // Only a few syllable types need reordering. - if (type is not "virama_terminated_cluster" and not "standard_cluster" and not "broken_cluster") + if (data.Syllable.Type is not SyllableType.ViramaTerminatedCluster + and not SyllableType.SakotTerminatedCluster + and not SyllableType.StandardCluster + and not SyllableType.SymbolCluster + and not SyllableType.BrokenCluster) { - // TODO: Check this. Harfbuzz seems to test more categories and returns. goto Increment; } - // Move things forward - if (info?.Category == "R" && end - start > 1) + // A leading repha moves towards the end but must remain before the first + // post-base form or halant. If neither exists, it becomes the last record. + if (data.Syllable.UseCategory == CategoryR && end - start > 1) { - // Got a repha. Reorder it to after first base, before first halant. for (int i = start + 1; i < end; i++) { - GlyphShapingData current = substitutionCollection[i]; - info = current.UniversalShapingEngineInfo; - if (IsBase(info) || IsHalant(current)) + ref GlyphShapingData current = ref buffer[i]; + bool isPostBase = postBaseCategories[current.Syllable.UseCategory] || IsHalant(ref current); + if (isPostBase || i == end - 1) { - // If we hit a halant, move before it; otherwise it's a base: move to it's - // place, and shift things in between backward. - if (IsHalant(current)) + // The target is the slot before a post-base item, but the last + // slot itself when the scan simply reached the syllable end. + if (isPostBase) { i--; } - substitutionCollection.MoveGlyph(start, i); + buffer.CombineInputStarts(start, i + 1); + buffer.MoveGlyph(start, i); break; } } } - // Move things back + // Pre-base vowels and modifiers move to the current insertion point. A + // halant advances that point past itself, so a pre-base item never crosses + // the stacker it belongs behind. for (int i = start, j = start; i < end; i++) { - GlyphShapingData current = substitutionCollection[i]; - info = current.UniversalShapingEngineInfo; + ref GlyphShapingData current = ref buffer[i]; - if (IsBase(info) || IsHalant(current)) + if (IsHalant(ref current)) { - // If we hit a halant, move after it; otherwise move to the beginning, and - // shift things in between forward. - if (IsHalant(current)) - { - j = i + 1; - } - else - { - j = i; - } + j = i + 1; } - else if ((info?.Category == "VPre" || info?.Category == "VMPre") - && current.LigatureComponent <= 0 // Only move the first component of a MultipleSubst + + // A multiple substitution gives every emitted glyph a component + // index. Moving only its first component preserves their order. + else if (current.Syllable.Type != SyllableType.None + && (current.Syllable.UseCategory == CategoryVPre || current.Syllable.UseCategory == CategoryVMPre) + && current.LigatureComponent <= 0 && j < i) { - substitutionCollection.MoveGlyph(i, j); + buffer.CombineInputStarts(j, i + 1); + buffer.MoveGlyph(i, j); } } Increment: start = end; - end = NextSyllable(substitutionCollection, start, max); + end = NextSyllable(buffer, start, max); + } + + // Later features are not constrained to syllables, so release the state + // once all syllable-local substitutions and reordering have completed. + for (int i = index; i < max; i++) + { + buffer[i].Syllable = default; } } /// - /// Finds the start index of the next syllable in the collection. + /// Finds the start index of the next syllable in the buffer. /// - /// The glyph substitution collection. + /// The glyph substitution buffer. /// The current index. /// The maximum index bound. /// The start index of the next syllable. - private static int NextSyllable(GlyphSubstitutionCollection collection, int index, int count) + private static int NextSyllable(ShapingBuffer buffer, int index, int count) { if (index >= count) { return index; } - int? syllable = collection[index].UniversalShapingEngineInfo?.Syllable; + int syllable = buffer[index].Syllable.Number; while (++index < count) { - if (collection[index].UniversalShapingEngineInfo?.Syllable != syllable) + if (buffer[index].Syllable.Number != syllable) { break; } @@ -474,14 +679,93 @@ private static int NextSyllable(GlyphSubstitutionCollection collection, int inde /// /// The glyph shaping data. /// if the glyph is a halant or equivalent. - private static bool IsHalant(GlyphShapingData data) - => (data.UniversalShapingEngineInfo?.Category is "H" or "HVM" or "IS") && !data.IsLigated; + private static bool IsHalant(ref GlyphShapingData data) + => data.Syllable.Type != SyllableType.None + && (data.Syllable.UseCategory == CategoryH || data.Syllable.UseCategory == CategoryHVM || data.Syllable.UseCategory == CategoryIS) + && !data.IsLigated; + + /// + /// Assigns joining forms by adjacent syllable for scripts without cursive joining data. + /// + /// The glyph shaping buffer. + /// The zero-based start index. + /// The number of elements to process. + private void SetupTopographicalMasks(ShapingBuffer buffer, int index, int count) + { + uint isolMask = this.Features.GetMask(ArabicJoining.IsolTag); + uint initMask = this.Features.GetMask(ArabicJoining.InitTag); + uint mediMask = this.Features.GetMask(ArabicJoining.MediTag); + uint finaMask = this.Features.GetMask(ArabicJoining.FinaTag); + uint allMasks = isolMask | initMask | mediMask | finaMask; + + // The previous syllable is revised only when a following joinable syllable + // proves it is not isolated or final. Retain its range and selected form + // until that decision can be made. + int end = index + count; + int lastStart = index; + uint lastMask = 0; + for (int start = index; start < end;) + { + int syllableEnd = NextSyllable(buffer, start, end); + SyllableType syllableType = buffer[start].Syllable.Type; + if (syllableType is SyllableType.HieroglyphCluster or SyllableType.NonCluster) + { + // These syllables never join and terminate any chain from the + // preceding syllable. + lastMask = 0; + } + else + { + bool joinsPrevious = lastMask == finaMask || lastMask == isolMask; + if (joinsPrevious) + { + // A previous final becomes medial when the chain continues; a + // previous isolated syllable becomes initial. + uint previousMask = lastMask == finaMask ? mediMask : initMask; + SetTopographicalMask(buffer, lastStart, start, allMasks, previousMask); + } + + // A continuing syllable is final for now. A new chain begins as + // isolated; either form may be revised by the next syllable. + lastMask = joinsPrevious ? finaMask : isolMask; + SetTopographicalMask(buffer, start, syllableEnd, allMasks, lastMask); + } + + lastStart = start; + start = syllableEnd; + } + } /// - /// Determines whether the shaping info represents a base consonant or generic base. + /// Replaces the topographical feature mask over a range. /// - /// The universal shaping engine info. - /// if the glyph is a base. - private static bool IsBase(UniversalShapingEngineInfo? info) - => info?.Category is "B" or "GB"; + /// The glyph shaping buffer. + /// The inclusive range start. + /// The exclusive range end. + /// The union of all topographical masks. + /// The mask selected for the range. + private static void SetTopographicalMask(ShapingBuffer buffer, int start, int end, uint allMasks, uint mask) + { + for (int i = start; i < end; i++) + { + // The forms are mutually exclusive. Clear the complete form group before + // enabling the one selected for this syllable. + buffer.DisableShapingFeature(i, allMasks); + buffer.EnableShapingFeature(i, mask); + } + } + + /// + /// Separates topographical substitutions from later presentation substitutions. + /// + /// The shaping plan. + /// The shaping buffer. + /// The zero-based index of the first record. + /// The number of records in the segment. + private static void Pause(ShapePlan plan, ShapingBuffer buffer, int index, int count) + { + // The feature planner treats a callback as a stage boundary even when the + // callback has no buffer work. Keeping this method empty is therefore the + // behavior: it separates topographical and presentation lookups. + } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/VowelConstraints.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/VowelConstraints.cs new file mode 100644 index 000000000..ae6d6393f --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Shapers/VowelConstraints.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.Fonts.Unicode.Resources; + +namespace SixLabors.Fonts.Tables.AdvancedTypographic.Shapers; + +/// +/// Separates the character sequences that spell one vowel but read as another. +/// The shapers whose scripts define such sequences run this over the text before +/// classifying it into syllables. +/// +internal static class VowelConstraints +{ + /// + /// The dotted circle, which stands between the characters of a sequence so + /// it cannot be mistaken for the vowel it imitates. + /// + private const int DottedCircle = 0x25CC; + + /// + /// Places a dotted circle before the final character of every constrained + /// sequence in the range. A sequence is consumed once recognised, so the + /// characters it spans cannot begin another. + /// + /// The glyph shaping buffer. + /// The font metrics used to resolve the dotted circle. + /// The script the segment is written in. + /// The zero-based index of the first record. + /// The number of records. + /// The number of dotted circles placed. + public static int Insert(ShapingBuffer buffer, FontMetrics fontMetrics, ScriptClass script, int index, int count) + { + if (!buffer.HasVowelConstraintCandidates || !VowelConstraintData.IsConstrainedScript(script)) + { + return 0; + } + + // The sequence is separated whether or not the font draws the circle; + // a font without one renders the missing glyph in its place. + if (!fontMetrics.TryGetGlyphId(new CodePoint(DottedCircle), out ushort circleId)) + { + circleId = 0; + } + + int inserted = 0; + int end = index + count; + int i = index; + while (i + 1 < end) + { + int first = buffer[i].CodePoint.Value; + int second = buffer[i + 1].CodePoint.Value; + + int length = 0; + if (i + 2 < end && VowelConstraintData.IsConstrainedTriple(script, first, second, buffer[i + 2].CodePoint.Value)) + { + length = 3; + } + else if (VowelConstraintData.IsConstrainedPair(script, first, second)) + { + length = 2; + } + + if (length == 0) + { + i++; + continue; + } + + buffer.InsertDottedCircle(i + length - 1, circleId); + inserted++; + end++; + + // The sequence and the circle now placed within it are behind the + // cursor, so the search resumes past all of them. + i += length + 1; + } + + return inserted; + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapingFeatureFlags.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapingFeatureFlags.cs new file mode 100644 index 000000000..39e1121f8 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/ShapingFeatureFlags.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// Per-feature registration flags controlling how the feature's lookups treat +/// joiners and syllable boundaries during sequence matching. +/// +[Flags] +internal enum ShapingFeatureFlags : byte +{ + /// + /// No flags: the feature's lookups skip the zero width joiner automatically, + /// match the zero width non-joiner, and match across syllables. + /// + None = 0, + + /// + /// The feature's lookups match the zero width non-joiner themselves instead of + /// the matcher treating it as transparent during context matching. + /// + ManualZwnj = 1 << 0, + + /// + /// The feature's lookups match the zero width joiner themselves instead of the + /// matcher treating it as transparent. + /// + ManualZwj = 1 << 1, + + /// + /// The feature's lookups match both joiners themselves. + /// + ManualJoiners = ManualZwnj | ManualZwj, + + /// + /// The feature's lookups never match across syllable boundaries: matching + /// latches the syllable at the cursor and refuses records of any other. + /// + PerSyllable = 1 << 2, + + /// + /// Alternate substitutions select from their set using the shaping buffer's + /// deterministic random sequence. + /// + Random = 1 << 3, +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/SkippingGlyphIterator.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/SkippingGlyphIterator.cs index 52b3c8e9a..cea8e545d 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/SkippingGlyphIterator.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/SkippingGlyphIterator.cs @@ -4,55 +4,194 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic; /// -/// An iterator over a glyph shaping collection that respects OpenType lookup flags, +/// An iterator over a glyph shaping buffer that respects OpenType lookup flags, /// skipping glyphs that should be ignored (marks, base glyphs, ligatures) based on the flags. /// internal struct SkippingGlyphIterator { private readonly FontMetrics fontMetrics; - private bool ignoreMarks; - private bool ignoreBaseGlyphs; - private bool ignoreLigatures; + + /// + /// The three ignore lookup flags collapsed into a mask over the packed glyph class + /// bits, so the common skip decision is a single bitwise test. + /// + private ushort ignoreClassMask; private ushort markAttachmentType; private bool useMarkFilteringSet; private ushort markFilteringSet; + /// + /// True when the current lookup flags cannot ignore any glyph, so stepping never + /// needs to fetch or classify glyphs. Most lookups carry no ignore flags, which + /// makes plain index arithmetic the common path. + /// + private bool skipsNothing; + + /// + /// True when stepping is plain index arithmetic: the lookup flags cannot + /// ignore any glyph and no default-ignorable transparency is active. Folded + /// into one test so the per-step fast path stays a single branch. + /// + private bool stepsDirectly; + + /// + /// True when this matcher reads the records the active pass has produced + /// rather than the input it has yet to consume. Backtrack reads that side: + /// the records behind the cursor were consumed by the pass, and a rule must + /// see what earlier lookups produced rather than the input they replaced. + /// + private bool readsProducedSide; + + /// + /// The bit recording that default-ignorable + /// transparency is active for the duration of sequence matching; plain + /// stepping outside a match keeps its historical semantics. + /// + private const byte TransparencyActiveFlag = 1 << 0; + + /// + /// The bit recording that the zero width non-joiner + /// is transparent rather than matchable. + /// + private const byte IgnoreZwnjFlag = 1 << 1; + + /// + /// The bit recording that the zero width joiner is + /// transparent rather than matchable. + /// + private const byte IgnoreZwjFlag = 1 << 2; + + /// + /// The bit recording that the substitution-visible + /// ignorables are transparent; set during positioning. + /// + private const byte IgnoreHiddenFlag = 1 << 3; + + /// + /// The bit recording that matching refuses records + /// outside the latched syllable. + /// + private const byte SyllableGateFlag = 1 << 4; + + /// + /// The bit recording that the applying lookup + /// matches per syllable, so stamping latches the syllable at the cursor. + /// + private const byte SyllableLatchFlag = 1 << 5; + + /// + /// The mask matched records must carry a bit of; all bits during context + /// matching, the applying lookup's mask otherwise. + /// + private uint matchMask; + + /// + /// Packed matcher state addressed through the named flag constants above, + /// keeping the struct narrow for the per-attempt copies matching makes. + /// + private byte matchFlags; + + /// + /// The latched syllable serial compared under . + /// + private byte syllableNumber; + + /// + /// The latched syllable type compared under . + /// + private byte syllableType; + /// /// Initializes a new instance of the struct. /// /// The font metrics for glyph class lookups. - /// The glyph shaping collection to iterate over. - /// The starting index in the collection. + /// The glyph shaping buffer to iterate over. + /// The starting index in the buffer. /// The lookup flags that control which glyphs to skip. /// The mark filtering set index, used when is set. public SkippingGlyphIterator( FontMetrics fontMetrics, - IGlyphShapingCollection collection, + ShapingBuffer buffer, int index, LookupFlags lookupFlags, ushort markFilteringSet) { this.fontMetrics = fontMetrics; - this.Collection = collection; + this.Collection = buffer; this.Index = index; - this.ignoreMarks = (lookupFlags & LookupFlags.IgnoreMarks) != 0; - this.ignoreBaseGlyphs = (lookupFlags & LookupFlags.IgnoreBaseGlyphs) != 0; - this.ignoreLigatures = (lookupFlags & LookupFlags.IgnoreLigatures) != 0; + this.ignoreClassMask = (ushort)(((lookupFlags & LookupFlags.IgnoreBaseGlyphs) != 0 ? GlyphShapingClass.BaseProp : 0) + | ((lookupFlags & LookupFlags.IgnoreLigatures) != 0 ? GlyphShapingClass.LigatureProp : 0) + | ((lookupFlags & LookupFlags.IgnoreMarks) != 0 ? GlyphShapingClass.MarkProp : 0)); this.markAttachmentType = (ushort)((int)(lookupFlags & LookupFlags.MarkAttachmentTypeMask) >> 8); this.useMarkFilteringSet = (lookupFlags & LookupFlags.UseMarkFilteringSet) != 0; this.markFilteringSet = markFilteringSet; + this.skipsNothing = this.ignoreClassMask == 0 && this.markAttachmentType == 0 && !this.useMarkFilteringSet; + this.stepsDirectly = this.skipsNothing; + this.matchMask = uint.MaxValue; } /// - /// Gets the glyph shaping collection being iterated. + /// Gets the glyph shaping buffer being iterated. /// - public IGlyphShapingCollection Collection { get; } + public ShapingBuffer Collection { get; } /// - /// Gets or sets the current index in the collection. + /// Gets or sets the current index in the buffer. /// public int Index { get; set; } + /// + /// Gets a value indicating whether the stamped matcher can encounter a + /// transparent record at all; matching keeps its solid-glyph walk when not. + /// + public readonly bool MatchTransparencyActive => (this.matchFlags & TransparencyActiveFlag) != 0; + + /// + /// Gets the number of records on the side this matcher reads. + /// + public readonly int RecordCount + => this.readsProducedSide ? this.Collection.PassOutputCount : this.Collection.Count; + + /// + /// Gets a reference to the record at the given index on the side this + /// matcher reads. + /// + /// The zero-based index on the read side. + /// A reference to the record. + public readonly ref GlyphShapingData RecordAt(int index) + { + if (this.readsProducedSide) + { + return ref this.Collection.PassOutputAt(index); + } + + return ref this.Collection[index]; + } + + /// + /// Points this matcher at the side backtrack reads, stamps it as context, + /// and steps to the first backtrack position. During a substitution pass + /// that side is the records the pass has produced; outside one the records + /// behind the cursor are the buffer itself. + /// + /// The first backtrack position, or a negative value when none remains. + public int StartBacktrack() + { + ShapingBuffer buffer = this.Collection; + if (buffer.IsPassActive) + { + this.readsProducedSide = true; + this.Index = buffer.PassOutputCount; + } + + this.SetMatchContext(0, true); + + // The matcher must test the immediately preceding record before deciding + // whether it is transparent. Stepping here would discard a transparent + // record that the backtrack sequence explicitly names. + return --this.Index; + } + /// /// Advances to the next non-skipped glyph in the forward direction. /// @@ -99,12 +238,16 @@ public int Increment(int count = 1) public void Reset(int index, LookupFlags lookupFlags, ushort markFilteringSet) { this.Index = index; - this.ignoreMarks = (lookupFlags & LookupFlags.IgnoreMarks) != 0; - this.ignoreBaseGlyphs = (lookupFlags & LookupFlags.IgnoreBaseGlyphs) != 0; - this.ignoreLigatures = (lookupFlags & LookupFlags.IgnoreLigatures) != 0; + this.ignoreClassMask = (ushort)(((lookupFlags & LookupFlags.IgnoreBaseGlyphs) != 0 ? GlyphShapingClass.BaseProp : 0) + | ((lookupFlags & LookupFlags.IgnoreLigatures) != 0 ? GlyphShapingClass.LigatureProp : 0) + | ((lookupFlags & LookupFlags.IgnoreMarks) != 0 ? GlyphShapingClass.MarkProp : 0)); this.markAttachmentType = (ushort)((int)(lookupFlags & LookupFlags.MarkAttachmentTypeMask) >> 8); this.useMarkFilteringSet = (lookupFlags & LookupFlags.UseMarkFilteringSet) != 0; this.markFilteringSet = markFilteringSet; + this.skipsNothing = this.ignoreClassMask == 0 && this.markAttachmentType == 0 && !this.useMarkFilteringSet; + this.stepsDirectly = this.skipsNothing; + this.matchMask = uint.MaxValue; + this.matchFlags = 0; } /// @@ -114,17 +257,159 @@ public void Reset(int index, LookupFlags lookupFlags, ushort markFilteringSet) private void Move(int direction) { this.Index += direction; - while (this.Index >= 0 && this.Index < this.Collection.Count) + + // When the flags cannot ignore anything and no transparency is active, + // ShouldIgnore is provably false for every glyph: the class mask test is + // against zero and the mark branches are disabled. Skip the per-glyph + // fetch and classification entirely. + if (this.stepsDirectly) { - if (!this.ShouldIgnore(this.Index)) + return; + } + + int limit = this.RecordCount; + while (this.Index >= 0 && this.Index < limit) + { + // The class-mask test only runs when the flags can actually ignore + // something; a skips-nothing iterator steps straight to transparency. + if (this.skipsNothing || !this.ShouldIgnore(this.Index)) { - break; + if ((this.matchFlags & TransparencyActiveFlag) == 0) + { + break; + } + + ref GlyphShapingData data = ref this.RecordAt(this.Index); + if (!this.IsTransparent(ref data)) + { + break; + } } this.Index += direction; } } + /// + /// Determines whether the glyph at the given index is ignored under the + /// current lookup flags without moving the iterator. The pass driver tests + /// each record with this before attempting a lookup, copying ignored records + /// through to the output side untouched. + /// + /// The index of the glyph to check. + /// if the glyph is ignored; otherwise, . + public readonly bool IsIgnored(int index) => !this.skipsNothing && this.ShouldIgnore(index); + + /// + /// Packs the matcher flags for the buffer's applying lookup, computed once + /// when the lookup is stamped rather than on every match attempt: which + /// joiner classes are transparent, whether transparency can apply at all, + /// and whether stamping latches a syllable. + /// + /// The buffer carrying the applying lookup's state. + /// Whether the flags serve backtrack or lookahead matching. + /// The packed matcher flags. + public static byte PackMatchFlags(ShapingBuffer buffer, bool contextMatch) + { + bool positioning = buffer.Role == ShapingBufferRole.Positioning; + + // A buffer holding no default ignorables cannot contain a transparent + // record, so matching keeps its solid-glyph fast paths. + byte flags = 0; + if (buffer.HasDefaultIgnorables) + { + flags |= TransparencyActiveFlag; + } + + if (positioning || (contextMatch && buffer.LookupAutoZwnj)) + { + flags |= IgnoreZwnjFlag; + } + + if (contextMatch || buffer.LookupAutoZwj) + { + flags |= IgnoreZwjFlag; + } + + if (positioning) + { + flags |= IgnoreHiddenFlag; + } + + if (!positioning && buffer.LookupPerSyllable) + { + flags |= SyllableLatchFlag; + } + + return flags; + } + + /// + /// Activates default-ignorable transparency for sequence matching under the + /// applying lookup's joiner handling: a default ignorable whose joiner bits + /// the lookup treats as transparent is stepped over unless it matches the + /// sequence position itself. Latches the syllable at the current index when + /// the lookup matches per syllable. Copies of the iterator carry the stamped + /// state; the caller's iterator is unaffected by helpers stamping their own + /// copies. + /// + /// The applying lookup's mask; ignored during context matching. + /// Whether this matcher walks backtrack or lookahead context. + public void SetMatchContext(uint mask, bool contextMatch) + { + ShapingBuffer buffer = this.Collection; + byte flags = contextMatch ? buffer.ContextMatchFlags : buffer.InputMatchFlags; + + this.matchMask = contextMatch ? uint.MaxValue : mask; + if ((flags & SyllableLatchFlag) != 0 && (uint)this.Index < (uint)buffer.Count) + { + SyllableInfo syllable = buffer[this.Index].Syllable; + this.syllableNumber = (byte)syllable.Number; + this.syllableType = (byte)syllable.Type; + if (syllable.Type != SyllableType.None || syllable.Number != 0) + { + flags |= SyllableGateFlag; + } + } + + this.matchFlags = flags; + this.stepsDirectly = this.skipsNothing && (flags & TransparencyActiveFlag) == 0; + } + + /// + /// Determines whether the record is transparent to the current matcher: a + /// default ignorable whose joiner bits the matcher ignores. Transparent + /// records are stepped over during matching unless they match the sequence + /// position themselves. + /// + /// The record to test. + /// when the record may be stepped over. + public readonly bool IsTransparent(ref GlyphShapingData data) + => (this.matchFlags & TransparencyActiveFlag) != 0 + && data.IsDefaultIgnorable + && ((this.matchFlags & IgnoreZwnjFlag) != 0 || !data.IsZwnj) + && ((this.matchFlags & IgnoreZwjFlag) != 0 || !data.IsZwj) + && ((this.matchFlags & IgnoreHiddenFlag) != 0 || !data.IsHiddenIgnorable); + + /// + /// Determines whether the record passes the matcher's mask and syllable + /// gates; the shape test itself runs only for records that do. + /// + /// The record to test. + /// when the record may be match-tested. + public readonly bool MayMatch(ref GlyphShapingData data) + => (data.FeatureMask & this.matchMask) != 0 + && ((this.matchFlags & SyllableGateFlag) == 0 || (data.Syllable.Number == this.syllableNumber && (byte)data.Syllable.Type == this.syllableType)); + + /// + /// Determines whether the glyph at the given index fails the lookup's glyph + /// property check and is therefore always skipped, whatever the sequence + /// expects. + /// + /// The index of the glyph to check. + /// when the glyph never participates. + public readonly bool IsPropertySkipped(int index) => !this.skipsNothing && this.ShouldIgnore(index); + /// /// Determines whether the glyph at the given index should be ignored based on the current lookup flags. /// @@ -132,22 +417,36 @@ private void Move(int direction) /// if the glyph should be skipped; otherwise, . private readonly bool ShouldIgnore(int index) { - GlyphShapingData data = this.Collection[index]; - GlyphShapingClass shapingClass = AdvancedTypographicUtils.GetGlyphShapingClass(this.fontMetrics, data.GlyphId, data); + ref GlyphShapingData data = ref this.RecordAt(index); + + // The shaping class is cached on the glyph keyed by glyph id; test the cache + // inline so the common hit path avoids the classification call entirely. + ushort props = data.ShapingClassCacheKey == data.GlyphId + ? data.CachedShapingClass.Props + : AdvancedTypographicUtils.GetGlyphShapingClass(this.fontMetrics, this.Collection, data.GlyphId, ref data).Props; - if (this.useMarkFilteringSet && shapingClass.IsMark) + if ((props & this.ignoreClassMask) != 0) + { + return true; + } + + if ((props & GlyphShapingClass.MarkProp) != 0) { // Skip marks not in the lookup's MarkFilteringSet. // This requires GDEF MarkGlyphSetsDef support. - if (!AdvancedTypographicUtils.IsInMarkFilteringSet(this.fontMetrics, this.markFilteringSet, data.GlyphId)) + if (this.useMarkFilteringSet && !AdvancedTypographicUtils.IsInMarkFilteringSet(this.fontMetrics, this.markFilteringSet, data.GlyphId)) + { + return true; + } + + // The high byte carries the mark attachment class; a lookup restricted to + // one attachment class skips marks of any other. + if (this.markAttachmentType > 0 && (props >> 8) != this.markAttachmentType) { return true; } } - return (this.ignoreMarks && shapingClass.IsMark) || - (this.ignoreBaseGlyphs && shapingClass.IsBase) || - (this.ignoreLigatures && shapingClass.IsLigature) || - (this.markAttachmentType > 0 && shapingClass.IsMark && shapingClass.MarkAttachmentType != this.markAttachmentType); + return false; } } diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/SyllableInfo.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/SyllableInfo.cs new file mode 100644 index 000000000..8e6ff4ebf --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/SyllableInfo.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using static SixLabors.Fonts.Unicode.Resources.IndicShapingData; + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// Per-glyph syllable classification assigned by the Indic, Myanmar, and Universal +/// Shaping Engine shapers, packed into a single word of byte lanes so classification +/// allocates nothing, every comparison is an integer compare, and the glyph record +/// stays narrow. of means no +/// classification has been assigned; consumers treat such glyphs as outside every +/// syllable. +/// +internal struct SyllableInfo +{ + /// + /// The bit offset of the byte lane in . + /// + private const int TypeShift = 8; + + /// + /// The bit offset of the byte lane in . + /// + private const int CategoryShift = 16; + + /// + /// The bit offset of the byte lane in . + /// + private const int PositionShift = 24; + + /// + /// The bit offset of the byte lane in . + /// + private const int UseCategoryShift = 32; + + /// + /// The packed classification: one byte lane per property, with + /// in the lowest byte and the lanes above it addressed + /// through the shift constants. + /// + private ulong bits; + + /// + /// Gets or sets the running syllable number within the shaping pass. Stored as a + /// byte: consumers only compare the numbers of nearby glyphs for equality, and + /// adjacent syllables always differ by one, so wrap-around cannot alias them. + /// + public int Number + { + readonly get => (byte)this.bits; + set => this.bits = (this.bits & ~0xFFUL) | (byte)value; + } + + /// + /// Gets or sets the syllable cluster type produced by the state machine, or + /// when unassigned. + /// + public SyllableType Type + { + readonly get => (SyllableType)(byte)(this.bits >> TypeShift); + set => this.bits = (this.bits & ~(0xFFUL << TypeShift)) | ((ulong)(byte)value << TypeShift); + } + + /// + /// Gets or sets the Indic or Myanmar shaping category. + /// + public Categories IndicCategory + { + readonly get => (Categories)(byte)(this.bits >> CategoryShift); + set => this.bits = (this.bits & ~(0xFFUL << CategoryShift)) | ((ulong)(byte)value << CategoryShift); + } + + /// + /// Gets or sets the Indic or Myanmar positional class. + /// + public Positions IndicPosition + { + readonly get => (Positions)(byte)(this.bits >> PositionShift); + set => this.bits = (this.bits & ~(0xFFUL << PositionShift)) | ((ulong)(byte)value << PositionShift); + } + + /// + /// Gets or sets the Universal Shaping Engine category as the symbol index the + /// state machine consumes, which is also the index into the generated category + /// name table. + /// + public int UseCategory + { + readonly get => (byte)(this.bits >> UseCategoryShift); + set => this.bits = (this.bits & ~(0xFFUL << UseCategoryShift)) | ((ulong)(byte)value << UseCategoryShift); + } + + /// + /// Gets the Myanmar view of : the Myanmar shaper shares + /// the Indic category storage and reads it through its own enum. + /// + public readonly MyanmarCategories MyanmarCategory => (MyanmarCategories)this.IndicCategory; +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/SyllableType.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/SyllableType.cs new file mode 100644 index 000000000..19d93aa4f --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/SyllableType.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// The syllable cluster types produced by the Indic, Myanmar, and Universal Shaping +/// Engine state machines. Stored per glyph as a value so syllable classification is +/// integer comparison; the values mirror the top-level grammar rule names. +/// +internal enum SyllableType +{ + /// + /// No syllable information has been assigned. + /// + None = 0, + + /// + /// A grammar rule name with no dedicated member. Comparisons against known types + /// are false, matching the behavior of an unrecognized rule name string. + /// + Other, + + /// + /// The broken_cluster rule: a cluster missing its base, repaired with a dotted circle. + /// + BrokenCluster, + + /// + /// The consonant_syllable rule. + /// + ConsonantSyllable, + + /// + /// The hieroglyph_cluster rule. + /// + HieroglyphCluster, + + /// + /// The independent_cluster rule. + /// + IndependentCluster, + + /// + /// The synthetic type for codepoints outside every syllable match. Assigned by the + /// shapers, not by a grammar rule. + /// + NonIndicCluster, + + /// + /// The non_cluster rule. + /// + NonCluster, + + /// + /// The number_joiner_terminated_cluster rule. + /// + NumberJoinerTerminatedCluster, + + /// + /// The numeral_cluster rule. + /// + NumeralCluster, + + /// + /// The sakot_terminated_cluster rule. + /// + SakotTerminatedCluster, + + /// + /// The standalone_cluster rule. + /// + StandaloneCluster, + + /// + /// The standard_cluster rule. + /// + StandardCluster, + + /// + /// The symbol_cluster rule. + /// + SymbolCluster, + + /// + /// The virama_terminated_cluster rule. + /// + ViramaTerminatedCluster, + + /// + /// The vowel_syllable rule. + /// + VowelSyllable, +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/SyllableTypeMap.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/SyllableTypeMap.cs new file mode 100644 index 000000000..e7bc53bac --- /dev/null +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/SyllableTypeMap.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Tables.AdvancedTypographic; + +/// +/// Maps state machine grammar rule names to values. Invoked +/// when a shaper translates its machine's tag rows into a per-state table, so match +/// handling and per-glyph storage never touch the rule name strings. +/// +internal static class SyllableTypeMap +{ + /// + /// Maps a grammar rule name to its . + /// + /// The rule name reported by the state machine match. + /// + /// The corresponding type, or for a rule name with + /// no dedicated member. Unknown names must not throw: an unrecognized rule name + /// string previously compared false against every known literal, and the mapping + /// preserves that behavior. + /// + public static SyllableType FromTag(string tag) => tag switch + { + "broken_cluster" => SyllableType.BrokenCluster, + "consonant_syllable" => SyllableType.ConsonantSyllable, + "hieroglyph_cluster" => SyllableType.HieroglyphCluster, + "independent_cluster" => SyllableType.IndependentCluster, + "non_khmer_cluster" => SyllableType.NonIndicCluster, + "non_cluster" => SyllableType.NonCluster, + "number_joiner_terminated_cluster" => SyllableType.NumberJoinerTerminatedCluster, + "numeral_cluster" => SyllableType.NumeralCluster, + "sakot_terminated_cluster" => SyllableType.SakotTerminatedCluster, + "standalone_cluster" => SyllableType.StandaloneCluster, + "standard_cluster" => SyllableType.StandardCluster, + "symbol_cluster" => SyllableType.SymbolCluster, + "virama_terminated_cluster" => SyllableType.ViramaTerminatedCluster, + "vowel_syllable" => SyllableType.VowelSyllable, + _ => SyllableType.Other, + }; + + /// + /// Builds the per-state syllable type table for a machine's tag rows, translated + /// once at machine construction so match handling reads an array element instead + /// of mapping a rule name string per match. States without a tag map to + /// . + /// + /// The machine's per-state tag rows. + /// The per-state table. + public static SyllableType[] FromMachineTags(string[][] tags) + { + SyllableType[] types = new SyllableType[tags.Length]; + for (int i = 0; i < tags.Length; i++) + { + string[] row = tags[i]; + types[i] = row.Length > 0 ? FromTag(row[0]) : SyllableType.None; + } + + return types; + } +} diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/UnicodeScriptTagMap.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/UnicodeScriptTagMap.cs index 3b12b0df2..27ac0f82b 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/UnicodeScriptTagMap.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/UnicodeScriptTagMap.cs @@ -51,7 +51,7 @@ private static UnicodeScriptTagMap CreateMap() { ScriptClass.Bamum, new[] { Tag.Parse("bamu") } }, { ScriptClass.BassaVah, new[] { Tag.Parse("bass") } }, { ScriptClass.Batak, new[] { Tag.Parse("batk") } }, - { ScriptClass.Bengali, new[] { Tag.Parse("bng2"), Tag.Parse("beng") } }, + { ScriptClass.Bengali, new[] { Tag.Parse("bng3"), Tag.Parse("bng2"), Tag.Parse("beng") } }, { ScriptClass.Bhaiksuki, new[] { Tag.Parse("bhks") } }, { ScriptClass.Bopomofo, new[] { Tag.Parse("bopo") } }, { ScriptClass.Brahmi, new[] { Tag.Parse("brah") } }, @@ -69,7 +69,7 @@ private static UnicodeScriptTagMap CreateMap() { ScriptClass.Cypriot, new[] { Tag.Parse("cprt") } }, { ScriptClass.Cyrillic, new[] { Tag.Parse("cyrl") } }, { ScriptClass.Default, new[] { Tag.Parse("DFLT"), Tag.Parse("dflt"), Tag.Parse("latn") } }, - { ScriptClass.Devanagari, new[] { Tag.Parse("dev2"), Tag.Parse("deva") } }, + { ScriptClass.Devanagari, new[] { Tag.Parse("dev3"), Tag.Parse("dev2"), Tag.Parse("deva") } }, { ScriptClass.DivesAkuru, new[] { Tag.Parse("diak") } }, { ScriptClass.Dogra, new[] { Tag.Parse("dogr") } }, { ScriptClass.Deseret, new[] { Tag.Parse("dsrt") } }, @@ -85,8 +85,8 @@ private static UnicodeScriptTagMap CreateMap() { ScriptClass.Gothic, new[] { Tag.Parse("goth") } }, { ScriptClass.Grantha, new[] { Tag.Parse("gran") } }, { ScriptClass.Greek, new[] { Tag.Parse("grek") } }, - { ScriptClass.Gujarati, new[] { Tag.Parse("gjr2"), Tag.Parse("gujr") } }, - { ScriptClass.Gurmukhi, new[] { Tag.Parse("gur2"), Tag.Parse("guru") } }, + { ScriptClass.Gujarati, new[] { Tag.Parse("gjr3"), Tag.Parse("gjr2"), Tag.Parse("gujr") } }, + { ScriptClass.Gurmukhi, new[] { Tag.Parse("gur3"), Tag.Parse("gur2"), Tag.Parse("guru") } }, { ScriptClass.Hangul, new[] { Tag.Parse("hang") } }, { ScriptClass.Han, new[] { Tag.Parse("hani") } }, { ScriptClass.Hanunoo, new[] { Tag.Parse("hano") } }, @@ -106,7 +106,7 @@ private static UnicodeScriptTagMap CreateMap() { ScriptClass.Khmer, new[] { Tag.Parse("khmr") } }, { ScriptClass.Khojki, new[] { Tag.Parse("khoj") } }, { ScriptClass.KhitanSmallScript, new[] { Tag.Parse("kits") } }, - { ScriptClass.Kannada, new[] { Tag.Parse("knd2"), Tag.Parse("knda") } }, + { ScriptClass.Kannada, new[] { Tag.Parse("knd3"), Tag.Parse("knd2"), Tag.Parse("knda") } }, { ScriptClass.Kaithi, new[] { Tag.Parse("kthi") } }, { ScriptClass.TaiTham, new[] { Tag.Parse("lana") } }, { ScriptClass.Lao, new[] { Tag.Parse("lao ") } }, @@ -127,13 +127,14 @@ private static UnicodeScriptTagMap CreateMap() { ScriptClass.MendeKikakui, new[] { Tag.Parse("mend") } }, { ScriptClass.MeroiticCursive, new[] { Tag.Parse("merc") } }, { ScriptClass.MeroiticHieroglyphs, new[] { Tag.Parse("mero") } }, - { ScriptClass.Malayalam, new[] { Tag.Parse("mlm2"), Tag.Parse("mlym") } }, + { ScriptClass.Malayalam, new[] { Tag.Parse("mlm3"), Tag.Parse("mlm2"), Tag.Parse("mlym") } }, { ScriptClass.Modi, new[] { Tag.Parse("modi") } }, { ScriptClass.Mongolian, new[] { Tag.Parse("mong") } }, { ScriptClass.Mro, new[] { Tag.Parse("mroo") } }, { ScriptClass.MeeteiMayek, new[] { Tag.Parse("mtei") } }, { ScriptClass.Multani, new[] { Tag.Parse("mult") } }, { ScriptClass.Myanmar, new[] { Tag.Parse("mym2"), Tag.Parse("mymr") } }, + { ScriptClass.MyanmarZawgyi, new[] { Tag.Parse("qaag") } }, { ScriptClass.Nandinagari, new[] { Tag.Parse("nand") } }, { ScriptClass.OldNorthArabian, new[] { Tag.Parse("narb") } }, { ScriptClass.Nabataean, new[] { Tag.Parse("nbat") } }, @@ -143,7 +144,7 @@ private static UnicodeScriptTagMap CreateMap() { ScriptClass.Ogham, new[] { Tag.Parse("ogam") } }, { ScriptClass.OlChiki, new[] { Tag.Parse("olck") } }, { ScriptClass.OldTurkic, new[] { Tag.Parse("orkh") } }, - { ScriptClass.Oriya, new[] { Tag.Parse("ory2"), Tag.Parse("orya") } }, + { ScriptClass.Oriya, new[] { Tag.Parse("ory3"), Tag.Parse("ory2"), Tag.Parse("orya") } }, { ScriptClass.Osage, new[] { Tag.Parse("osge") } }, { ScriptClass.Osmanya, new[] { Tag.Parse("osma") } }, { ScriptClass.OldUyghur, new[] { Tag.Parse("ougr") } }, @@ -179,10 +180,10 @@ private static UnicodeScriptTagMap CreateMap() { ScriptClass.Takri, new[] { Tag.Parse("takr") } }, { ScriptClass.TaiLe, new[] { Tag.Parse("tale") } }, { ScriptClass.NewTaiLue, new[] { Tag.Parse("talu") } }, - { ScriptClass.Tamil, new[] { Tag.Parse("tml2"), Tag.Parse("taml") } }, + { ScriptClass.Tamil, new[] { Tag.Parse("tml3"), Tag.Parse("tml2"), Tag.Parse("taml") } }, { ScriptClass.Tangut, new[] { Tag.Parse("tang") } }, { ScriptClass.TaiViet, new[] { Tag.Parse("tavt") } }, - { ScriptClass.Telugu, new[] { Tag.Parse("tel2"), Tag.Parse("telu") } }, + { ScriptClass.Telugu, new[] { Tag.Parse("tel3"), Tag.Parse("tel2"), Tag.Parse("telu") } }, { ScriptClass.Tifinagh, new[] { Tag.Parse("tfng") } }, { ScriptClass.Tagalog, new[] { Tag.Parse("tglg") } }, { ScriptClass.Thaana, new[] { Tag.Parse("thaa") } }, diff --git a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Variations/GlyphVariationProcessor.cs b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Variations/GlyphVariationProcessor.cs index 5f8429bf0..229cf30e8 100644 --- a/src/SixLabors.Fonts/Tables/AdvancedTypographic/Variations/GlyphVariationProcessor.cs +++ b/src/SixLabors.Fonts/Tables/AdvancedTypographic/Variations/GlyphVariationProcessor.cs @@ -19,6 +19,31 @@ namespace SixLabors.Fonts.Tables.AdvancedTypographic.Variations; /// internal class GlyphVariationProcessor { + /// + /// The number of metric points appended to each glyph's outline points. + /// + private const int PhantomPointCount = 4; + + /// + /// The horizontal leading-side point within the metric point suffix. + /// + private const int LeftPhantomPoint = 0; + + /// + /// The horizontal trailing-side point within the metric point suffix. + /// + private const int RightPhantomPoint = 1; + + /// + /// The vertical leading-side point within the metric point suffix. + /// + private const int TopPhantomPoint = 2; + + /// + /// The vertical trailing-side point within the metric point suffix. + /// + private const int BottomPhantomPoint = 3; + /// /// The item variation store shared by CFF2 and other variation lookups. /// @@ -124,8 +149,11 @@ public GlyphVariationProcessor( /// /// The glyph identifier. /// The glyph vector whose control points will be modified in-place. - public void TransformPoints(ushort glyphId, ref GlyphVector glyphPoints) + /// The horizontal and vertical advance adjustments encoded by the glyph's metric points. + public void TransformPoints(ushort glyphId, ref GlyphVector glyphPoints, out Vector2 advanceAdjustment) { + advanceAdjustment = Vector2.Zero; + if (this.gVar is null) { return; @@ -144,11 +172,11 @@ public void TransformPoints(ushort glyphId, ref GlyphVector glyphPoints) if (glyphPoints.IsComposite && glyphPoints.CompositeComponents is not null) { - this.TransformCompositePoints(variationData, ref glyphPoints); + this.TransformCompositePoints(variationData, ref glyphPoints, ref advanceAdjustment); } else { - this.TransformSimplePoints(variationData, ref glyphPoints); + this.TransformSimplePoints(variationData, ref glyphPoints, ref advanceAdjustment); } } @@ -157,15 +185,14 @@ public void TransformPoints(ushort glyphId, ref GlyphVector glyphPoints) /// /// The glyph's variation data from the gvar table. /// The glyph vector whose control points will be modified in-place. - private void TransformSimplePoints(GlyphVariationData variationData, ref GlyphVector glyphPoints) + /// The accumulated horizontal and vertical advance adjustments. + private void TransformSimplePoints(GlyphVariationData variationData, ref GlyphVector glyphPoints, ref Vector2 advanceAdjustment) { IList controlPoints = glyphPoints.ControlPoints; int pointCount = controlPoints.Count; - // gvar encodes deltas for outline points + 4 phantom points (LSB, advance width, - // TSB, advance height). We must decode all of them so X/Y delta streams stay aligned, - // even though we only apply deltas to the outline points. - const int PhantomPointCount = 4; + // The tuple carries outline deltas followed by four metric-point deltas. + // Decode both groups together so the X and Y streams stay aligned. int totalPointCount = pointCount + PhantomPointCount; // Clone the original points for IUP reference (interpolation needs unmodified originals). @@ -197,6 +224,8 @@ private void TransformSimplePoints(GlyphVariationData variationData, ref GlyphVe continue; } + ApplyPhantomDeltas(pointCount, pointNumbers, deltasX, deltasY, factor, ref advanceAdjustment); + bool allPoints = pointNumbers is null or { Length: 0 }; if (allPoints) @@ -267,13 +296,16 @@ private void TransformSimplePoints(GlyphVariationData variationData, ref GlyphVe /// After applying deltas, the offset changes are propagated to all assembled /// outline points belonging to each component. /// - private void TransformCompositePoints(GlyphVariationData variationData, ref GlyphVector glyphPoints) + /// The glyph's variation data from the gvar table. + /// The glyph vector whose control points will be modified in-place. + /// The accumulated horizontal and vertical advance adjustments. + private void TransformCompositePoints(GlyphVariationData variationData, ref GlyphVector glyphPoints, ref Vector2 advanceAdjustment) { CompositeComponent[] components = glyphPoints.CompositeComponents!; int componentCount = components.Length; // gvar "point count" for composites = number of components + 4 phantom points. - int syntheticPointCount = componentCount + 4; + int syntheticPointCount = componentCount + PhantomPointCount; // Build synthetic points from component offsets. using Buffer synXBuf = new(syntheticPointCount, clear: true); @@ -313,6 +345,8 @@ private void TransformCompositePoints(GlyphVariationData variationData, ref Glyp continue; } + ApplyPhantomDeltas(componentCount, pointNumbers, deltasX, deltasY, factor, ref advanceAdjustment); + bool allPoints = pointNumbers is null or { Length: 0 }; if (allPoints) @@ -366,35 +400,37 @@ private void TransformCompositePoints(GlyphVariationData variationData, ref Glyp } /// - /// Gets the horizontal advance width adjustment for the given glyph from the HVAR table. - /// Returns 0 if no HVAR table is present. + /// Gets the rounded horizontal advance adjustment, preferring HVAR data over + /// the adjustment encoded by the glyph's metric points. /// /// The glyph identifier. - /// The advance width delta value. - public float AdvanceAdjustment(int glyphId) + /// The adjustment encoded by the glyph's metric points. + /// The rounded advance width adjustment. + public int AdvanceAdjustment(int glyphId, float glyphAdjustment) { if (this.hVar is null) { - return 0; + return RoundAdjustment(glyphAdjustment); } - return this.GetMetricDelta(glyphId, this.hVar.AdvanceWidthMapping, this.hVar.ItemVariationStore); + return RoundAdjustment(this.GetMetricDelta(glyphId, this.hVar.AdvanceWidthMapping, this.hVar.ItemVariationStore)); } /// - /// Gets the vertical advance height adjustment for the given glyph from the VVAR table. - /// Returns 0 if no VVAR table is present. + /// Gets the rounded vertical advance adjustment, preferring VVAR data over + /// the adjustment encoded by the glyph's metric points. /// /// The glyph identifier. - /// The advance height delta value. - public float VerticalAdvanceAdjustment(int glyphId) + /// The adjustment encoded by the glyph's metric points. + /// The rounded advance height adjustment. + public int VerticalAdvanceAdjustment(int glyphId, float glyphAdjustment) { if (this.vVar is null) { - return 0; + return RoundAdjustment(glyphAdjustment); } - return this.GetMetricDelta(glyphId, this.vVar.AdvanceWidthMapping, this.vVar.ItemVariationStore); + return RoundAdjustment(this.GetMetricDelta(glyphId, this.vVar.AdvanceWidthMapping, this.vVar.ItemVariationStore)); } /// @@ -868,6 +904,70 @@ private float GetMetricDelta(int glyphId, DeltaSetIndexMap[]? mapping, ItemVaria return this.ComputeDelta(store, outerIndex, innerIndex); } + /// + /// Accumulates the changes to the two advances from one tuple's metric-point deltas. + /// + /// The index of the first metric point. + /// The explicitly varied point indices, or when every point is present. + /// The horizontal point deltas. + /// The vertical point deltas. + /// The tuple's scalar at the current variation coordinates. + /// The accumulated horizontal and vertical advance adjustments. + private static void ApplyPhantomDeltas(int phantomStart, ushort[]? pointNumbers, short[] deltasX, short[] deltasY, float factor, ref Vector2 advanceAdjustment) + { + int left = phantomStart + LeftPhantomPoint; + int right = phantomStart + RightPhantomPoint; + int top = phantomStart + TopPhantomPoint; + int bottom = phantomStart + BottomPhantomPoint; + + if (pointNumbers is null or { Length: 0 }) + { + // Advances are the distance between each pair of metric points, so a + // leading-side movement subtracts while a trailing-side movement adds. + if (right < deltasX.Length) + { + advanceAdjustment.X += (deltasX[right] - deltasX[left]) * factor; + } + + if (bottom < deltasY.Length) + { + advanceAdjustment.Y += (deltasY[top] - deltasY[bottom]) * factor; + } + + return; + } + + int deltaCount = Math.Min(pointNumbers.Length, Math.Min(deltasX.Length, deltasY.Length)); + for (int i = 0; i < deltaCount; i++) + { + int point = pointNumbers[i]; + if (point == left) + { + advanceAdjustment.X -= deltasX[i] * factor; + } + else if (point == right) + { + advanceAdjustment.X += deltasX[i] * factor; + } + else if (point == top) + { + advanceAdjustment.Y += deltasY[i] * factor; + } + else if (point == bottom) + { + advanceAdjustment.Y -= deltasY[i] * factor; + } + } + } + + /// + /// Rounds a variation adjustment to the integer design-unit grid. + /// + /// The unrounded adjustment. + /// The nearest integer adjustment, with halfway values rounded toward positive infinity. + private static int RoundAdjustment(float value) + => (int)MathF.Floor(value + 0.5F); + /// /// Decodes deferred delta data for the all-points case. /// diff --git a/src/SixLabors.Fonts/Tables/Cff/CffGlyphMetrics.cs b/src/SixLabors.Fonts/Tables/Cff/CffGlyphMetrics.cs index 97ac2631d..c5cfc7ad3 100644 --- a/src/SixLabors.Fonts/Tables/Cff/CffGlyphMetrics.cs +++ b/src/SixLabors.Fonts/Tables/Cff/CffGlyphMetrics.cs @@ -30,7 +30,7 @@ internal class CffGlyphMetrics : FontGlyphMetrics /// The text attributes. /// The text decorations. /// The glyph type. - internal CffGlyphMetrics( + public CffGlyphMetrics( StreamFontMetrics fontMetrics, ushort glyphId, CodePoint codePoint, @@ -59,81 +59,18 @@ internal CffGlyphMetrics( glyphType) => this.glyphData = glyphData; - /// - /// Initializes a new instance of the class with offset, scale, and text run parameters. - /// - /// The font metrics. - /// The glyph identifier. - /// The Unicode code point. - /// The CFF glyph data containing the charstring program. - /// The glyph bounding box. - /// The advance width. - /// The advance height. - /// The left side bearing. - /// The top side bearing. - /// The units per em. - /// The glyph offset. - /// The scale factor. - /// The text run for rendering. - /// The glyph type. - internal CffGlyphMetrics( - StreamFontMetrics fontMetrics, - ushort glyphId, - CodePoint codePoint, - CffGlyphData glyphData, - Bounds bounds, - ushort advanceWidth, - ushort advanceHeight, - short leftSideBearing, - short topSideBearing, - ushort unitsPerEM, - Vector2 offset, - Vector2 scaleFactor, - TextRun textRun, - GlyphType glyphType) - : base( - fontMetrics, - glyphId, - codePoint, - bounds, - advanceWidth, - advanceHeight, - leftSideBearing, - topSideBearing, - unitsPerEM, - offset, - scaleFactor, - textRun, - glyphType) - => this.glyphData = glyphData; - - /// - internal override FontGlyphMetrics CloneForRendering(TextRun textRun) - => new CffGlyphMetrics( - this.FontMetrics, - this.GlyphId, - this.CodePoint, - this.glyphData, - this.Bounds, - this.AdvanceWidth, - this.AdvanceHeight, - this.LeftSideBearing, - this.TopSideBearing, - this.UnitsPerEm, - this.Offset, - this.ScaleFactor, - textRun, - this.GlyphType); - /// internal override void RenderOutlineTo( IGlyphRenderer renderer, Vector2 glyphOrigin, GlyphLayoutMode mode, + TextRun? textRun, + Vector2 positionOffset, + Vector2 positionedAdvance, float scaledPPEM, HintingMode hintingMode) { - Matrix3x2 transform = this.GetOutlineTransform(mode); + Matrix3x2 transform = this.GetOutlineTransform(mode, textRun); Vector2 scale = new Vector2(scaledPPEM) / this.ScaleFactor; @@ -146,8 +83,8 @@ internal override void RenderOutlineTo( scale *= new Vector2((float)(fm[0] * upm), (float)(fm[3] * upm)); } - Vector2 scaledOffset = this.Offset * scale; - float boldStrength = this.GetSyntheticBoldStrength(scaledPPEM); + Vector2 scaledOffset = (this.Offset + positionOffset) * scale; + float boldStrength = this.GetSyntheticBoldStrength(scaledPPEM, textRun); if (boldStrength > 0F) { // Flush through the supplied renderer before glyph completion so skip-ink observes diff --git a/src/SixLabors.Fonts/Tables/General/CMap/Format13SubTable.cs b/src/SixLabors.Fonts/Tables/General/CMap/Format13SubTable.cs new file mode 100644 index 000000000..19e096b59 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/General/CMap/Format13SubTable.cs @@ -0,0 +1,173 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts.Unicode; +using SixLabors.Fonts.WellKnownIds; + +namespace SixLabors.Fonts.Tables.General.CMap; + +/// +/// Format 13 maps ranges of character codes onto a single glyph each, rather than +/// onto a run of glyphs as format 12 does. Fonts that answer for characters they +/// have no artwork for use it, mapping every character of a range to one glyph +/// such as a blank or a last-resort shape. +/// +/// +internal sealed class Format13SubTable : CMapSubTable +{ + /// + /// Initializes a new instance of the class. + /// + /// The language code for this subtable. + /// The platform identifier. + /// The platform-specific encoding identifier. + /// The array of constant map groups. + public Format13SubTable(uint language, PlatformIDs platform, ushort encoding, ConstantMapGroup[] groups) + : base(platform, encoding, 13) + { + this.Language = language; + this.ConstantMapGroups = groups; + } + + /// + /// Gets the array of constant map groups defining character-to-glyph mappings. + /// + public ConstantMapGroup[] ConstantMapGroups { get; } + + /// + /// Gets the language code for this subtable. + /// + public uint Language { get; } + + /// + public override bool TryGetGlyphId(CodePoint codePoint, out ushort glyphId) + { + int charAsInt = codePoint.Value; + + for (int i = 0; i < this.ConstantMapGroups.Length; i++) + { + ref ConstantMapGroup group = ref this.ConstantMapGroups[i]; + + if (charAsInt >= group.StartCodePoint && charAsInt <= group.EndCodePoint) + { + glyphId = (ushort)group.GlyphId; + return true; + } + } + + glyphId = 0; + return false; + } + + /// + public override bool TryGetCodePoint(ushort glyphId, out CodePoint codePoint) + { + // The mapping is many to one, so a glyph names a whole range of + // characters and the first of them is the only sensible answer. + for (int i = 0; i < this.ConstantMapGroups.Length; i++) + { + ref ConstantMapGroup group = ref this.ConstantMapGroups[i]; + if (glyphId == group.GlyphId) + { + codePoint = new CodePoint((int)group.StartCodePoint); + return true; + } + } + + codePoint = default; + return false; + } + + /// + public override IEnumerable GetAvailableCodePoints() + => this.ConstantMapGroups.SelectMany(group => + { + int start = (int)group.StartCodePoint; + int end = (int)group.EndCodePoint; + return Enumerable.Range(start, end - start + 1); + }); + + /// + /// Loads one or more instances from the specified encoding records and reader. + /// + /// The encoding records that share this subtable. + /// The binary reader positioned after the format field. + /// An enumerable of instances, one per encoding record. + public static IEnumerable Load(IEnumerable encodings, BigEndianBinaryReader reader) + { + // 'cmap' Subtable Format 13: + // Type | Name | Description + // -----------------|-------------------|------------------------------------------------------------------------ + // uint16 | format | Subtable format; set to 13. + // uint16 | reserved | Reserved; set to 0 + // uint32 | length | Byte length of this subtable (including the header) + // uint32 | language | For requirements on use of the language field, see "Use of the language field in 'cmap' subtables" in this document. + // uint32 | numGroups | Number of groupings which follow + // ConstantMapGroup | groups[numGroups] | Array of ConstantMapGroup records. + + // format has already been read by this point skip it + ushort reserved = reader.ReadUInt16(); + uint length = reader.ReadUInt32(); + uint language = reader.ReadUInt32(); + uint numGroups = reader.ReadUInt32(); + + var groups = new ConstantMapGroup[numGroups]; + for (var i = 0; i < numGroups; i++) + { + groups[i] = ConstantMapGroup.Load(reader); + } + + foreach (EncodingRecord encoding in encodings) + { + yield return new Format13SubTable(language, encoding.PlatformID, encoding.EncodingID, groups); + } + } + + /// + /// Represents a constant map group record that maps a contiguous range of + /// character codes to a single glyph index. + /// + internal readonly struct ConstantMapGroup + { + /// + /// The first character code in this group. + /// + public readonly uint StartCodePoint; + + /// + /// The last character code in this group (inclusive). + /// + public readonly uint EndCodePoint; + + /// + /// The glyph index every character code in this group maps to. + /// + public readonly uint GlyphId; + + /// + /// Initializes a new instance of the struct. + /// + /// The first character code in this group. + /// The last character code in this group. + /// The glyph index every character code in this group maps to. + public ConstantMapGroup(uint startCodePoint, uint endCodePoint, uint glyphId) + { + this.StartCodePoint = startCodePoint; + this.EndCodePoint = endCodePoint; + this.GlyphId = glyphId; + } + + /// + /// Loads a from the specified reader. + /// + /// The binary reader positioned at the constant map group data. + /// The parsed . + public static ConstantMapGroup Load(BigEndianBinaryReader reader) + { + uint startCodePoint = reader.ReadUInt32(); + uint endCodePoint = reader.ReadUInt32(); + uint glyphId = reader.ReadUInt32(); + return new ConstantMapGroup(startCodePoint, endCodePoint, glyphId); + } + } +} diff --git a/src/SixLabors.Fonts/Tables/General/CMapTable.cs b/src/SixLabors.Fonts/Tables/General/CMapTable.cs index 3218afa18..5646ac177 100644 --- a/src/SixLabors.Fonts/Tables/General/CMapTable.cs +++ b/src/SixLabors.Fonts/Tables/General/CMapTable.cs @@ -1,8 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers.Binary; using SixLabors.Fonts.Tables.General.CMap; using SixLabors.Fonts.Unicode; +using SixLabors.Fonts.Unicode.Resources; using SixLabors.Fonts.WellKnownIds; namespace SixLabors.Fonts.Tables.General; @@ -18,11 +20,48 @@ internal sealed class CMapTable : Table /// internal const string TableName = "cmap"; + /// + /// The Windows platform's symbol encoding identifier. A font declaring it + /// does not map Unicode: it maps its own character codes, and the codes it + /// uses live in 's private use page. + /// + private const ushort SymbolEncodingId = 0; + + /// + /// The first character of the private use page a symbol font maps into. + /// Such a font addresses its glyphs as U+F000 upwards while the text that + /// uses it holds the character codes those shadow, so a lookup that misses + /// is retried one page higher. + /// + private const int SymbolPageStart = 0xF000; + + /// + /// The highest character code a symbol font's page can shadow. The page is + /// a single byte wide, U+F000 to U+F0FF, so only a character that fits in a + /// byte has a counterpart there. + /// + private const int SymbolPageLastShadowed = 0xFF; + /// /// The format 14 subtables for Unicode variation sequences. /// private readonly Format14SubTable[] format14SubTables = Array.Empty(); + /// + /// The one subtable characters map through. + /// + private readonly CMapSubTable? characterMap; + + /// + /// Whether uses the symbol encoding. + /// + private readonly bool isSymbolic; + + /// + /// The legacy font-page marker controlling symbolic character remapping. + /// + private ushort symbolFontPage; + /// /// Cached codepoints available in the font. /// @@ -36,13 +75,29 @@ public CMapTable(IEnumerable tables) { this.Tables = tables.OrderBy(t => GetPreferredPlatformOrder(t.Platform)).ToArray(); this.format14SubTables = this.Tables.OfType().ToArray(); + this.characterMap = SelectCharacterMap(this.Tables, out bool symbolic); + this.isSymbolic = symbolic; } + /// + /// Gets a value indicating whether the font declares Unicode variation sequences. + /// When it does not, glyph lookup never needs the following codepoint, so callers + /// can skip decoding it entirely. + /// + public bool HasVariationSequences => this.format14SubTables.Length > 0; + /// /// Gets the subtables ordered by preferred platform. /// internal CMapSubTable[] Tables { get; } + /// + /// Sets the legacy font-page marker used by a symbolic character map. + /// + /// The font-page marker, or zero for ordinary symbol remapping. + public void SetSymbolFontPage(ushort fontPage) + => this.symbolFontPage = this.isSymbolic ? fontPage : (ushort)0; + /// /// Gets the preferred platform ordering for subtable selection. /// Windows is preferred, followed by Unicode, then Macintosh. @@ -58,6 +113,62 @@ private static int GetPreferredPlatformOrder(PlatformIDs platform) _ => int.MaxValue }; + /// + /// Chooses the one subtable that maps characters to glyphs, in the order the + /// specification's implementations agree on: the Windows symbol encoding + /// first, then the 32-bit Unicode encodings, then the 16-bit ones, and the + /// Macintosh encoding only when a font offers nothing else. A font that + /// carries several is not consulted for more than one of them - a character + /// the chosen subtable does not map is unmapped, not a reason to consult a + /// less preferred encoding, which would resolve characters through a table + /// the font does not intend for them. + /// + /// The subtables the font declares. + /// Whether the chosen subtable uses the symbol encoding. + /// The subtable to map characters through, or when the font declares none. + private static CMapSubTable? SelectCharacterMap(CMapSubTable[] tables, out bool symbolic) + { + symbolic = false; + foreach (CMapSubTable table in tables) + { + if (table.Platform == PlatformIDs.Windows && table.Encoding == SymbolEncodingId) + { + symbolic = true; + return table; + } + } + + // Widest coverage first: an encoding that reaches beyond the basic + // multilingual plane is preferred to one that cannot, and a Unicode + // encoding to the Macintosh one, whose codes are bytes in a legacy + // character set rather than characters. + ReadOnlySpan<(PlatformIDs Platform, ushort Encoding)> preference = + [ + (PlatformIDs.Windows, 10), // Windows, full Unicode + (PlatformIDs.Unicode, 6), // Unicode 13.0 and later, full + (PlatformIDs.Unicode, 4), // Unicode 2.0 and later, full + (PlatformIDs.Windows, 1), // Windows, basic multilingual plane + (PlatformIDs.Unicode, 3), // Unicode 2.0 and later, plane zero + (PlatformIDs.Unicode, 2), // Unicode, ISO/IEC 10646 + (PlatformIDs.Unicode, 1), // Unicode 1.1 + (PlatformIDs.Unicode, 0), // Unicode 1.0 + (PlatformIDs.Macintosh, 0), // Macintosh, single byte codes + ]; + + foreach ((PlatformIDs platform, ushort encoding) in preference) + { + foreach (CMapSubTable table in tables) + { + if (table.Platform == platform && table.Encoding == encoding && table is not Format14SubTable) + { + return table; + } + } + } + + return null; + } + /// /// Tries to get the glyph ID for the given code point, optionally considering the next code point /// for Unicode Variation Sequence (UVS) matching. @@ -103,29 +214,39 @@ public bool TryGetGlyphId(CodePoint codePoint, CodePoint? nextCodePoint, out ush /// if a glyph ID was found; otherwise, . private bool TryGetGlyphId(CodePoint codePoint, out ushort glyphId) { - bool foundFallback = false; + glyphId = 0; + if (this.characterMap is null) + { + return false; + } - foreach (CMapSubTable t in this.Tables) + if (this.characterMap.TryGetGlyphId(codePoint, out glyphId) && glyphId > 0) { - // Keep looking until we have an index that's not the fallback. - // Regardless of the encoding scheme, character codes that do - // not correspond to any glyph in the font should be mapped to glyph index 0. - // The glyph at this location must be a special glyph representing a missing character, commonly known as .notdef. - if (!t.TryGetGlyphId(codePoint, out glyphId)) + return true; + } + + if (this.isSymbolic && this.symbolFontPage == 0 && codePoint.Value <= SymbolPageLastShadowed) + { + // An ordinary symbol font shadows one byte of character codes in its + // U+F000 private-use page. + CodePoint shadowed = new(SymbolPageStart + codePoint.Value); + if (this.characterMap.TryGetGlyphId(shadowed, out glyphId) && glyphId > 0) { - continue; + return true; } + } - if (glyphId > 0) + if (this.isSymbolic && this.symbolFontPage != 0) + { + ushort mappedCodePoint = ArabicLegacyEncodingData.GetMappedCodePoint(this.symbolFontPage, codePoint.Value); + if (mappedCodePoint != 0 && this.characterMap.TryGetGlyphId(new CodePoint(mappedCodePoint), out glyphId) && glyphId > 0) { return true; } - - foundFallback = true; } glyphId = 0; - return foundFallback; + return false; } /// @@ -160,10 +281,42 @@ public ReadOnlyMemory GetAvailableCodePoints() } HashSet values = new(); - - foreach (int v in this.Tables.SelectMany(subtable => subtable.GetAvailableCodePoints())) + if (this.characterMap is not null) { - values.Add(v); + // Only the subtable characters map through: a codepoint another + // subtable lists is not one this font resolves. + foreach (int v in this.characterMap.GetAvailableCodePoints()) + { + values.Add(v); + } + + if (this.isSymbolic && this.symbolFontPage == 0) + { + // An ordinary symbol font's page is reachable through the byte + // values it shadows as well as through the page itself. + foreach (int v in this.characterMap.GetAvailableCodePoints()) + { + if (v >= SymbolPageStart && v <= SymbolPageStart + SymbolPageLastShadowed) + { + values.Add(v - SymbolPageStart); + } + } + } + else if (this.isSymbolic) + { + ReadOnlySpan mappings = ArabicLegacyEncodingData.GetMappings(this.symbolFontPage); + for (int offset = 0; offset < mappings.Length; offset += ArabicLegacyEncodingData.MappingEntrySize) + { + ushort mappedCodePoint = BinaryPrimitives.ReadUInt16LittleEndian( + mappings.Slice(offset + ArabicLegacyEncodingData.MappedCodePointOffset, sizeof(ushort))); + + if (this.characterMap.TryGetGlyphId(new CodePoint(mappedCodePoint), out ushort glyphId) && glyphId > 0) + { + ushort codePoint = BinaryPrimitives.ReadUInt16LittleEndian(mappings.Slice(offset, sizeof(ushort))); + values.Add(codePoint); + } + } + } } return this.codepoints = values.OrderBy(v => v).Select(v => new CodePoint(v)).ToArray(); @@ -215,6 +368,9 @@ public static CMapTable Load(BigEndianBinaryReader reader) case 12: tables.AddRange(Format12SubTable.Load(encoding, reader)); break; + case 13: + tables.AddRange(Format13SubTable.Load(encoding, reader)); + break; case 14: tables.AddRange(Format14SubTable.Load(encoding, reader, offset)); break; diff --git a/src/SixLabors.Fonts/Tables/General/Kern/Format2SubTable.cs b/src/SixLabors.Fonts/Tables/General/Kern/Format2SubTable.cs new file mode 100644 index 000000000..7f4b43205 --- /dev/null +++ b/src/SixLabors.Fonts/Tables/General/Kern/Format2SubTable.cs @@ -0,0 +1,117 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Tables.General.Kern; + +/// +/// Represents class-pair kerning in a format 2 'kern' subtable. +/// +/// +/// Format 2 is defined by the +/// OpenType 'kern' specification +/// and the +/// Apple TrueType Reference Manual. +/// This implementation follows Apple's subtable-relative class-offset interpretation. +/// +internal sealed class Format2SubTable : KerningSubTable +{ + /// + /// The first glyph covered by the left-hand class offsets. + /// + private readonly ushort firstLeftGlyph; + + /// + /// The first glyph covered by the right-hand class offsets. + /// + private readonly ushort firstRightGlyph; + + /// + /// Byte offsets selecting a row for each covered left-hand glyph. + /// + private readonly ushort[] leftClassOffsets; + + /// + /// Byte offsets selecting a column for each covered right-hand glyph. + /// + private readonly ushort[] rightClassOffsets; + + /// + /// The byte offset from the subtable start to the kerning value array. + /// + private readonly ushort arrayOffset; + + /// + /// The class-pair kerning values. + /// + private readonly short[] values; + + /// + /// Initializes a new instance of the class. + /// + /// The first glyph covered by the left-hand class offsets. + /// The left-hand class offsets. + /// The first glyph covered by the right-hand class offsets. + /// The right-hand class offsets. + /// The byte offset to the kerning value array. + /// The class-pair kerning values. + /// The coverage flags for this subtable. + private Format2SubTable(ushort firstLeftGlyph, ushort[] leftClassOffsets, ushort firstRightGlyph, ushort[] rightClassOffsets, ushort arrayOffset, short[] values, KerningCoverage coverage) + : base(coverage) + { + this.firstLeftGlyph = firstLeftGlyph; + this.leftClassOffsets = leftClassOffsets; + this.firstRightGlyph = firstRightGlyph; + this.rightClassOffsets = rightClassOffsets; + this.arrayOffset = arrayOffset; + this.values = values; + } + + /// + /// Loads class-pair kerning from the specified binary reader. + /// + /// The reader positioned after the shared subtable header. + /// The table-relative offset of the subtable. + /// The length of the subtable in bytes. + /// The coverage flags for this subtable. + /// The loaded class-pair kerning subtable. + public static Format2SubTable Load(BigEndianBinaryReader reader, long subtableOffset, uint subtableLength, in KerningCoverage coverage) + { + // Row width is redundant at lookup time because the left-hand class values already contain complete row offsets. + _ = reader.ReadUInt16(); + ushort leftClassTableOffset = reader.ReadOffset16(); + ushort rightClassTableOffset = reader.ReadOffset16(); + ushort arrayOffset = reader.ReadOffset16(); + + reader.Seek(subtableOffset + leftClassTableOffset, SeekOrigin.Begin); + ushort firstLeftGlyph = reader.ReadUInt16(); + ushort[] leftClassOffsets = reader.ReadUInt16Array(reader.ReadUInt16()); + + reader.Seek(subtableOffset + rightClassTableOffset, SeekOrigin.Begin); + ushort firstRightGlyph = reader.ReadUInt16(); + ushort[] rightClassOffsets = reader.ReadUInt16Array(reader.ReadUInt16()); + + reader.Seek(subtableOffset + arrayOffset, SeekOrigin.Begin); + short[] values = reader.ReadInt16Array(checked((int)((subtableLength - arrayOffset) / sizeof(short)))); + + return new Format2SubTable(firstLeftGlyph, leftClassOffsets, firstRightGlyph, rightClassOffsets, arrayOffset, values, coverage); + } + + /// + protected override bool TryGetOffset(ushort index1, ushort index2, out short offset) + { + int leftIndex = index1 - this.firstLeftGlyph; + int rightIndex = index2 - this.firstRightGlyph; + ushort leftOffset = (uint)leftIndex < (uint)this.leftClassOffsets.Length ? this.leftClassOffsets[leftIndex] : (ushort)0; + ushort rightOffset = (uint)rightIndex < (uint)this.rightClassOffsets.Length ? this.rightClassOffsets[rightIndex] : (ushort)0; + int valueIndex = (leftOffset + rightOffset - this.arrayOffset) / sizeof(short); + + if ((uint)valueIndex < (uint)this.values.Length) + { + offset = this.values[valueIndex]; + return true; + } + + offset = 0; + return false; + } +} diff --git a/src/SixLabors.Fonts/Tables/General/Kern/KerningCoverage.cs b/src/SixLabors.Fonts/Tables/General/Kern/KerningCoverage.cs index 045c4efba..d85018cd3 100644 --- a/src/SixLabors.Fonts/Tables/General/Kern/KerningCoverage.cs +++ b/src/SixLabors.Fonts/Tables/General/Kern/KerningCoverage.cs @@ -17,13 +17,15 @@ internal readonly struct KerningCoverage /// Whether the table contains minimum values instead of kerning values. /// Whether kerning is perpendicular to the flow of text. /// Whether the kerning value should replace the currently accumulated value. + /// Whether the subtable contains variation kerning. /// The subtable format number. - private KerningCoverage(bool horizontal, bool hasMinimum, bool crossStream, bool overrideAccumulator, byte format) + private KerningCoverage(bool horizontal, bool hasMinimum, bool crossStream, bool overrideAccumulator, bool variation, byte format) { this.Horizontal = horizontal; this.HasMinimum = hasMinimum; this.CrossStream = crossStream; this.OverrideAccumulator = overrideAccumulator; + this.Variation = variation; this.Format = format; } @@ -50,6 +52,11 @@ private KerningCoverage(bool horizontal, bool hasMinimum, bool crossStream, bool /// public bool OverrideAccumulator { get; } + /// + /// Gets a value indicating whether the subtable contains variation kerning. + /// + public bool Variation { get; } + /// /// Gets the format of the subtable. Only formats 0 and 2 have been defined. /// @@ -58,6 +65,10 @@ private KerningCoverage(bool horizontal, bool hasMinimum, bool crossStream, bool /// /// Reads a from the specified binary reader. /// + /// + /// The coverage field is defined by the + /// OpenType 'kern' specification. + /// /// The binary reader positioned at the coverage field. /// The parsed . public static KerningCoverage Read(BigEndianBinaryReader reader) @@ -79,7 +90,30 @@ public static KerningCoverage Read(BigEndianBinaryReader reader) bool hasMinimum = ((coverage >> 1) & 0x1) == 1; bool crossStream = ((coverage >> 2) & 0x1) == 1; bool overrideAccumulator = ((coverage >> 3) & 0x1) == 1; - byte format = (byte)((coverage >> 7) & 0xff); - return new KerningCoverage(horizontal, hasMinimum, crossStream, overrideAccumulator, format); + byte format = (byte)(coverage >> 8); + return new KerningCoverage(horizontal, hasMinimum, crossStream, overrideAccumulator, false, format); + } + + /// + /// Reads Apple kerning coverage and format bytes from the specified binary reader. + /// + /// + /// The coverage field is defined by the + /// Apple TrueType Reference Manual. + /// + /// The binary reader positioned at the coverage byte. + /// The parsed . + public static KerningCoverage ReadApple(BigEndianBinaryReader reader) + { + const byte VerticalMask = 0x80; + const byte CrossStreamMask = 0x40; + const byte VariationMask = 0x20; + + byte coverage = reader.ReadByte(); + byte format = reader.ReadByte(); + bool horizontal = (coverage & VerticalMask) == 0; + bool crossStream = (coverage & CrossStreamMask) != 0; + bool variation = (coverage & VariationMask) != 0; + return new KerningCoverage(horizontal, false, crossStream, false, variation, format); } } diff --git a/src/SixLabors.Fonts/Tables/General/Kern/KerningSubTable.cs b/src/SixLabors.Fonts/Tables/General/Kern/KerningSubTable.cs index 829cc1244..192f9b0bc 100644 --- a/src/SixLabors.Fonts/Tables/General/Kern/KerningSubTable.cs +++ b/src/SixLabors.Fonts/Tables/General/Kern/KerningSubTable.cs @@ -28,10 +28,19 @@ public KerningSubTable(KerningCoverage coverage) /// Loads a from the specified binary reader. /// Returns if the subtable format is not supported. /// + /// + /// The version 0 subtable header is defined by the + /// OpenType 'kern' specification. + /// The version 1.0 subtable header is defined by the + /// Apple TrueType Reference Manual. + /// /// The binary reader positioned at the start of the subtable header. + /// Whether the subtable uses the Apple 1.0 header. /// The loaded , or for unsupported formats. - public static KerningSubTable? Load(BigEndianBinaryReader reader) + public static KerningSubTable? Load(BigEndianBinaryReader reader, bool useAppleHeader) { + long subtableOffset = reader.BaseStream.Position - reader.StartOfStream; + // Kerning subtables will share the same header format. // This header is used to identify the format of the subtable and the kind of information it contains: // +--------+----------+----------------------------------------------------------+ @@ -43,18 +52,31 @@ public KerningSubTable(KerningCoverage coverage) // +--------+----------+----------------------------------------------------------+ // | uint16 | coverage | What type of information is contained in this table. | // +--------+----------+----------------------------------------------------------+ - ushort subVersion = reader.ReadUInt16(); - ushort length = reader.ReadUInt16(); - KerningCoverage coverage = KerningCoverage.Read(reader); - if (coverage.Format == 0) + uint length; + KerningCoverage coverage; + if (useAppleHeader) { - return Format0SubTable.Load(reader, coverage); + length = reader.ReadUInt32(); + coverage = KerningCoverage.ReadApple(reader); + _ = reader.ReadUInt16(); } else { - // we don't support versions other than 'Format 0' same as Windows - return null; + _ = reader.ReadUInt16(); + length = reader.ReadUInt16(); + coverage = KerningCoverage.Read(reader); } + + KerningSubTable? subtable = coverage.Variation ? null : coverage.Format switch + { + 0 => Format0SubTable.Load(reader, coverage), + 2 => Format2SubTable.Load(reader, subtableOffset, length, coverage), + _ => null + }; + + // Each header length covers its complete subtable, so unsupported formats cannot desynchronize the next header. + reader.Seek(subtableOffset + length, SeekOrigin.Begin); + return subtable; } /// diff --git a/src/SixLabors.Fonts/Tables/General/Kern/KerningTable.cs b/src/SixLabors.Fonts/Tables/General/Kern/KerningTable.cs index ba2211ba5..0b1ddc0c3 100644 --- a/src/SixLabors.Fonts/Tables/General/Kern/KerningTable.cs +++ b/src/SixLabors.Fonts/Tables/General/Kern/KerningTable.cs @@ -15,7 +15,7 @@ internal sealed class KerningTable : Table /// /// The table tag name identifying the 'kern' table. /// - internal const string TableName = "kern"; + public const string TableName = "kern"; /// /// The array of kerning subtables contained in this table. @@ -58,6 +58,12 @@ public static KerningTable Load(FontReader fontReader) /// /// Loads the from the specified binary reader. /// + /// + /// The version 0 header is defined by the + /// OpenType 'kern' specification. + /// The version 1.0 header is defined by the + /// Apple TrueType Reference Manual. + /// /// The binary reader positioned at the start of the kern table data. /// The loaded . public static KerningTable Load(BigEndianBinaryReader reader) @@ -69,13 +75,29 @@ public static KerningTable Load(BigEndianBinaryReader reader) // +--------+---------+-------------------------------------------+ // | uint16 | nTables | Number of subtables in the kerning table. | // +--------+---------+-------------------------------------------+ - ushort version = reader.ReadUInt16(); - ushort subTableCount = reader.ReadUInt16(); + ushort majorVersion = reader.ReadUInt16(); + int subTableCount; + bool useAppleHeader; + if (majorVersion == 0) + { + subTableCount = reader.ReadUInt16(); + useAppleHeader = false; + } + else if (majorVersion == 1) + { + _ = reader.ReadUInt16(); + subTableCount = checked((int)reader.ReadUInt32()); + useAppleHeader = true; + } + else + { + return new KerningTable([]); + } List tables = new(subTableCount); for (int i = 0; i < subTableCount; i++) { - KerningSubTable? t = KerningSubTable.Load(reader); // returns null for unknown/supported table format + KerningSubTable? t = KerningSubTable.Load(reader, useAppleHeader); if (t != null) { tables.Add(t); @@ -89,30 +111,45 @@ public static KerningTable Load(BigEndianBinaryReader reader) /// Updates glyph positions by applying kerning adjustments for the specified glyph pair. /// /// The font metrics used for position calculations. - /// The glyph positioning collection to update. - /// The index of the left glyph in the collection. - /// The index of the right glyph in the collection. - public void UpdatePositions(FontMetrics fontMetrics, GlyphPositioningCollection collection, int left, int right) + /// The glyph positioning buffer to update. + /// The index of the left glyph in the buffer. + /// The index of the right glyph in the buffer. + public void UpdatePositions(FontMetrics fontMetrics, ShapingBuffer buffer, int left, int right) { - if (this.Count == 0 || collection.Count == 0) + if (this.Count == 0 || buffer.Count == 0) { return; } - GlyphShapingData current = collection[left]; - if (current.IsKerned) + ref GlyphShapingPosition currentPosition = ref buffer.PositionAt(left); + if (currentPosition.IsKerned + || buffer.MetricsAt(left).Metrics.FontMetrics != fontMetrics + || buffer.MetricsAt(right).Metrics.FontMetrics != fontMetrics) { - // Already kerned via previous processing. + // A shared shaping buffer can contain glyph ids from several fonts. A + // kerning table's glyph ids are meaningful only within its own font. return; } - ushort currentId = current.GlyphId; - ushort nextId = collection[right].GlyphId; + ushort currentId = buffer[left].GlyphId; + ushort nextId = buffer[right].GlyphId; if (this.TryGetKerningOffset(currentId, nextId, out Vector2 result)) { - collection.Advance(fontMetrics, left, currentId, (short)result.X, (short)result.Y); - current.IsKerned = true; + int horizontalKern = (int)result.X; + int verticalKern = (int)result.Y; + short firstHorizontal = (short)(horizontalKern >> 1); + short secondHorizontal = (short)(horizontalKern - firstHorizontal); + short firstVertical = (short)(verticalKern >> 1); + short secondVertical = (short)(verticalKern - firstVertical); + + buffer.Advance(fontMetrics, left, currentId, firstHorizontal, firstVertical); + buffer.Advance(fontMetrics, right, nextId, secondHorizontal, secondVertical); + + ref GlyphShapingPosition nextPosition = ref buffer.PositionAt(right); + nextPosition.Bounds.X += secondHorizontal; + nextPosition.Bounds.Y += secondVertical; + currentPosition.IsKerned = true; } } diff --git a/src/SixLabors.Fonts/Tables/General/OS2Table.cs b/src/SixLabors.Fonts/Tables/General/OS2Table.cs index d785c7c9c..db16b3d4b 100644 --- a/src/SixLabors.Fonts/Tables/General/OS2Table.cs +++ b/src/SixLabors.Fonts/Tables/General/OS2Table.cs @@ -9,6 +9,11 @@ namespace SixLabors.Fonts.Tables.General; /// internal sealed class OS2Table : Table { + /// + /// The mask selecting the legacy font-page marker from version 0 style flags. + /// + private const ushort FontPageMask = 0xFF00; + /// /// The table name identifier. /// @@ -368,6 +373,11 @@ internal enum FontStyleSelection : ushort /// public FontStyleSelection FontStyle { get; } + /// + /// Gets the legacy font-page marker encoded by a version 0 table, or zero for later versions. + /// + public ushort FontPage { get; private set; } + /// /// Gets the visual weight class of the font. /// @@ -591,6 +601,7 @@ public static OS2Table Load(BigEndianBinaryReader reader) if (version == 0) { + version0Table.FontPage = (ushort)((ushort)fontStyle & FontPageMask); return version0Table; } diff --git a/src/SixLabors.Fonts/Tables/TrueType/Glyphs/EmptyGlyphLoader.cs b/src/SixLabors.Fonts/Tables/TrueType/Glyphs/EmptyGlyphLoader.cs index 1ed92af1a..088aa9eba 100644 --- a/src/SixLabors.Fonts/Tables/TrueType/Glyphs/EmptyGlyphLoader.cs +++ b/src/SixLabors.Fonts/Tables/TrueType/Glyphs/EmptyGlyphLoader.cs @@ -9,9 +9,7 @@ namespace SixLabors.Fonts.Tables.TrueType.Glyphs; /// internal class EmptyGlyphLoader : GlyphLoader { - private bool loop; private readonly Bounds fallbackEmptyBounds; - private GlyphVector? glyph; /// /// Initializes a new instance of the class. @@ -23,14 +21,9 @@ public EmptyGlyphLoader(Bounds fallbackEmptyBounds) /// public override GlyphVector CreateGlyph(GlyphTable table) { - if (this.loop) - { - this.glyph ??= GlyphVector.Empty(this.fallbackEmptyBounds); - return this.glyph.Value; - } - - this.loop = true; - this.glyph ??= GlyphVector.Empty(table.GetGlyph(0).Bounds); - return this.glyph.Value; + // A zero-length glyf entry has no ink. Reusing glyph zero's bounds would + // incorrectly turn every empty glyph, including spaces, into .notdef. + // Measurement derives fallback advance bounds from this empty vector later. + return GlyphVector.Empty(this.fallbackEmptyBounds); } } diff --git a/src/SixLabors.Fonts/Tables/TrueType/TrueTypeGlyphMetrics.cs b/src/SixLabors.Fonts/Tables/TrueType/TrueTypeGlyphMetrics.cs index 98ceef5be..ca0b2adf4 100644 --- a/src/SixLabors.Fonts/Tables/TrueType/TrueTypeGlyphMetrics.cs +++ b/src/SixLabors.Fonts/Tables/TrueType/TrueTypeGlyphMetrics.cs @@ -14,9 +14,16 @@ namespace SixLabors.Fonts.Tables.TrueType; /// public partial class TrueTypeGlyphMetrics : FontGlyphMetrics { - private static readonly Vector2 YInverter = new(1, -1); private readonly GlyphVector vector; - private readonly ConcurrentDictionary scaledVectorCache = new(); + + /// + /// Scaled, hinted, upright outline copies keyed by ppem. Offset translation, synthetic + /// oblique, and layout rotation are applied per point at emit time so one cached copy + /// serves every run, layout mode, and positioned offset. Allocated on first render: + /// shaping and measurement clone metrics without ever rendering them, so an eager cache + /// would cost a dictionary per glyph per shaping pass. + /// + private ConcurrentDictionary? scaledVectorCache; /// /// Initializes a new instance of the class. @@ -61,70 +68,6 @@ internal TrueTypeGlyphMetrics( glyphType) => this.vector = vector; - /// - /// Initializes a new instance of the class - /// with explicit offset, scale, and text run for rendering clones. - /// - /// The font metrics this glyph belongs to. - /// The glyph identifier. - /// The Unicode code point for this glyph. - /// The glyph outline vector. - /// The advance width in font units. - /// The advance height in font units. - /// The left side bearing in font units. - /// The top side bearing in font units. - /// The units per em for the font. - /// The rendering offset. - /// The scale factor. - /// The text run this glyph is associated with. - /// The glyph type. - internal TrueTypeGlyphMetrics( - StreamFontMetrics font, - ushort glyphId, - CodePoint codePoint, - GlyphVector vector, - ushort advanceWidth, - ushort advanceHeight, - short leftSideBearing, - short topSideBearing, - ushort unitsPerEM, - Vector2 offset, - Vector2 scaleFactor, - TextRun textRun, - GlyphType glyphType) - : base( - font, - glyphId, - codePoint, - vector.Bounds, - advanceWidth, - advanceHeight, - leftSideBearing, - topSideBearing, - unitsPerEM, - offset, - scaleFactor, - textRun, - glyphType) - => this.vector = vector; - - /// - internal override FontGlyphMetrics CloneForRendering(TextRun textRun) - => new TrueTypeGlyphMetrics( - this.FontMetrics, - this.GlyphId, - this.CodePoint, - GlyphVector.DeepClone(this.vector), - this.AdvanceWidth, - this.AdvanceHeight, - this.LeftSideBearing, - this.TopSideBearing, - this.UnitsPerEm, - this.Offset, - this.ScaleFactor, - textRun, - this.GlyphType); - /// /// Gets the outline for the current glyph. /// @@ -136,34 +79,41 @@ internal override void RenderOutlineTo( IGlyphRenderer renderer, Vector2 glyphOrigin, GlyphLayoutMode mode, + TextRun? textRun, + Vector2 positionOffset, + Vector2 positionedAdvance, float scaledPPEM, HintingMode hintingMode) { - Matrix3x2 transform = this.GetOutlineTransform(mode); - GlyphVector scaledVector = this.scaledVectorCache.GetOrAdd(scaledPPEM, _ => + ConcurrentDictionary cache = + LazyInitializer.EnsureInitialized(ref this.scaledVectorCache, static () => new()); + Vector2 scale = new Vector2(scaledPPEM) / this.ScaleFactor; + GlyphVector scaledVector = cache.GetOrAdd(scaledPPEM, _ => { // Create a scaled deep copy of the vector so that we do not alter - // the globally cached instance. + // the globally cached instance. The hinter always receives the upright, + // untranslated outline. GlyphVector clone = GlyphVector.DeepClone(this.vector); - Vector2 scale = new Vector2(scaledPPEM) / this.ScaleFactor; - - Matrix3x2 matrix = Matrix3x2.CreateScale(scale); - matrix.Translation = this.Offset * scale; - GlyphVector.TransformInPlace(ref clone, matrix); + GlyphVector.TransformInPlace(ref clone, Matrix3x2.CreateScale(scale)); float pixelSize = scaledPPEM / 72F; this.FontMetrics.ApplyTrueTypeHinting(this.GetHintingMode(hintingMode), this, ref clone, scale, pixelSize); - - // The shared outline transform applies synthetic oblique before layout rotation. - // Both happen after hinting so the hinter always receives the upright outline. - GlyphVector.TransformInPlace(ref clone, transform); return clone; }); IList controlPoints = scaledVector.ControlPoints; IReadOnlyList endPoints = scaledVector.EndPoints; - float boldStrength = this.GetSyntheticBoldStrength(scaledPPEM); + // Offset translation, synthetic oblique, and layout rotation are applied per point at + // emit time so the cached outline stays shareable across runs, modes, and positioned + // offsets. Placement lands after hinting, matching FreeType's treatment of GPOS + // offsets, followed by the Y-flip into device space and the origin translation. + Matrix3x2 emit = Matrix3x2.CreateTranslation((this.Offset + positionOffset) * scale); + emit *= this.GetOutlineTransform(mode, textRun); + emit *= Matrix3x2.CreateScale(1F, -1F); + emit.Translation += glyphOrigin; + + float boldStrength = this.GetSyntheticBoldStrength(scaledPPEM, textRun); EmboldeningGlyphRenderer? emboldening = null; IGlyphRenderer target = renderer; if (boldStrength > 0F) @@ -182,8 +132,8 @@ internal override void RenderOutlineTo( endOfContour = endPoints[i]; Vector2 prev; - Vector2 curr = (YInverter * controlPoints[endOfContour].Point) + glyphOrigin; - Vector2 next = (YInverter * controlPoints[startOfContour].Point) + glyphOrigin; + Vector2 curr = Vector2.Transform(controlPoints[endOfContour].Point, emit); + Vector2 next = Vector2.Transform(controlPoints[startOfContour].Point, emit); if (controlPoints[endOfContour].OnCurve) { @@ -211,7 +161,7 @@ internal override void RenderOutlineTo( int currentIndex = startOfContour + p; int nextIndex = startOfContour + ((p + 1) % length); int prevIndex = startOfContour + ((length + p - 1) % length); - next = (YInverter * controlPoints[nextIndex].Point) + glyphOrigin; + next = Vector2.Transform(controlPoints[nextIndex].Point, emit); if (controlPoints[currentIndex].OnCurve) { diff --git a/src/SixLabors.Fonts/TextBlock.Visitors.cs b/src/SixLabors.Fonts/TextBlock.Visitors.cs index eafdf603e..2dfd6232a 100644 --- a/src/SixLabors.Fonts/TextBlock.Visitors.cs +++ b/src/SixLabors.Fonts/TextBlock.Visitors.cs @@ -752,6 +752,7 @@ public void Visit(in GlyphLayout glyph) this.glyphMetrics[this.count] = new GlyphMetrics( glyph.Glyph.GlyphMetrics.CodePoint, + glyph.Glyph.GlyphMetrics.GlyphId, advance, bounds, renderableBounds, diff --git a/src/SixLabors.Fonts/TextBlock.cs b/src/SixLabors.Fonts/TextBlock.cs index 15a48f3b0..c61c4ee0d 100644 --- a/src/SixLabors.Fonts/TextBlock.cs +++ b/src/SixLabors.Fonts/TextBlock.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Numerics; @@ -58,8 +58,7 @@ public TextBlock(ReadOnlySpan text, TextOptions options) return; } - ShapedText shaped = TextLayout.ShapeText(text, options); - this.LogicalLine = TextLayout.ComposeLogicalLine(shaped, text, options); + this.LogicalLine = TextLayout.ComposeLogicalLine(text, options); } /// diff --git a/src/SixLabors.Fonts/TextLayout.LineBreaking.cs b/src/SixLabors.Fonts/TextLayout.LineBreaking.cs index 75b125d0f..712b25c15 100644 --- a/src/SixLabors.Fonts/TextLayout.LineBreaking.cs +++ b/src/SixLabors.Fonts/TextLayout.LineBreaking.cs @@ -15,6 +15,23 @@ internal static partial class TextLayout private const int StandardHyphen = 0x2010; private const int StandardEllipsis = 0x2026; + /// + /// Shapes the text and composes the logical before + /// width-dependent line breaking. Shaping runs on pooled pipeline state scoped + /// entirely to this call; composition copies everything the result retains. + /// + /// The source text. + /// The text shaping and layout options. + /// The logical text line and line break opportunities before line breaking. + public static LogicalTextLine ComposeLogicalLine(ReadOnlySpan text, TextOptions options) + { + // Composition retains run references in its layout data, so the runs are + // built per call rather than reused from pooled state. + IReadOnlyList runs = TextShaper.BuildTextRuns(text, options); + using TextShaper.ShapedTextScope scope = TextShaper.ShapeText(text, options, runs); + return ComposeLogicalLine(scope.Shaped, text, options); + } + /// /// Composes the logical from shaped glyph data before width-dependent line breaking. /// @@ -22,7 +39,7 @@ internal static partial class TextLayout /// The original source text. /// The text shaping and layout options. /// The logical text line and line break opportunities before line breaking. - public static LogicalTextLine ComposeLogicalLine( + private static LogicalTextLine ComposeLogicalLine( in ShapedText shapedText, ReadOnlySpan text, TextOptions options) @@ -30,9 +47,11 @@ public static LogicalTextLine ComposeLogicalLine( bool isHorizontalLayout = shapedText.LayoutMode.IsHorizontal(); bool isVerticalLayout = shapedText.LayoutMode.IsVertical(); bool isVerticalMixedLayout = shapedText.LayoutMode.IsVerticalMixed(); + bool hasTracking = options.Tracking != 0; int graphemeIndex = 0; int codePointIndex = 0; + int glyphSearchBidiRunIndex = -1; int glyphSearchIndex = 0; TextLine textLine = new(); int stringIndex = 0; @@ -40,9 +59,44 @@ public static LogicalTextLine ComposeLogicalLine( List hyphenationMarkers = []; CodePoint? hyphenationMarkerCodePoint = GetHyphenationMarkerCodePoint(options); - // No glyph should contain more than 64 metrics. - // We do a sanity check below just in case. - Span decomposedAdvancesBuffer = stackalloc float[64]; + // Browsers hand layout each directional run's glyphs exactly as the shaper + // finalized them, already in visual order, and consume that storage + // directly. Materialize the projection once into a single array in that + // same per-run visual order; every line entry below is a contiguous slice + // of it, so no later stage can rearrange the glyphs inside an entry. + // + // This is composition's one deliberate heap allocation. The composed line + // is retained and re-laid-out beyond the pooled shaping scope, so the + // shaped stream must be copied into storage the line owns. It replaces the + // former per-source-position collections, and renting it instead would + // require a disposal contract the retained result does not have. + PositionedGlyphMetrics[] glyphStorage = new PositionedGlyphMetrics[shapedText.GlyphCount]; + for (int i = 0; i < shapedText.GlyphCount; i++) + { + ref readonly ShapedGlyphInfo info = ref shapedText.Infos[i]; + if (info.IsPlaceholder) + { + // Placeholder records materialize generated metrics when their + // entries are added below; their storage slots are never sliced. + continue; + } + + ShapedTextRun run = shapedText.Runs[info.RunIndex]; + ref readonly ShapedGlyphPosition position = ref shapedText.Positions[i]; + + // The shaped result carries numbers only; composition resolves the + // metrics instance from the owning font's cache by the same arguments + // shaping used, so the same instance is returned. + FontGlyphMetrics glyphMetrics = run.Font.FontMetrics.GetGlyphMetrics( + info.CodePoint, + info.GlyphId, + run.TextRun.TextAttributes, + run.TextRun.TextDecorations, + shapedText.LayoutMode, + options.ColorFontSupport); + + glyphStorage[i] = new(glyphMetrics, position.AdvanceWidth, position.AdvanceHeight, position.Offset, run.TextRun); + } // Word-boundary segments are prepared with the logical line, while grapheme // and codepoint enumeration still own shaping data creation. @@ -61,14 +115,17 @@ public static LogicalTextLine ComposeLogicalLine( SpanCodePointEnumerator codePointEnumerator = new(grapheme); while (codePointEnumerator.MoveNext()) { - if (!shapedText.Positionings.TryGetGlyphMetricsAtOffset( + if (!shapedText.TryGetGlyphsAtOffset( codePointIndex, + ref glyphSearchBidiRunIndex, ref glyphSearchIndex, + out int glyphStart, + out int glyphCount, out float pointSize, out bool isSubstituted, out bool isVerticalSubstitution, out bool isDecomposed, - out IReadOnlyList? glyphData)) + out int nextShapedCodePointIndex)) { // Codepoint was skipped during original enumeration. codePointIndex++; @@ -76,14 +133,29 @@ public static LogicalTextLine ComposeLogicalLine( continue; } - List metrics = []; - for (int i = 0; i < glyphData.Count; i++) + BidiRun bidiRun = shapedText.BidiRuns[shapedText.BidiMap[codePointIndex]]; + + // Locate the entry's contiguous slice of the retained storage + // without copying a single glyph. Placeholder records sit at the + // edges of a source position's range and become standalone + // entries, exactly as browsers keep atomic inlines as their own + // line items. + int sliceStart = -1; + int sliceEnd = -1; + bool isCursiveScript = false; + float verticalGroupAdvance = 0; + for (int i = 0; i < glyphCount; i++) { - GlyphPositioningCollection.GlyphPositioningData data = glyphData[i]; - if (data.Data.IsPlaceholder) + int shapedGlyphIndex = glyphStart + i; + ref readonly ShapedGlyphInfo info = ref shapedText.Infos[shapedGlyphIndex]; + isCursiveScript |= info.IsCursiveScript; + if (info.IsPlaceholder) { + ShapedTextRun run = shapedText.Runs[info.RunIndex]; textLine.AddPlaceholder( - data, + PlaceholderGlyphMetrics.Create(run.Font, run.TextRun, options.Dpi), + in run, + info.CodePointIndex, graphemeIndex, stringIndex, isHorizontalLayout, @@ -93,10 +165,45 @@ public static LogicalTextLine ComposeLogicalLine( continue; } - metrics.Add(data.Metrics); + if (sliceStart < 0) + { + sliceStart = shapedGlyphIndex; + } + + sliceEnd = shapedGlyphIndex; + + if (!isHorizontalLayout) + { + // Accumulate while this loop already visits every + // positioned glyph, so the upright path needs no second + // pass over the slice. + ref readonly PositionedGlyphMetrics positioned = ref glyphStorage[shapedGlyphIndex]; + FontGlyphMetrics positionedMetrics = positioned.Metrics; + float scaleAY = shapedText.Runs[info.RunIndex].PointSize / positionedMetrics.ScaleFactor.Y; + float positionedAdvance = positioned.AdvanceHeight * scaleAY; + VerticalMetrics verticalMetrics = positionedMetrics.FontMetrics.VerticalMetrics; + if (verticalMetrics.Synthesized && positioned.AdvanceHeight != 0) + { + // Browsers provide the device-rounded fallback height to + // shaping as the nominal vertical advance. Replace only that + // nominal component so positioning deltas survive, while the + // zero advance shaping assigned to marks remains untouched. + float nominalAdvance = positionedMetrics.AdvanceHeight * scaleAY; + + // scaleAY converts design units to the DPI-normalized + // layout space consumed by TextLayout. Round in target + // device pixels, then return to that layout space. + float deviceScale = options.Dpi; + float browserAdvance = (MathF.Floor((verticalMetrics.Ascender * scaleAY * deviceScale) + .5F) + + MathF.Floor((-verticalMetrics.Descender * scaleAY * deviceScale) + .5F)) / deviceScale; + positionedAdvance += browserAdvance - nominalAdvance; + } + + verticalGroupAdvance += positionedAdvance; + } } - if (metrics.Count == 0) + if (sliceStart < 0) { // This source codepoint was skipped during shaping; any placeholder // sharing the same source offset has already been added above. @@ -105,7 +212,10 @@ public static LogicalTextLine ComposeLogicalLine( continue; } - FontGlyphMetrics glyph = metrics[0]; + ReadOnlyMemory metrics = glyphStorage.AsMemory(sliceStart, sliceEnd - sliceStart + 1); + ReadOnlySpan metricsSpan = metrics.Span; + Font entryFont = shapedText.Runs[shapedText.Infos[sliceStart].RunIndex].Font; + FontGlyphMetrics glyph = metricsSpan[0].Metrics; // Retrieve the current codepoint from the enumerator. // If the glyph represents a substituted codepoint and the substitution is a single codepoint substitution, @@ -115,7 +225,7 @@ public static LogicalTextLine ComposeLogicalLine( // // Note: Not all glyphs in a font will have a codepoint associated with them. e.g. most compositions, ligatures, etc. CodePoint codePoint = codePointEnumerator.Current; - if (isSubstituted && metrics.Count == 1) + if (isSubstituted && metricsSpan.Length == 1) { codePoint = glyph.CodePoint; } @@ -144,30 +254,22 @@ VerticalOrientationType.Rotate or } // Calculate the advance for the current codepoint. - - // This should never happen, but we need to ensure that the buffer is large enough - // if, for some crazy reason, a glyph does contain more than 64 metrics. - Span decomposedAdvances = metrics.Count > decomposedAdvancesBuffer.Length - ? new float[metrics.Count] - : decomposedAdvancesBuffer[..(isDecomposed ? metrics.Count : 1)]; - float glyphAdvance; if (isHorizontalLayout || shouldRotate) { - glyphAdvance = glyph.AdvanceWidth; + glyphAdvance = metricsSpan[0].AdvanceWidth; } else { - glyphAdvance = glyph.AdvanceHeight; + glyphAdvance = metricsSpan[0].AdvanceHeight; } - decomposedAdvances[0] = glyphAdvance; + bool usePositionedVerticalAdvances = false; bool isSoftHyphen = codePoint.Value == SoftHyphen; if (isSoftHyphen) { glyphAdvance = 0; - decomposedAdvances[0] = 0; } else if (CodePoint.IsTabulation(codePoint)) { @@ -186,62 +288,50 @@ VerticalOrientationType.Rotate or shapedText.LayoutMode, options.ColorFontSupport); + // The tab advance lives only in the positioned snapshot; + // the metrics instance is shared and must not be mutated. + // Writing through the storage slot keeps the entry's + // slice and the retained stream in agreement. if (isHorizontalLayout || shouldRotate) { glyphAdvance = spaceMetrics.AdvanceWidth * options.TabWidth; - glyph.SetAdvanceWidth((ushort)glyphAdvance); + glyphStorage[sliceStart] = new(glyph, (ushort)glyphAdvance, metricsSpan[0].AdvanceHeight, metricsSpan[0].Offset, metricsSpan[0].TextRun); } else { glyphAdvance = spaceMetrics.AdvanceHeight * options.TabWidth; - glyph.SetAdvanceHeight((ushort)glyphAdvance); + glyphStorage[sliceStart] = new(glyph, metricsSpan[0].AdvanceWidth, (ushort)glyphAdvance, metricsSpan[0].Offset, metricsSpan[0].TextRun); } } } } - else if (metrics.Count == 1 && (CodePoint.IsZeroWidthJoiner(codePoint) || CodePoint.IsZeroWidthNonJoiner(codePoint))) + else if (metricsSpan.Length == 1 && (CodePoint.IsZeroWidthJoiner(codePoint) || CodePoint.IsZeroWidthNonJoiner(codePoint))) { // The zero-width joiner characters should be ignored when determining word or // line break boundaries so are safe to skip here. Any existing instances are the result of font error // unless multiple metrics are associated with code point. In this case they are most likely the result // of a substitution and shouldn't be ignored. glyphAdvance = 0; - decomposedAdvances[0] = 0; } else if (!CodePoint.IsNewLine(codePoint)) { - // Standard text. - // If decomposed we need to add the advance; otherwise, use the largest advance for the metrics. + // Standard text. Browser layout retains every shaped glyph + // advance; the entry's advance is the sum over its slice while + // the per-glyph values remain in the retained storage for the + // positioned walk. + usePositionedVerticalAdvances = !isHorizontalLayout && !shouldRotate; if (isHorizontalLayout || shouldRotate) { - for (int i = 1; i < metrics.Count; i++) + for (int i = 1; i < metricsSpan.Length; i++) { - float a = metrics[i].AdvanceWidth; - if (isDecomposed) - { - glyphAdvance += a; - decomposedAdvances[i] = a; - } - else if (a > glyphAdvance) - { - glyphAdvance = a; - } + glyphAdvance += metricsSpan[i].AdvanceWidth; } } else { - for (int i = 1; i < metrics.Count; i++) + for (int i = 1; i < metricsSpan.Length; i++) { - float a = metrics[i].AdvanceHeight; - if (isDecomposed) - { - glyphAdvance += a; - decomposedAdvances[i] = a; - } - else if (a > glyphAdvance) - { - glyphAdvance = a; - } + glyphAdvance += metricsSpan[i].AdvanceHeight; } } } @@ -249,151 +339,146 @@ VerticalOrientationType.Rotate or // Now scale the advance. We use inches for comparison. if (isHorizontalLayout || shouldRotate) { - float scaleAX = pointSize / glyph.ScaleFactor.X; - glyphAdvance *= scaleAX; - for (int i = 0; i < decomposedAdvances.Length; i++) - { - decomposedAdvances[i] *= scaleAX; - } + glyphAdvance *= pointSize / glyph.ScaleFactor.X; + } + else if (usePositionedVerticalAdvances) + { + // Ordinary upright text follows the positioned glyph stream. + // Special characters above deliberately keep their established + // zero or caller-defined advance instead. + glyphAdvance = verticalGroupAdvance; } else { - float scaleAY = pointSize / glyph.ScaleFactor.Y; - glyphAdvance *= scaleAY; - for (int i = 0; i < decomposedAdvances.Length; i++) - { - decomposedAdvances[i] *= scaleAY; - } + // Tabs, soft hyphens, joiners, and hard breaks have already + // selected their logical advance. Scale that value normally + // rather than replacing it with a font-wide vertical fallback. + glyphAdvance *= pointSize / glyph.ScaleFactor.Y; } int graphemeCodePointMax = CodePoint.GetCodePointCount(grapheme) - 1; - - // For non-decomposed glyphs the length is always 1. - int glyphDataIndex = 0; - - for (int i = 0; i < decomposedAdvances.Length; i++) + int graphemeCodePointEnd = codePointIndex - graphemeCodePointIndex + graphemeCodePointMax; + + // The next distinct source start, not an individual glyph's + // coverage count, tells layout whether this is the final shaped + // input represented by the current .NET grapheme. + bool isLastInGrapheme = nextShapedCodePointIndex > graphemeCodePointEnd; + + // Browsers attach letter spacing once to the final visual + // glyph for a shaped source start, preserving relative + // positions between a base and its combining marks. Adding the + // spacing to the entry's advance realizes exactly that: the + // positioned walk assigns each entry's residual advance to the + // final glyph of its slice. Browsers apply the same boundary. + // CSS Text §8.2.1 governs the spacing: + // https://www.w3.org/TR/css-text-4/#letter-spacing-property + if (isLastInGrapheme && hasTracking) { - // Determine if this is the last codepoint in the grapheme. - bool isLastInGrapheme = graphemeCodePointIndex == graphemeCodePointMax && i == decomposedAdvances.Length - 1; - - float decomposedAdvance = decomposedAdvances[i]; - - // Work out the scaled metrics for the glyph. - while (glyphData[glyphDataIndex].Data.IsPlaceholder) + // Tab characters and line terminators never receive tracking. + // CSS Text §8.2.1 also requires + // cursive joins to remain unspaced while word separators receive it: + // https://www.w3.org/TR/css-text-4/#cursive-tracking + if ((!isCursiveScript || CodePoint.IsWhiteSpace(codePoint)) + && !CodePoint.IsTabulation(codePoint) + && !CodePoint.IsNewLine(codePoint)) { - glyphDataIndex++; - } - - GlyphPositioningCollection.GlyphPositioningData positionedGlyph = glyphData[glyphDataIndex]; - FontGlyphMetrics metric = positionedGlyph.Metrics; - - // Adjust the advance for the last decomposed glyph to add tracking if applicable. - // Tracking should only be added once per grapheme, so only on the last codepoint of the grapheme. - if (isLastInGrapheme && options.Tracking != 0 && i == decomposedAdvances.Length - 1) - { - // Tracking should not be applied to tab characters or non-rendered codepoints. - if (!CodePoint.IsTabulation(codePoint) && !UnicodeUtility.ShouldNotBeRendered(codePoint)) + if (isHorizontalLayout || shouldRotate) { - if (isHorizontalLayout || shouldRotate) - { - float scaleAX = pointSize / glyph.ScaleFactor.X; - decomposedAdvance += options.Tracking * metric.FontMetrics.UnitsPerEm * scaleAX; - } - else - { - float scaleAY = pointSize / glyph.ScaleFactor.Y; - decomposedAdvance += options.Tracking * metric.FontMetrics.UnitsPerEm * scaleAY; - } + glyphAdvance += options.Tracking * glyph.FontMetrics.UnitsPerEm * (pointSize / glyph.ScaleFactor.X); + } + else + { + glyphAdvance += options.Tracking * glyph.FontMetrics.UnitsPerEm * (pointSize / glyph.ScaleFactor.Y); } } + } - // Convert design-space units to pixels based on the target point size. - // ScaleFactor.Y represents the vertical UPEM scaling factor for this glyph. - float scaleY = pointSize / metric.ScaleFactor.Y; - - // Choose which metrics table to use based on layout orientation. - // Horizontal is the default; vertical fonts use VMTX if available. - IMetricsHeader metricsHeader = isHorizontalLayout || shouldRotate - ? metric.FontMetrics.HorizontalMetrics - : metric.FontMetrics.VerticalMetrics; + // Convert design-space units to pixels based on the target point size. + // ScaleFactor.Y represents the vertical UPEM scaling factor for this glyph. + float scaleY = pointSize / glyph.ScaleFactor.Y; - // Ascender and descender are stored in font design units, so scale them to pixels. - float ascender = metricsHeader.Ascender * scaleY; + // Choose which metrics table to use based on layout orientation. + // Horizontal is the default; vertical fonts use VMTX if available. + IMetricsHeader metricsHeader = isHorizontalLayout || shouldRotate + ? glyph.FontMetrics.HorizontalMetrics + : glyph.FontMetrics.VerticalMetrics; - // Match browser line-height calculation logic. - // Reference: https://www.w3.org/TR/CSS2/visudet.html#propdef-line-height - // The line height in CSS is based on a multiple of the font-size (pointSize), - // but fonts may define a custom LineHeight in their metrics that differs from UPEM. - float descender = Math.Abs(metricsHeader.Descender * scaleY); - float lineHeight = metric.UnitsPerEm * scaleY; + // Ascender and descender are stored in font design units, so scale them to pixels. + float ascender = metricsHeader.Ascender * scaleY; - // The delta centers the font's line box within the CSS line box when - // LineHeight differs from the nominal font size. - float delta = ((metricsHeader.LineHeight * scaleY) - lineHeight) * 0.5F; + // Match browser line-height calculation logic. + // Reference: https://www.w3.org/TR/CSS2/visudet.html#propdef-line-height + // The line height in CSS is based on a multiple of the font-size (pointSize), + // but fonts may define a custom LineHeight in their metrics that differs from UPEM. + float descender = Math.Abs(metricsHeader.Descender * scaleY); + float lineHeight = glyph.UnitsPerEm * scaleY; - // Adjust ascender and descender symmetrically by delta to preserve visual balance. - ascender -= delta; - descender -= delta; + // The delta centers the font's line box within the CSS line box when + // LineHeight differs from the nominal font size. + float delta = ((metricsHeader.LineHeight * scaleY) - lineHeight) * 0.5F; - GlyphLayoutMode mode = GlyphLayoutMode.Horizontal; - if (isVerticalLayout) - { - mode = GlyphLayoutMode.Vertical; - } - else if (isVerticalMixedLayout) - { - mode = shouldRotate ? GlyphLayoutMode.VerticalRotated : GlyphLayoutMode.Vertical; - } + // Adjust ascender and descender symmetrically by delta to preserve visual balance. + ascender -= delta; + descender -= delta; - int hyphenationMarkerIndex = -1; - if (isSoftHyphen && hyphenationMarkerCodePoint.HasValue) - { - // U+00AD is shaped as an invisible source entry, but if this exact - // discretionary break is later selected we need a visible marker with - // the same run, font attributes, bidi mapping, and source mapping. Build - // that marker here while those values are already in hand; BreakLines can - // then account for its advance without rescanning or reshaping the line. - hyphenationMarkerIndex = hyphenationMarkers.Count; - hyphenationMarkers.Add(CreateGeneratedMarker( - glyph, - pointSize, - shapedText.BidiRuns[shapedText.BidiMap[codePointIndex]], - graphemeIndex, - isLastInGrapheme, - codePointIndex, - graphemeCodePointIndex, - stringIndex, - hyphenationMarkerCodePoint.Value, - shapedText.LayoutMode, - positionedGlyph.Font, - options)); - } + GlyphLayoutMode mode = GlyphLayoutMode.Horizontal; + if (isVerticalLayout) + { + mode = GlyphLayoutMode.Vertical; + } + else if (isVerticalMixedLayout) + { + mode = shouldRotate ? GlyphLayoutMode.VerticalRotated : GlyphLayoutMode.Vertical; + } - // Add our metrics to the line. - textLine.Add( - isDecomposed ? new FontGlyphMetrics[] { metric } : metrics, - positionedGlyph.Font, + int hyphenationMarkerIndex = -1; + if (isSoftHyphen && hyphenationMarkerCodePoint.HasValue) + { + // U+00AD is shaped as an invisible source entry, but if this exact + // discretionary break is later selected we need a visible marker with + // the same run, font attributes, bidi mapping, and source mapping. Build + // that marker here while those values are already in hand; BreakLines can + // then account for its advance without rescanning or reshaping the line. + hyphenationMarkerIndex = hyphenationMarkers.Count; + hyphenationMarkers.Add(CreateGeneratedMarker( + metricsSpan[0], pointSize, - decomposedAdvance, - lineHeight, - ascender, - descender, - delta, - shapedText.BidiRuns[shapedText.BidiMap[codePointIndex]], + bidiRun, graphemeIndex, isLastInGrapheme, codePointIndex, graphemeCodePointIndex, - shouldRotate || shouldOffset, - isDecomposed, stringIndex, - mode, - options.LineSpacing, - hyphenationMarkerIndex); - - glyphDataIndex++; + hyphenationMarkerCodePoint.Value, + shapedText.LayoutMode, + entryFont, + options)); } + // One entry per shaped source position, holding the whole slice. + // Line breaking and reordering move this unit; the glyphs inside + // keep the shaper's visual stream untouched. + textLine.Add( + metrics, + entryFont, + pointSize, + glyphAdvance, + lineHeight, + ascender, + descender, + delta, + bidiRun, + graphemeIndex, + isLastInGrapheme, + codePointIndex, + graphemeCodePointIndex, + shouldRotate || shouldOffset, + isDecomposed, + stringIndex, + mode, + options.LineSpacing, + hyphenationMarkerIndex); + codePointIndex++; graphemeCodePointIndex++; } @@ -412,22 +497,28 @@ VerticalOrientationType.Rotate or // Placeholders do not consume source text. A placeholder inserted at // the final source position has no following codepoint to visit in // the main loop, so we add those trailing placeholder entries here. - if (shapedText.Positionings.TryGetGlyphMetricsAtOffset( + if (shapedText.TryGetGlyphsAtOffset( codePointIndex, + ref glyphSearchBidiRunIndex, ref glyphSearchIndex, + out int endStart, + out int endCount, out _, out _, out _, out _, - out IReadOnlyList? endGlyphData)) + out _)) { - for (int i = 0; i < endGlyphData.Count; i++) + for (int i = 0; i < endCount; i++) { - GlyphPositioningCollection.GlyphPositioningData data = endGlyphData[i]; - if (data.Data.IsPlaceholder) + ref readonly ShapedGlyphInfo info = ref shapedText.Infos[endStart + i]; + if (info.IsPlaceholder) { + ShapedTextRun run = shapedText.Runs[info.RunIndex]; textLine.AddPlaceholder( - data, + PlaceholderGlyphMetrics.Create(run.Font, run.TextRun, options.Dpi), + in run, + info.CodePointIndex, graphemeIndex, stringIndex, isHorizontalLayout, @@ -437,10 +528,11 @@ VerticalOrientationType.Rotate or } } - // Line break candidates are width-independent and belong with the composed logical line. - List lineBreaks = CollectLineBreaks(text, hyphenationMarkerCodePoint.HasValue); - - return new LogicalTextLine(textLine, lineBreaks, wordSegments, hyphenationMarkers); + // Browsers retain the source text and query break opportunities through a + // lazy cursor during line filling rather than materializing the paragraph's + // candidates. Retain the text once so every wrapping length can run the + // same streaming query. + return new LogicalTextLine(textLine, text.ToArray(), wordSegments, hyphenationMarkers); } /// @@ -494,52 +586,15 @@ public static TextDirection GetTextDirection(in LogicalTextLine logicalLine, Tex : options.TextDirection; /// - /// Collects the line break opportunities used by the wrapping loop. + /// Gets the configured hyphenation marker codepoint. /// /// - /// - /// is the Unicode-conforming default line breaker. Its default - /// constructor remains independent from layout policy so the Unicode conformance tests continue to - /// describe only the default UAX #14 behavior. This method is the boundary where layout-specific - /// tailoring is requested. - /// - /// - /// The line breaker itself is streaming and does not allocate. Layout materializes the resulting - /// break opportunities because the line fitting loop scans the same candidates repeatedly while it - /// removes finalized lines from the front of the shaped text line. - /// - /// - /// Solidus handling is intentionally conservative. UAX #14 classifies U+002F SOLIDUS as SY, which - /// gives ordinary text a break opportunity after a slash. That is valid for the default algorithm, - /// but it produced undesirable layout in issue 448 for ordinary slash-separated text. At the same - /// time, UAX #14 section 8 explicitly calls out URL tailoring that can allow breaks after slash - /// separated URL segments even when the next segment starts with a digit. The result here is: - /// keep default slash behavior for standard enumeration, suppress ordinary slash breaks for layout, - /// and reintroduce the narrow URL numeric-segment break only for URL-like runs. - /// + /// Also the switch the line-filling cursor uses to decide whether soft-hyphen + /// break opportunities participate in wrapping at all. /// - /// The original source text being laid out. - /// Whether soft-hyphen break opportunities should be included. - /// The ordered line break opportunities after layout-level tailoring. - private static List CollectLineBreaks(ReadOnlySpan text, bool includeHyphenationBreaks) - { - LineBreakEnumerator lineBreakEnumerator = new(text, tailorUrls: true); - List lineBreaks = []; - while (lineBreakEnumerator.MoveNext()) - { - LineBreak lineBreak = lineBreakEnumerator.Current; - if (lineBreak.IsHyphenationBreak && !includeHyphenationBreaks) - { - continue; - } - - lineBreaks.Add(lineBreak); - } - - return lineBreaks; - } - - private static CodePoint? GetHyphenationMarkerCodePoint(TextOptions options) + /// The text options used for layout. + /// The configured hyphenation marker codepoint, or when hyphenation is disabled. + public static CodePoint? GetHyphenationMarkerCodePoint(TextOptions options) => options.TextHyphenation switch { TextHyphenation.Standard => new CodePoint(StandardHyphen), @@ -550,7 +605,7 @@ private static List CollectLineBreaks(ReadOnlySpan text, bool i /// /// Creates a visible generated marker that matches the layout style of the anchor entry. /// - /// The glyph metric that supplies font, run, attributes, and decorations. + /// The positioned anchor glyph that supplies font, run, attributes, and decorations. /// The point size at which the marker is rendered. /// The bidi run that the marker belongs to. /// The source grapheme index to map the marker to. @@ -563,8 +618,8 @@ private static List CollectLineBreaks(ReadOnlySpan text, bool i /// The font used to shape and render the marker. /// The text options used for layout. /// The generated marker entry. - internal static GlyphLayoutData CreateGeneratedMarker( - FontGlyphMetrics anchorMetric, + public static GlyphLayoutData CreateGeneratedMarker( + PositionedGlyphMetrics anchor, float pointSize, BidiRun bidiRun, int graphemeIndex, @@ -577,6 +632,7 @@ internal static GlyphLayoutData CreateGeneratedMarker( Font font, TextOptions options) { + FontGlyphMetrics anchorMetric = anchor.Metrics; anchorMetric.FontMetrics.TryGetGlyphId(markerCodePoint, out ushort markerGlyphId); FontGlyphMetrics markerMetric = anchorMetric.FontMetrics.GetGlyphMetrics( @@ -587,8 +643,6 @@ internal static GlyphLayoutData CreateGeneratedMarker( layoutMode, options.ColorFontSupport); - markerMetric = markerMetric.CloneForRendering(anchorMetric.TextRun); - bool isHorizontalLayout = layoutMode.IsHorizontal(); bool isVerticalLayout = layoutMode.IsVertical(); bool isVerticalMixedLayout = layoutMode.IsVerticalMixed(); @@ -634,10 +688,12 @@ VerticalOrientationType.Rotate or FontRectangle markerBox = FontGlyphMetrics.ShouldSkipGlyphRendering(markerMetric.CodePoint) ? FontRectangle.Empty - : markerMetric.GetBoundingBox(markerMode, Vector2.Zero, pointSize); + : markerMetric.GetBoundingBox(markerMode, Vector2.Zero, pointSize, anchor.TextRun, Vector2.Zero, new Vector2(markerMetric.AdvanceWidth, markerMetric.AdvanceHeight)); + // Generated markers are not part of the shaped stream, so the entry owns + // its single-glyph storage. return new GlyphLayoutData( - new FontGlyphMetrics[] { markerMetric }, + new PositionedGlyphMetrics[] { new(markerMetric, markerMetric.AdvanceWidth, markerMetric.AdvanceHeight, Vector2.Zero, anchor.TextRun) }, font, pointSize, markerAdvance, diff --git a/src/SixLabors.Fonts/TextLayout.cs b/src/SixLabors.Fonts/TextLayout.cs index d6ea9118b..4a526c59e 100644 --- a/src/SixLabors.Fonts/TextLayout.cs +++ b/src/SixLabors.Fonts/TextLayout.cs @@ -3,7 +3,6 @@ using System.Numerics; using SixLabors.Fonts.Tables.AdvancedTypographic; -using SixLabors.Fonts.Unicode; namespace SixLabors.Fonts; @@ -22,265 +21,6 @@ internal static partial class TextLayout /// private static readonly Tag IdeographicBaselineTag = Tag.Parse("ideo"); - /// - /// Resolves the ordered sequence of instances that cover . - /// - /// - /// If is or empty, a single run covering the entire - /// grapheme range of using is returned. Otherwise the - /// supplied runs are ordered, gaps are filled with default-font runs, and overlapping ranges are trimmed. - /// - /// The text to partition into runs. - /// The text options supplying the default font and optional user-defined runs. - /// The resolved runs that together cover the entire grapheme range of . - public static IReadOnlyList BuildTextRuns(ReadOnlySpan text, TextOptions options) - { - int start = 0; - int end = text.GetGraphemeCount(); - if (end == 0) - { - return []; - } - - if (options.TextRuns is null || options.TextRuns.Count == 0) - { - TextRun textRun = new() - { - Start = 0, - End = text.GetGraphemeCount(), - Font = options.Font - }; - - textRun.ResolveFontWeight(options.FontWeight); - return [textRun]; - } - - List textRuns = []; - foreach (TextRun textRun in options.TextRuns.OrderBy(x => x.Start)) - { - // Fill gaps within runs. - if (textRun.Start > start) - { - textRuns.Add(new() - { - Start = start, - End = textRun.Start, - Font = options.Font - }); - } - - // Add the current run, ensuring the font is not null. - textRun.Font ??= options.Font; - - if (textRun.Placeholder.HasValue && textRun.End != textRun.Start) - { - throw new ArgumentException("Placeholder text runs must be zero-length insertion runs.", nameof(options)); - } - - // Ensure that the previous run does not overlap the current. - if (textRuns.Count > 0) - { - int prevIndex = textRuns.Count - 1; - TextRun previous = textRuns[prevIndex]; - previous.End = Math.Min(previous.End, textRun.Start); - } - - textRuns.Add(textRun); - start = textRun.End; - } - - // Add a final run if required. - if (start < end) - { - textRuns.Add(new() - { - Start = start, - End = end, - Font = options.Font - }); - } - - foreach (TextRun textRun in textRuns) - { - textRun.ResolveFontWeight(options.FontWeight); - } - - return textRuns; - } - - /// - /// Shapes into shaping state that is independent of the wrapping length. - /// - /// - /// Performs the font-run build, bidi analysis, GSUB/GPOS shaping (including fallback font - /// resolution for unmapped codepoints). The result contains the positioned glyph collection - /// and bidi state used by logical line composition. - /// - /// The text to process. - /// The text options used while shaping. - /// The wrapping-independent shaping state. - public static ShapedText ShapeText(ReadOnlySpan text, TextOptions options) - { - // Gather the font and fallbacks. - Font[] fallbackFonts = (options.FallbackFontFamilies?.Count > 0) - ? [.. options.FallbackFontFamilies.Select(x => new Font(x, options.Font.Size, options.Font.RequestedStyle))] - : []; - - LayoutMode layoutMode = options.LayoutMode; - GlyphSubstitutionCollection substitutions = new(options); - GlyphPositioningCollection positionings = new(options); - - // Analyse the text for bidi directional runs. - BidiAlgorithm bidi = BidiAlgorithm.Instance.Value!; - BidiData bidiData = new(); - bidiData.Init(text, (sbyte)options.TextDirection); - - if (options.TextBidiMode == TextBidiMode.Override) - { - BidiCharacterType overrideType = options.TextDirection == TextDirection.Auto - ? (bidi.ResolveEmbeddingLevel(bidiData.Types) == 1 ? BidiCharacterType.RightToLeft : BidiCharacterType.LeftToRight) - : (options.TextDirection == TextDirection.RightToLeft ? BidiCharacterType.RightToLeft : BidiCharacterType.LeftToRight); - - for (int i = 0; i < bidiData.Types.Length; i++) - { - // Bidi override is a higher-level protocol override: real text behaves as the requested - // strong direction, while separators and explicit bidi controls keep their structural role. - bidiData.Types[i] = bidiData.Types[i] switch - { - BidiCharacterType.ParagraphSeparator - or BidiCharacterType.SegmentSeparator - or BidiCharacterType.BoundaryNeutral - or BidiCharacterType.LeftToRightEmbedding - or BidiCharacterType.RightToLeftEmbedding - or BidiCharacterType.LeftToRightOverride - or BidiCharacterType.RightToLeftOverride - or BidiCharacterType.PopDirectionalFormat - or BidiCharacterType.LeftToRightIsolate - or BidiCharacterType.RightToLeftIsolate - or BidiCharacterType.FirstStrongIsolate - or BidiCharacterType.PopDirectionalIsolate => bidiData.Types[i], - _ => overrideType, - }; - } - } - - bidi.Process(bidiData); - - // Get the list of directional runs - BidiRun[] bidiRuns = [.. BidiRun.CoalesceLevels(bidi.ResolvedLevels)]; - Dictionary bidiMap = []; - - // Incrementally build out collection of glyphs. - IReadOnlyList textRuns = BuildTextRuns(text, options); - - // First do multiple font runs using the individual text runs. - bool complete = true; - int textRunIndex = 0; - int codePointIndex = 0; - int bidiRunIndex = 0; - foreach (TextRun textRun in textRuns) - { - if (textRun.Placeholder.HasValue) - { - substitutions.Clear(); - - while (bidiRunIndex < bidiRuns.Length && codePointIndex == bidiRuns[bidiRunIndex].End) - { - bidiRunIndex++; - } - - // Placeholder direction comes from the bidi region at the insertion - // point. If the insertion point is after all source text, use the - // default even/LTR embedding level. - BidiRun placeholderBidiRun = bidiRunIndex < bidiRuns.Length - ? bidiRuns[bidiRunIndex] - : new(BidiCharacterType.LeftToRight, 2, codePointIndex, 0); - - // Placeholder runs are inserted into the layout stream and do not consume - // source graphemes, source codepoints, or bidi runs. - substitutions.AddPlaceholder( - CodePoint.ObjectReplacementChar, - placeholderBidiRun, - textRun, - codePointIndex); - - complete &= positionings.TryAdd(textRun.ResolvedFont, substitutions); - textRunIndex++; - continue; - } - - if (!DoFontRun( - textRun.Slice(text), - textRun.Start, - textRuns, - ref textRunIndex, - ref codePointIndex, - ref bidiRunIndex, - false, - textRun.ResolvedFont, - bidiRuns, - bidiMap, - substitutions, - positionings)) - { - complete = false; - } - } - - if (!complete) - { - // Finally try our fallback fonts. - // We do a complete run here across the whole collection. - foreach (Font font in fallbackFonts) - { - textRunIndex = 0; - codePointIndex = 0; - bidiRunIndex = 0; - if (DoFontRun( - text, - 0, - textRuns, - ref textRunIndex, - ref codePointIndex, - ref bidiRunIndex, - true, - font, - bidiRuns, - bidiMap, - substitutions, - positionings)) - { - break; - } - } - } - - // Update the positions of the glyphs in the completed collection. - // Each set of metrics is associated with single font and will only be updated - // by that font so it's safe to use a single collection. - Font? lastFont = null; - for (int i = 0; i < textRuns.Count; i++) - { - TextRun textRun = textRuns[i]; - - Font font = textRun.ResolvedFont; - if (font == lastFont) - { - continue; - } - - font.FontMetrics.UpdatePositions(positionings); - lastFont = font; - } - - foreach (Font font in fallbackFonts) - { - font.FontMetrics.UpdatePositions(positionings); - } - - return new ShapedText(positionings, bidiRuns, bidiMap, layoutMode); - } - /// /// Lays out the supplied , streaming each laid-out glyph through the /// supplied in layout order using the supplied wrapping length for alignment. @@ -515,7 +255,7 @@ private static float GetBaselineShift(float baselineOffset, float dpi, bool isVe /// Use to disable culling at this edge. /// /// The visitor that receives each positioned glyph. - internal static void LayoutText( + public static void LayoutText( TextBox textBox, TextOptions options, float wrappingLength, @@ -835,7 +575,8 @@ private static void LayoutLineHorizontal( if (data.IsNewLine) { - FontGlyphMetrics metric = data.Metrics[0]; + PositionedGlyphMetrics hardBreakPositioned = data.Metrics.Span[0]; + FontGlyphMetrics metric = hardBreakPositioned.Metrics; // Hard breaks bypass the normal glyph loop, but still need the // current pen position plus the same baseline origin used by glyphs. @@ -843,7 +584,7 @@ private static void LayoutLineHorizontal( visitor.Visit( new GlyphLayout( - new Glyph(metric, data.PointSize), + new Glyph(metric, data.PointSize, hardBreakPositioned.TextRun, hardBreakPositioned.Offset, new Vector2(hardBreakPositioned.AdvanceWidth, hardBreakPositioned.AdvanceHeight)), data.Font, boundsLocation, hardBreakGlyphOrigin, @@ -865,28 +606,45 @@ private static void LayoutLineHorizontal( return; } - // Index rather than enumerate: the interface-typed metrics list would allocate a - // heap enumerator per glyph on this per-glyph hot path. - IReadOnlyList metrics = data.Metrics; - for (int j = 0; j < metrics.Count; j++) + // The entry's slice is the shaper's visual glyph stream; index it as a + // span so the per-glyph hot path performs no interface dispatch. + ReadOnlySpan metrics = data.Metrics.Span; + float glyphAdvanceX = 0; + for (int j = 0; j < metrics.Length; j++) { - FontGlyphMetrics metric = metrics[j]; - Vector2 glyphOrigin = penLocation + new Vector2(0, textLine.ScaledMaxAscender); + PositionedGlyphMetrics positioned = metrics[j]; + FontGlyphMetrics metric = positioned.Metrics; + float positionedAdvanceX = positioned.AdvanceWidth * (data.PointSize / metric.ScaleFactor.X); + + // Browsers supply the current accumulated advance to each glyph + // before adding that glyph's own advance. Preserve that positioned + // walk when several glyphs share one layout entry. + Vector2 advanceOrigin = boundsLocation + new Vector2(glyphAdvanceX, 0); + Vector2 glyphOrigin = penLocation + new Vector2(glyphAdvanceX, textLine.ScaledMaxAscender); + + // Tracking and justification live on the layout entry rather than + // in shaping. Assign their residual to the final positioned glyph + // so per-glyph logical boxes still sum to the entry's exact advance. + float glyphLayoutAdvance = j == metrics.Length - 1 + ? data.ScaledAdvance - glyphAdvanceX + : positionedAdvanceX; visitor.Visit( new GlyphLayout( - new Glyph(metric, data.PointSize), + new Glyph(metric, data.PointSize, positioned.TextRun, positioned.Offset, new Vector2(positioned.AdvanceWidth, positioned.AdvanceHeight)), data.Font, - boundsLocation, + advanceOrigin, glyphOrigin, glyphOrigin, - data.ScaledAdvance, + glyphLayoutAdvance, advanceY, GlyphLayoutMode.Horizontal, data.BidiRun.Level, i == 0 && j == 0, data.GraphemeIndex, data.StringIndex)); + + glyphAdvanceX += positionedAdvanceX; } boxLocation.X += layoutAdvance; @@ -903,8 +661,7 @@ private static void LayoutLineHorizontal( /// /// Positions one line of vertical text ( and /// ). All glyphs are treated as naturally vertical — - /// transformed (rotated) graphemes receive grapheme-level horizontal centering based on the - /// collective ink width of every entry sharing a grapheme index. + /// every shaped glyph is positioned at its running vertical advance. /// /// The concrete visitor struct type. /// The containing text box (used to look up sibling lines for block alignment). @@ -1045,23 +802,7 @@ private static void LayoutLineVertical( return; } - float lineOriginX = penLocation.X; Vector2 boundsLocation = boxLocation; - float boundsLineOriginX = boundsLocation.X; - - // Grapheme-scoped state for transformed glyph alignment. - // - // IMPORTANT: GlyphLayoutData is per-codepoint, not per-grapheme. - // Complex scripts can therefore produce multiple entries for a single grapheme. - // For example Devanagari "र्कि" can end up as two entries ("र्" and "कि") even though it - // visually shapes as a single cluster. - // - // - Compute a single alignX for the whole grapheme (across all entries with the same GraphemeIndex). - // - Apply that alignX as a positional offset only, never as part of pen/box advance. - // - Transformed entries still advance along X within the grapheme (horizontal glyphs inside a vertical flow), - // then X is reset at the end of the grapheme. - float currentGraphemeAlignX = 0; - bool currentGraphemeIsTransformed = false; for (int i = 0; i < textLine.Count; i++) { @@ -1071,7 +812,8 @@ private static void LayoutLineVertical( if (data.IsNewLine) { - FontGlyphMetrics metric = data.Metrics[0]; + PositionedGlyphMetrics hardBreakPositioned = data.Metrics.Span[0]; + FontGlyphMetrics metric = hardBreakPositioned.Metrics; Vector2 scale = new Vector2(data.PointSize) / metric.ScaleFactor; // Hard breaks bypass the normal glyph loop, but still need the @@ -1081,7 +823,7 @@ private static void LayoutLineVertical( visitor.Visit( new GlyphLayout( - new Glyph(metric, data.PointSize), + new Glyph(metric, data.PointSize, hardBreakPositioned.TextRun, hardBreakPositioned.Offset, new Vector2(hardBreakPositioned.AdvanceWidth, hardBreakPositioned.AdvanceHeight)), data.Font, boundsLocation, hardBreakGlyphOrigin, @@ -1105,184 +847,74 @@ private static void LayoutLineVertical( int j = 0; - bool isFirstInGrapheme = data.GraphemeCodePointIndex == 0; - float alignX = 0; - float entryScaledAdvanceWidth = 0; - - if (isFirstInGrapheme) - { - // Reset grapheme-scoped state at the start of each grapheme. - currentGraphemeAlignX = 0; - currentGraphemeIsTransformed = false; - - // Determine whether this grapheme contains any transformed entries. - // This is intentionally done at grapheme scope because individual entries can differ. - int graphemeIndex = data.GraphemeIndex; - - for (int k = i; k < textLine.Count; k++) - { - GlyphLayoutData g = textLine[k]; - - if (g.GraphemeIndex != graphemeIndex) - { - break; - } - - if (g.IsTransformed) - { - currentGraphemeIsTransformed = true; - break; - } - } - - if (currentGraphemeIsTransformed) - { - // In vertical layout, glyphs with a vertical orientation of TransformRotate/TransformUpright are - // rendered as "horizontal" glyphs inside a vertical flow. - // - // Their horizontal metrics (including LSB) are still expressed in the font's horizontal writing mode, - // so without an adjustment these glyphs appear shifted within the column. - // - // To make transformed glyphs align visually with naturally-vertical glyphs, we center the ink bounds - // of the ENTIRE grapheme (across all entries with the same GraphemeIndex) within the column width - // (`scaledMaxLineHeight`). - float minX = float.PositiveInfinity; - float maxX = float.NegativeInfinity; - - for (int k = i; k < textLine.Count; k++) - { - GlyphLayoutData g = textLine[k]; - - if (g.GraphemeIndex != graphemeIndex) - { - break; - } - - // Index rather than enumerate to avoid a heap enumerator per grapheme. - IReadOnlyList inkMetrics = g.Metrics; - for (int m = 0; m < inkMetrics.Count; m++) - { - FontGlyphMetrics inkMetric = inkMetrics[m]; - Vector2 s = new Vector2(g.PointSize) / inkMetric.ScaleFactor; - - float glyphMinX = inkMetric.Bounds.Min.X * s.X; - float glyphMaxX = inkMetric.Bounds.Max.X * s.X; - - if (glyphMinX < minX) - { - minX = glyphMinX; - } - - if (glyphMaxX > maxX) - { - maxX = glyphMaxX; - } - } - } - - float inkWidth = maxX - minX; - - // Normalize ink minX to 0 and center within the entry's own line box. - // The decoration origin has already centered that entry line box within - // the widest line box, so using the widest line box here would apply the - // mixed-size offset twice. - // This is grapheme-correct and avoids centering based only on the "first" entry, - // which is not representative for marks like reph in Devanagari. - currentGraphemeAlignX = -minX + ((scaledLineHeight - inkWidth) * .5F); - } - } - - if (currentGraphemeIsTransformed) + // The entry's slice is the shaper's visual glyph stream; index it as a + // span so the per-glyph hot path performs no interface dispatch. + ReadOnlySpan metrics = data.Metrics.Span; + float glyphAdvanceY = 0; + for (int metricIndex = 0; metricIndex < metrics.Length; metricIndex++) { - // Apply the grapheme-level horizontal centering offset to every entry in the grapheme. - // This is positional only and must never be folded into any advance. - alignX = currentGraphemeAlignX; - - // Transformed glyphs are still positioned using horizontal metrics (`AdvanceWidth`) even though - // they participate in a vertical flow. `AdvanceWidth` gives us the horizontal pen advance we must - // apply between entries inside the transformed grapheme. - // Index rather than enumerate to avoid a heap enumerator per glyph. - IReadOnlyList transformedMetrics = data.Metrics; - for (int m = 0; m < transformedMetrics.Count; m++) - { - FontGlyphMetrics transformedMetric = transformedMetrics[m]; - Vector2 s = new Vector2(data.PointSize) / transformedMetric.ScaleFactor; - entryScaledAdvanceWidth += transformedMetric.AdvanceWidth * s.X; - } - } + PositionedGlyphMetrics positioned = metrics[metricIndex]; + FontGlyphMetrics metric = positioned.Metrics; - // Index rather than enumerate: the interface-typed metrics list would allocate a - // heap enumerator per glyph on this per-glyph hot path. - IReadOnlyList metrics = data.Metrics; - for (int metricIndex = 0; metricIndex < metrics.Count; metricIndex++) - { - FontGlyphMetrics metric = metrics[metricIndex]; - - // Align the glyph horizontally and vertically centering vertically around the baseline. + // Browsers retain each shaped glyph and advance the vertical pen after + // positioning it; source grouping only controls added letter spacing. Vector2 scale = new Vector2(data.PointSize) / metric.ScaleFactor; - float glyphAlignX = alignX; - if (!currentGraphemeIsTransformed) + // Upright glyphs use a vertical origin centered on half their + // nominal horizontal advance, even when shaping zeroed a mark's + // positioned advance, so center that nominal width in the line box. + float glyphAlignX = (scaledLineHeight - (metric.AdvanceWidth * scale.X)) * .5F; + float verticalOriginY = (metric.Bounds.Max.Y + metric.TopSideBearing) * scale.Y; + float positionedAdvanceY = positioned.AdvanceHeight * scale.Y; + VerticalMetrics verticalMetrics = metric.FontMetrics.VerticalMetrics; + if (verticalMetrics.Synthesized && positioned.AdvanceHeight != 0) { - // Vertical origin fallback places the vertical origin at half the - // horizontal advance. The decoration origin has already centered this - // entry's line box in the column, so center the glyph advance inside it. - glyphAlignX = (scaledLineHeight - (metric.AdvanceWidth * scale.X)) * .5F; + // Browsers round the synthesized nominal height before shaping. + // Replace that component after shaping while retaining any + // positioning delta carried by this glyph. + float nominalAdvance = metric.AdvanceHeight * scale.Y; + float browserAdvance = (MathF.Floor((verticalMetrics.Ascender * scale.Y * options.Dpi) + .5F) + + MathF.Floor((-verticalMetrics.Descender * scale.Y * options.Dpi) + .5F)) / options.Dpi; + positionedAdvanceY += browserAdvance - nominalAdvance; } // Move the glyph origin without changing the advance or decoration origin. - Vector2 glyphOffset = new(glyphAlignX, (metric.Bounds.Max.Y + metric.TopSideBearing) * scale.Y); - Vector2 decorationOrigin = penLocation + new Vector2((unscaledLineHeight - scaledLineHeight) * .5F, 0); + Vector2 glyphOffset = new(glyphAlignX, verticalOriginY); + Vector2 advanceOrigin = boundsLocation + new Vector2(0, glyphAdvanceY); + Vector2 decorationOrigin = penLocation + new Vector2((unscaledLineHeight - scaledLineHeight) * .5F, glyphAdvanceY); Vector2 glyphOrigin = decorationOrigin + glyphOffset; - float advanceW = advanceX; - - if (currentGraphemeIsTransformed && !isFirstInGrapheme) - { - // For transformed glyphs after the first in the grapheme we advance - // horizontally using the horizontal advance not the line height. - // This gives us the correct total advance across the grapheme. - advanceW = scale.X * metric.AdvanceWidth; - } + // The final positioned glyph owns tracking and justification so the + // logical boxes cover the exact entry advance without changing the + // HarfBuzz-derived origins of any preceding glyph. + float glyphLayoutAdvance = metricIndex == metrics.Length - 1 + ? data.ScaledAdvance - glyphAdvanceY + : positionedAdvanceY; visitor.Visit( new GlyphLayout( - new Glyph(metric, data.PointSize), + new Glyph(metric, data.PointSize, positioned.TextRun, positioned.Offset, new Vector2(positioned.AdvanceWidth, positioned.AdvanceHeight)), data.Font, - boundsLocation, + advanceOrigin, glyphOrigin, decorationOrigin, - advanceW, - data.ScaledAdvance, + advanceX, + glyphLayoutAdvance, GlyphLayoutMode.Vertical, data.BidiRun.Level, i == 0 && j == 0, data.GraphemeIndex, data.StringIndex)); + // Several glyphs may share one source position. Advance after each + // visit so marks and decomposed forms retain their shaped + // relative positions instead of being painted on one origin. + glyphAdvanceY += positionedAdvanceY; j++; } - if (currentGraphemeIsTransformed) - { - // Advance horizontally between entries inside the transformed grapheme. - boxLocation.X += entryScaledAdvanceWidth; - penLocation.X += entryScaledAdvanceWidth; - } - - if (currentGraphemeIsTransformed) - { - boundsLocation.X += entryScaledAdvanceWidth; - } - - if (data.IsLastInGrapheme) - { - penLocation.Y += layoutAdvance; - boxLocation.X = lineOriginX; - penLocation.X = lineOriginX; - boundsLocation.Y += data.ScaledAdvance; - boundsLocation.X = boundsLineOriginX; - } + penLocation.Y += layoutAdvance; + boundsLocation.Y += data.ScaledAdvance; } boxLocation.Y = originY; @@ -1446,7 +1078,8 @@ private static void LayoutLineVerticalMixed( if (data.IsNewLine) { - FontGlyphMetrics metric = data.Metrics[0]; + PositionedGlyphMetrics hardBreakPositioned = data.Metrics.Span[0]; + FontGlyphMetrics metric = hardBreakPositioned.Metrics; Vector2 scale = new Vector2(data.PointSize) / metric.ScaleFactor; // Hard breaks bypass the normal glyph loop, but still need the @@ -1456,7 +1089,7 @@ private static void LayoutLineVerticalMixed( visitor.Visit( new GlyphLayout( - new Glyph(metric, data.PointSize), + new Glyph(metric, data.PointSize, hardBreakPositioned.TextRun, hardBreakPositioned.Offset, new Vector2(hardBreakPositioned.AdvanceWidth, hardBreakPositioned.AdvanceHeight)), data.Font, boundsLocation, hardBreakGlyphOrigin, @@ -1480,78 +1113,117 @@ private static void LayoutLineVerticalMixed( if (data.IsTransformed) { - // Index rather than enumerate: the interface-typed metrics list would allocate a - // heap enumerator per glyph on this per-glyph hot path. - IReadOnlyList metrics = data.Metrics; - for (int j = 0; j < metrics.Count; j++) + // Browsers derive the text origin from the primary font of the styled run, then + // paints every fallback glyph at that shared baseline. Using each fallback + // font's ascender and descender here would shift scripts with different metrics + // across the column even though they belong to the same styled run. + FontMetrics baselineFontMetrics = data.Metrics.Span[0].TextRun.ResolvedFont.FontMetrics; + HorizontalMetrics baselineMetrics = baselineFontMetrics.HorizontalMetrics; + float baselineScale = data.PointSize / baselineFontMetrics.ScaleFactor; + float centralOffset = (baselineMetrics.Ascender + baselineMetrics.Descender) * .5F * baselineScale; + float baselineX = (unscaledLineHeight * .5F) - centralOffset; + + // The entry's slice is the shaper's visual glyph stream; index it as + // a span so the per-glyph hot path performs no interface dispatch. + ReadOnlySpan metrics = data.Metrics.Span; + float glyphAdvanceY = 0; + for (int j = 0; j < metrics.Length; j++) { - FontGlyphMetrics metric = metrics[j]; + PositionedGlyphMetrics positioned = metrics[j]; + FontGlyphMetrics metric = positioned.Metrics; + float positionedAdvanceY = positioned.AdvanceWidth * (data.PointSize / metric.ScaleFactor.X); // The glyph will be rotated 90 degrees for vertical mixed layout. - // We still advance along Y, but the glyphs are laid out sideways in X. + // Its horizontal shaped advance therefore becomes a positive + // vertical device-space advance after the clockwise rotation. + Vector2 advanceOrigin = boundsLocation + new Vector2(0, glyphAdvanceY); + Vector2 glyphOrigin = penLocation + new Vector2(baselineX, glyphAdvanceY); - // Rotated glyphs sit on the line's alphabetic baseline, which lies half - // the ascender-plus-descender span toward the under side of the central - // column axis at the middle of the line. Upright glyphs in the same line - // center on that axis, so both orientations share the column lines the - // horizontal metrics synthesize, which is also how browsers position - // mixed-orientation runs. - Vector2 rotatedScale = new Vector2(data.PointSize) / metric.ScaleFactor; - HorizontalMetrics rotatedMetrics = metric.FontMetrics.HorizontalMetrics; - float centralOffset = (rotatedMetrics.Ascender + rotatedMetrics.Descender) * .5F * rotatedScale.Y; - - float baselineX = (unscaledLineHeight * .5F) - centralOffset; - Vector2 glyphOrigin = penLocation + new Vector2(baselineX, 0); + // Preserve the positioned-glyph walk and attach any layout-only + // spacing to its final glyph, exactly as in the horizontal path. + float glyphLayoutAdvance = j == metrics.Length - 1 + ? data.ScaledAdvance - glyphAdvanceY + : positionedAdvanceY; visitor.Visit( new GlyphLayout( - new Glyph(metric, data.PointSize), + new Glyph(metric, data.PointSize, positioned.TextRun, positioned.Offset, new Vector2(positioned.AdvanceWidth, positioned.AdvanceHeight)), data.Font, - boundsLocation, + advanceOrigin, glyphOrigin, glyphOrigin, advanceX, - data.ScaledAdvance, + glyphLayoutAdvance, GlyphLayoutMode.VerticalRotated, data.BidiRun.Level, i == 0 && j == 0, data.GraphemeIndex, data.StringIndex)); + + glyphAdvanceY += positionedAdvanceY; } } else { - // Index rather than enumerate to avoid a heap enumerator per glyph. - IReadOnlyList metrics = data.Metrics; - for (int j = 0; j < metrics.Count; j++) + // The entry's slice is the shaper's visual glyph stream; index it as + // a span so the per-glyph hot path performs no interface dispatch. + ReadOnlySpan metrics = data.Metrics.Span; + float glyphAdvanceY = 0; + for (int j = 0; j < metrics.Length; j++) { - FontGlyphMetrics metric = metrics[j]; + PositionedGlyphMetrics positioned = metrics[j]; + FontGlyphMetrics metric = positioned.Metrics; - // Align the glyph horizontally and vertically centering vertically around the baseline. + // Each glyph from one source position retains its shaped origin and + // contributes its positioned advance to the following glyph. Vector2 scale = new Vector2(data.PointSize) / metric.ScaleFactor; // Vertical origin fallback places the vertical origin at half the - // horizontal advance. The decoration origin has already centered this - // entry's line box in the column, so center the glyph advance inside it. + // nominal horizontal advance. Positioned mark advances can be zero, + // but that must not move their vertical origin across the column. float glyphAlignX = (scaledLineHeight - (metric.AdvanceWidth * scale.X)) * .5F; - Vector2 glyphOffset = new(glyphAlignX, (metric.Bounds.Max.Y + metric.TopSideBearing) * scale.Y); - Vector2 decorationOrigin = penLocation + new Vector2((unscaledLineHeight - scaledLineHeight) * .5F, 0); + float verticalOriginY = (metric.Bounds.Max.Y + metric.TopSideBearing) * scale.Y; + float positionedAdvanceY = positioned.AdvanceHeight * scale.Y; + VerticalMetrics verticalMetrics = metric.FontMetrics.VerticalMetrics; + if (verticalMetrics.Synthesized && positioned.AdvanceHeight != 0) + { + // Preserve the shaper's positioning delta while replacing the + // nominal synthesized height with the device-rounded browser value. + float nominalAdvance = metric.AdvanceHeight * scale.Y; + float browserAdvance = (MathF.Floor((verticalMetrics.Ascender * scale.Y * options.Dpi) + .5F) + + MathF.Floor((-verticalMetrics.Descender * scale.Y * options.Dpi) + .5F)) / options.Dpi; + positionedAdvanceY += browserAdvance - nominalAdvance; + } + + Vector2 glyphOffset = new(glyphAlignX, verticalOriginY); + Vector2 advanceOrigin = boundsLocation + new Vector2(0, glyphAdvanceY); + Vector2 decorationOrigin = penLocation + new Vector2((unscaledLineHeight - scaledLineHeight) * .5F, glyphAdvanceY); Vector2 glyphOrigin = decorationOrigin + glyphOffset; + // Layout-only spacing belongs to the final positioned glyph, + // preserving exact per-glyph origins and the aggregate advance. + float glyphLayoutAdvance = j == metrics.Length - 1 + ? data.ScaledAdvance - glyphAdvanceY + : positionedAdvanceY; + visitor.Visit( new GlyphLayout( - new Glyph(metric, data.PointSize), + new Glyph(metric, data.PointSize, positioned.TextRun, positioned.Offset, new Vector2(positioned.AdvanceWidth, positioned.AdvanceHeight)), data.Font, - boundsLocation, + advanceOrigin, glyphOrigin, decorationOrigin, advanceX, - data.ScaledAdvance, + glyphLayoutAdvance, GlyphLayoutMode.Vertical, data.BidiRun.Level, i == 0 && j == 0, data.GraphemeIndex, data.StringIndex)); + + // Browser paint walks every positioned glyph in visual order; + // source membership controls spacing, not whether the pen advances. + glyphAdvanceY += positionedAdvanceY; } } @@ -1565,182 +1237,6 @@ private static void LayoutLineVerticalMixed( penLocation.X += xLineAdvance; } - /// - /// Shapes a single font run — maps codepoints in to glyph ids using - /// , then runs GSUB substitution and GPOS positioning. Codepoints that - /// the font cannot map are recorded for a later fallback pass. - /// - /// The run-relative text slice to shape. - /// The starting grapheme index (absolute within the original input). - /// The ordered list of resolved text runs. - /// The index of the current text run; advanced as the enumerator crosses run boundaries. - /// The running codepoint index (absolute within the original input). - /// The running bidi run index. - /// - /// if this call is the fallback-font pass (in which case unmapped codepoints - /// may still emit .notdef glyphs). - /// - /// The font to shape with. - /// The resolved bidi runs covering the whole input. - /// A codepoint → bidi-run mapping accumulated across shaping passes. - /// The GSUB substitution collection to write into. - /// The GPOS positioning collection to write into. - /// - /// if every codepoint mapped successfully; if any - /// codepoint remains unmapped (so a fallback-font pass is needed). - /// - private static bool DoFontRun( - ReadOnlySpan text, - int start, - IReadOnlyList textRuns, - ref int textRunIndex, - ref int codePointIndex, - ref int bidiRunIndex, - bool isFallbackRun, - Font font, - BidiRun[] bidiRuns, - Dictionary bidiMap, - GlyphSubstitutionCollection substitutions, - GlyphPositioningCollection positionings) - { - // For each run we start with a fresh substitution collection to avoid - // overwriting the glyph ids. - substitutions.Clear(); - - // Enumerate through each grapheme in the text. - int graphemeIndex = start; - SpanGraphemeEnumerator graphemeEnumerator = new(text); - while (graphemeEnumerator.MoveNext()) - { - ReadOnlySpan grapheme = graphemeEnumerator.Current.Span; - int graphemeMax = grapheme.Length - 1; - int graphemeCodePointIndex = 0; - int charIndex = 0; - - while (textRunIndex < textRuns.Count - 1 && graphemeIndex == textRuns[textRunIndex].End) - { - textRunIndex++; - } - - // Now enumerate through each codepoint in the grapheme. - bool skipNextCodePoint = false; - SpanCodePointEnumerator codePointEnumerator = new(grapheme); - while (codePointEnumerator.MoveNext()) - { - if (codePointIndex == bidiRuns[bidiRunIndex].End) - { - bidiRunIndex++; - } - - if (skipNextCodePoint) - { - codePointIndex++; - graphemeCodePointIndex++; - continue; - } - - bidiMap[codePointIndex] = bidiRunIndex; - - int charsConsumed = 0; - CodePoint current = codePointEnumerator.Current; - charIndex += current.Utf16SequenceLength; - CodePoint? next = graphemeCodePointIndex < graphemeMax - ? CodePoint.DecodeFromUtf16At(grapheme, charIndex, out charsConsumed) - : null; - - charIndex += charsConsumed; - - // Get the glyph id for the codepoint and add to the collection. - bool hasGlyph = font.FontMetrics.TryGetGlyphId(current, next, out ushort glyphId, out skipNextCodePoint); - - // Unsupported default-ignorable code points such as FE0F should not block - // GSUB sequences like emoji ZWJ ligatures. Preserve joiners explicitly. - if (!hasGlyph && - UnicodeUtility.IsDefaultIgnorableCodePoint((uint)current.Value) && - !UnicodeUtility.ShouldRenderWhiteSpaceOnly(current) && - !CodePoint.IsZeroWidthJoiner(current) && - !CodePoint.IsZeroWidthNonJoiner(current)) - { - codePointIndex++; - graphemeCodePointIndex++; - continue; - } - - substitutions.AddGlyph(glyphId, current, (TextDirection)bidiRuns[bidiRunIndex].Direction, textRuns[textRunIndex], codePointIndex); - - codePointIndex++; - graphemeCodePointIndex++; - } - - graphemeIndex++; - } - - // Apply the simple and complex substitutions. - // TODO: Investigate HarfBuzz normalizer. - SubstituteBidiMirrors(font.FontMetrics, substitutions); - font.FontMetrics.ApplySubstitution(substitutions); - - return !isFallbackRun - ? positionings.TryAdd(font, substitutions) - : positionings.TryUpdate(font, substitutions); - } - - /// - /// Substitutes mirrored bracket glyphs (for example ()) inside right-to-left - /// bidi runs, per Unicode Bidirectional Algorithm rule L4. Relies on the font's rtlm - /// feature when available and falls back to the Unicode mirror table otherwise. - /// - /// The font metrics used to look up mirrored glyph ids. - /// The substitution collection whose glyphs will be rewritten in place. - private static void SubstituteBidiMirrors(FontMetrics fontMetrics, GlyphSubstitutionCollection collection) - { - for (int i = 0; i < collection.Count; i++) - { - GlyphShapingData data = collection[i]; - - if (data.Direction != TextDirection.RightToLeft) - { - continue; - } - - if (!CodePoint.TryGetBidiMirror(data.CodePoint, out CodePoint mirror)) - { - continue; - } - - if (fontMetrics.TryGetGlyphId(mirror, out ushort glyphId)) - { - collection.Replace(i, glyphId, KnownFeatureTags.RightToLeftMirroredForms); - } - } - - // TODO: This only replaces certain glyphs. We should investigate the specification further. - // https://www.unicode.org/reports/tr50/#vertical_alternates - if (collection.TextOptions.LayoutMode.IsHorizontal()) - { - return; - } - - for (int i = 0; i < collection.Count; i++) - { - GlyphShapingData data = collection[i]; - if (CodePoint.GetVerticalOrientationType(data.CodePoint) is VerticalOrientationType.Upright or VerticalOrientationType.TransformUpright) - { - continue; - } - - if (!CodePoint.TryGetVerticalMirror(data.CodePoint, out CodePoint mirror)) - { - continue; - } - - if (fontMetrics.TryGetGlyphId(mirror, out ushort glyphId)) - { - collection.Replace(i, glyphId, KnownFeatureTags.VerticalAlternates); - } - } - } - /// /// Calculates the X offset to apply to a single line of horizontal text so that it is positioned /// within the wrapping block according to the requested horizontal and text alignment. @@ -1756,7 +1252,7 @@ private static void SubstituteBidiMirrors(FontMetrics fontMetrics, GlyphSubstitu /// Per-line alignment within the block. /// The resolved text direction for this line. /// The X offset to add to the line's pen location. - internal static float CalculateLineOffsetX( + public static float CalculateLineOffsetX( float lineAdvance, float maxScaledAdvance, HorizontalAlignment horizontalAlignment, @@ -1820,7 +1316,7 @@ internal static float CalculateLineOffsetX( /// Per-line alignment within the block. /// The resolved text direction for this line. /// The Y offset to add to the line's pen location. - internal static float CalculateLineOffsetY( + public static float CalculateLineOffsetY( float lineAdvance, float maxScaledAdvance, VerticalAlignment verticalAlignment, diff --git a/src/SixLabors.Fonts/TextLine.cs b/src/SixLabors.Fonts/TextLine.cs index b1251edfb..932335fe2 100644 --- a/src/SixLabors.Fonts/TextLine.cs +++ b/src/SixLabors.Fonts/TextLine.cs @@ -15,7 +15,6 @@ namespace SixLabors.Fonts; internal sealed class TextLine { private readonly List data; - private readonly Dictionary advances = []; /// /// Initializes a new instance of the class with a small default capacity. @@ -132,7 +131,7 @@ public int CountGlyphLayouts() int count = 0; for (int i = 0; i < this.data.Count; i++) { - count += this.data[i].Metrics.Count; + count += this.data[i].Metrics.Length; } return count; @@ -141,7 +140,7 @@ public int CountGlyphLayouts() /// /// Appends a shaped entry to this line, updating the aggregated line-level metrics. /// - /// The glyph metrics produced by shaping this entry's codepoint. + /// The entry's contiguous slice of the line's shaped glyph storage. /// The font used to shape and render this entry. /// The point size at which the entry is rendered. /// The scaled advance contributed by this entry. @@ -161,7 +160,7 @@ public int CountGlyphLayouts() /// The line-spacing factor to apply to . /// The marker index to use if this entry becomes a selected soft-hyphen break. public void Add( - IReadOnlyList metrics, + ReadOnlyMemory metrics, Font font, float pointSize, float scaledAdvance, @@ -186,11 +185,9 @@ public void Add( // Reset metrics. // We track the maximum metrics for each line to ensure glyphs can be aligned. - if (graphemeCodePointIndex == 0) - { - // TODO: Check this logic is correct. - this.ScaledLineAdvance += scaledAdvance; - } + // Layout consumes every positioned glyph advance. Grapheme boundaries govern + // text semantics, but do not collapse multiple positioned glyph advances. + this.ScaledLineAdvance += scaledAdvance; this.ScaledMaxLineHeight = MathF.Max(this.ScaledMaxLineHeight, scaledLineHeight); this.ScaledMaxAscender = MathF.Max(this.ScaledMaxAscender, scaledAscender); @@ -201,15 +198,16 @@ public void Add( // For scripts with stacked marks (Tibetan, etc) this can be significantly // above the typographic ascender, so we cannot trust ascender alone. float scaledMinY = 0; - for (int i = 0; i < metrics.Count; i++) + ReadOnlySpan metricsSpan = metrics.Span; + for (int i = 0; i < metricsSpan.Length; i++) { - FontGlyphMetrics metric = metrics[i]; + FontGlyphMetrics metric = metricsSpan[i].Metrics; if (FontGlyphMetrics.ShouldSkipGlyphRendering(metric.CodePoint)) { continue; } - FontRectangle bbox = metric.GetBoundingBox(layoutMode, Vector2.Zero, pointSize); + FontRectangle bbox = metric.GetBoundingBox(layoutMode, Vector2.Zero, pointSize, metricsSpan[i].TextRun, metricsSpan[i].Offset, new Vector2(metricsSpan[i].AdvanceWidth, metricsSpan[i].AdvanceHeight)); scaledMinY = MathF.Min(scaledMinY, bbox.Y); } @@ -248,29 +246,32 @@ public void Add( /// /// Adds an inline placeholder entry at an existing source codepoint position without consuming source text. /// - /// The positioned placeholder glyph data. + /// The placeholder glyph metrics. + /// The shaped run carrying the placeholder's font, point size, text run, and bidi run. + /// The source codepoint index at the placeholder insertion point. /// The source grapheme index at the placeholder insertion point. /// The source UTF-16 index at the placeholder insertion point. /// when the current layout advances horizontally. /// when the current layout is vertical mixed. /// The line-spacing factor to apply to placeholder line height. public void AddPlaceholder( - GlyphPositioningCollection.GlyphPositioningData placeholder, + FontGlyphMetrics placeholderGlyph, + in ShapedTextRun run, + int offset, int graphemeIndex, int stringIndex, bool isHorizontalLayout, bool isVerticalMixedLayout, float lineSpacing) { - FontGlyphMetrics placeholderGlyph = placeholder.Metrics; bool isPlaceholderHorizontal = isHorizontalLayout || isVerticalMixedLayout; float placeholderAdvance = isPlaceholderHorizontal ? placeholderGlyph.AdvanceWidth : placeholderGlyph.AdvanceHeight; Vector2 placeholderScale = new( - placeholder.PointSize / placeholderGlyph.ScaleFactor.X, - placeholder.PointSize / placeholderGlyph.ScaleFactor.Y); + run.PointSize / placeholderGlyph.ScaleFactor.X, + run.PointSize / placeholderGlyph.ScaleFactor.Y); placeholderAdvance *= isPlaceholderHorizontal ? placeholderScale.X : placeholderScale.Y; @@ -278,7 +279,7 @@ public void AddPlaceholder( ? GlyphLayoutMode.Horizontal : GlyphLayoutMode.Vertical; - FontRectangle placeholderBox = placeholderGlyph.GetBoundingBox(placeholderMode, Vector2.Zero, placeholder.PointSize); + FontRectangle placeholderBox = placeholderGlyph.GetBoundingBox(placeholderMode, Vector2.Zero, run.PointSize, run.TextRun, Vector2.Zero, new Vector2(placeholderGlyph.AdvanceWidth, placeholderGlyph.AdvanceHeight)); IMetricsHeader metricsHeader = isPlaceholderHorizontal ? placeholderGlyph.FontMetrics.HorizontalMetrics @@ -288,7 +289,7 @@ public void AddPlaceholder( // normal ascender/descender band. Keep the run font line-box model as // the baseline contribution, then expand only the side the placeholder // actually overhangs so following lines reserve enough space. - float placeholderScaleY = placeholder.PointSize / placeholderGlyph.ScaleFactor.Y; + float placeholderScaleY = run.PointSize / placeholderGlyph.ScaleFactor.Y; float placeholderLineHeight = placeholderGlyph.UnitsPerEm * placeholderScaleY; float placeholderDelta = ((metricsHeader.LineHeight * placeholderScaleY) - placeholderLineHeight) * .5F; float placeholderAscender = (metricsHeader.Ascender * placeholderScaleY) - placeholderDelta; @@ -301,19 +302,21 @@ public void AddPlaceholder( // Placeholders share the source codepoint offset at their insertion point, // but they do not consume source grapheme, codepoint, or UTF-16 indexes. + // Generated glyphs are not part of the shaped stream, so the entry owns + // its single-glyph storage. this.Add( - new FontGlyphMetrics[] { placeholderGlyph }, - placeholder.Font, - placeholder.PointSize, + new PositionedGlyphMetrics[] { new(placeholderGlyph, placeholderGlyph.AdvanceWidth, placeholderGlyph.AdvanceHeight, Vector2.Zero, run.TextRun) }, + run.Font, + run.PointSize, placeholderAdvance, placeholderLineHeight, placeholderAscender, placeholderDescender, placeholderDelta, - placeholder.Data.BidiRun, + run.BidiRun, graphemeIndex, true, - placeholder.Offset, + offset, 0, false, false, @@ -334,43 +337,6 @@ public void InsertAt(int index, TextLine textLine) RecalculateLineMetrics(this); } - /// - /// Returns the cumulative scaled advance up to and including the glyph at the given index. - /// Whitespace entries at or after are skipped so the returned value - /// represents the advance at the last non-whitespace glyph before a potential line break. - /// - /// Results are memoized by index. - /// The zero-based index to measure up to. - /// The cumulative scaled advance. - public float MeasureAt(int index) - { - if (this.advances.TryGetValue(index, out float advance)) - { - return advance; - } - - if (index >= this.data.Count) - { - index = this.data.Count - 1; - } - - while (index >= 0 && CodePoint.IsWhiteSpace(this.data[index].CodePoint)) - { - // If the index is whitespace, we need to measure at the previous - // non-whitespace glyph to ensure we don't break too early. - index--; - } - - advance = 0; - for (int i = 0; i <= index; i++) - { - advance += this.data[i].ScaledAdvance; - } - - this.advances[index] = advance; - return advance; - } - /// /// Gets the marker advance for a selected soft-hyphen entry. /// @@ -413,7 +379,7 @@ public void ApplyEllipsisMarker( GlyphLayoutData anchor = this.data[^1]; GlyphLayoutData marker = TextLayout.CreateGeneratedMarker( - anchor.Metrics[0], + anchor.Metrics.Span[0], anchor.PointSize, anchor.BidiRun, anchor.GraphemeIndex, @@ -548,6 +514,15 @@ public bool TrySplitAt(LineBreak lineBreak, bool keepAll, [NotNullWhen(true)] ou { if (this.data[--index].CodePointIndex == lineBreak.PositionWrap) { + // One source codepoint can emit several layout entries. The + // reverse search lands on its final entry, so rewind to the first + // entry or the preceding line would retain part of the grapheme + // after its hard break. + while (index > 0 && this.data[index - 1].CodePointIndex == lineBreak.PositionWrap) + { + index--; + } + break; } } @@ -658,6 +633,7 @@ or LineBreakClass.Ambiguous /// Finalizes this line after line-breaking: trims trailing breaking whitespace when requested, /// applies bidi reordering so entries are in visual order, and recomputes aggregated metrics. /// + /// The orientation used to finalize directional run fragments. /// /// When , marks the line so becomes a no-op /// (used for paragraph-final lines). @@ -670,13 +646,14 @@ or LineBreakClass.Ambiguous /// /// This line, for fluent chaining. public TextLine Finalize( + LayoutMode layoutMode, bool skipJustification = false, bool normalizeDecomposedAdvances = false, bool preserveTrailingBreakingWhitespace = false) { this.SkipJustification = skipJustification; this.RemoveTrailingBreakingWhitespace(preserveTrailingBreakingWhitespace); - this.BidiReOrder(); + this.BidiReOrder(layoutMode); if (normalizeDecomposedAdvances) { @@ -830,87 +807,10 @@ public void Justify(TextOptions options) /// /// Re-orders the entries in this line from logical to visual order according to the - /// Unicode Bidirectional Algorithm (, rules L1 and L2). + /// Unicode Bidirectional Algorithm (, rule L2). /// - public void BidiReOrder() - { - // Build up the collection of ordered runs. - BidiRun run = this.data[0].BidiRun; - OrderedBidiRun orderedRun = new(run.Level); - OrderedBidiRun? current = orderedRun; - for (int i = 0; i < this.data.Count; i++) - { - GlyphLayoutData g = this.data[i]; - if (run != g.BidiRun) - { - run = g.BidiRun; - current.Next = new(run.Level); - current = current.Next; - } - - current.Add(g); - } - - // Reorder them into visual order. - orderedRun = LinearReOrder(orderedRun); - - // Now perform a recursive reversal of each run. - // From the highest level found in the text to the lowest odd level on each line, including intermediate levels - // not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher. - // https://unicode.org/reports/tr9/#L2 - int max = 0; - int min = int.MaxValue; - for (int i = 0; i < this.data.Count; i++) - { - int level = this.data[i].BidiRun.Level; - if (level > max) - { - max = level; - } - - if ((level & 1) != 0 && level < min) - { - min = level; - } - } - - if (min > max) - { - min = max; - } - - if (max == 0 || (min == max && (max & 1) == 0)) - { - // Nothing to reverse. - return; - } - - // Now apply the reversal and replace the original contents. - int minLevelToReverse = max; - while (minLevelToReverse >= min) - { - current = orderedRun; - while (current != null) - { - if (current.Level >= minLevelToReverse) - { - current.Reverse(); - } - - current = current.Next; - } - - minLevelToReverse--; - } - - this.data.Clear(); - current = orderedRun; - while (current != null) - { - this.data.AddRange(current.AsSlice()); - current = current.Next; - } - } + /// The orientation used to finalize directional run fragments. + public void BidiReOrder(LayoutMode layoutMode) => BidiReordering.Reorder(this.data, layoutMode); /// /// Recomputes the aggregated per-line metrics (advance, max line height, ascender, @@ -944,152 +844,5 @@ private static void RecalculateLineMetrics(TextLine textLine) textLine.ScaledMaxDelta = delta; textLine.ScaledMaxLineHeight = lineHeight; textLine.ScaledMinY = minY; - - textLine.advances.Clear(); - } - - /// - /// Reorders a series of runs from logical to visual order, returning the left most run. - /// - /// - /// The ordered bidi run. - /// The . - private static OrderedBidiRun LinearReOrder(OrderedBidiRun? line) - { - BidiRange? range = null; - OrderedBidiRun? run = line; - - while (run != null) - { - OrderedBidiRun? next = run.Next; - - while (range != null && range.Level > run.Level - && range.Previous != null && range.Previous.Level >= run.Level) - { - range = BidiRange.MergeWithPrevious(range); - } - - if (range != null && range.Level >= run.Level) - { - // Attach run to the range. - if ((run.Level & 1) != 0) - { - // Odd, range goes to the right of run. - run.Next = range.Left; - range.Left = run; - } - else - { - // Even, range goes to the left of run. - range.Right!.Next = run; - range.Right = run; - } - - range.Level = run.Level; - } - else - { - BidiRange r = new(); - r.Left = r.Right = run; - r.Level = run.Level; - r.Previous = range; - range = r; - } - - run = next; - } - - while (range?.Previous != null) - { - range = BidiRange.MergeWithPrevious(range); - } - - // Terminate. - range!.Right!.Next = null; - return range!.Left!; - } - - /// - /// A node in the linked list of contiguous same-level bidi runs used by . - /// Each node owns the glyph entries at its bidi embedding level and can be reversed in place. - /// - private sealed class OrderedBidiRun - { - private ArrayBuilder info; - - /// - /// Initializes a new instance of the class. - /// - /// The bidi embedding level for this run. - public OrderedBidiRun(int level) => this.Level = level; - - /// Gets the bidi embedding level of this run. - public int Level { get; } - - /// Gets or sets the next run in visual order. - public OrderedBidiRun? Next { get; set; } - - /// Appends an entry to this run. - /// The entry to append. - public void Add(GlyphLayoutData info) => this.info.Add(info); - - /// Returns a slice view over this run's entries. - /// A slice over the entries. - public ArraySlice AsSlice() => this.info.AsSlice(); - - /// Reverses the entries in this run in place (for rule L2). - public void Reverse() => this.AsSlice().Span.Reverse(); - } - - /// - /// An intermediate grouping of links used by the linear-reorder - /// algorithm to stitch pairs of same-level ranges together. - /// - private sealed class BidiRange - { - /// Gets or sets the shared bidi embedding level for this range. - public int Level { get; set; } - - /// Gets or sets the leftmost run in the range. - public OrderedBidiRun? Left { get; set; } - - /// Gets or sets the rightmost run in the range. - public OrderedBidiRun? Right { get; set; } - - /// Gets or sets the previous range in the processing stack. - public BidiRange? Previous { get; set; } - - /// - /// Stitches the current range with its predecessor, producing a single merged range - /// whose internal orientation depends on the predecessor's embedding level parity. - /// - /// The current range whose will be merged. - /// The merged range (always the predecessor instance, reused in place). - public static BidiRange MergeWithPrevious(BidiRange? range) - { - BidiRange previous = range!.Previous!; - BidiRange left; - BidiRange right; - - if ((previous.Level & 1) != 0) - { - // Odd, previous goes to the right of range. - left = range; - right = previous; - } - else - { - // Even, previous goes to the left of range. - left = previous; - right = range; - } - - // Stitch them - left.Right!.Next = right.Left; - previous.Left = left.Left; - previous.Right = right.Right; - - return previous; - } } } diff --git a/src/SixLabors.Fonts/TextLineBreakEnumerator.cs b/src/SixLabors.Fonts/TextLineBreakEnumerator.cs index 0e183b8e6..ce69aca66 100644 --- a/src/SixLabors.Fonts/TextLineBreakEnumerator.cs +++ b/src/SixLabors.Fonts/TextLineBreakEnumerator.cs @@ -18,7 +18,10 @@ internal sealed class TextLineBreakEnumerator private readonly bool normalizeDecomposedAdvances; private readonly int maxLines; private readonly CodePoint? ellipsisMarkerCodePoint; - private readonly List lineBreaks; + private readonly bool includeHyphenationBreaks; + private LineBreakEnumerator.State breakCursorState; + private LineBreak bufferedBreak; + private bool hasBufferedBreak; private TextLine textLine; private int processed; private int lineCount; @@ -39,7 +42,8 @@ public TextLineBreakEnumerator(in LogicalTextLine logicalLine, TextOptions optio this.normalizeDecomposedAdvances = options.LayoutMode.IsVertical(); this.maxLines = options.MaxLines; this.ellipsisMarkerCodePoint = TextLayout.GetEllipsisMarkerCodePoint(options); - this.lineBreaks = logicalLine.LineBreaks; + this.includeHyphenationBreaks = TextLayout.GetHyphenationMarkerCodePoint(options).HasValue; + this.ReseedBreakCursor(); // The breaker mutates the remaining line as it advances, so each cursor owns // a clone of the immutable prepared line held by TextBlock. @@ -72,43 +76,72 @@ public bool MoveNext(float wrappingLength) { LineBreak? bestBreak = null; - // Index rather than enumerate: the interface-typed break list would allocate a heap - // enumerator for every produced line. - for (int i = 0; i < this.lineBreaks.Count; i++) + // Browsers fill each line by pulling the next break opportunity from a + // lazy cursor and measuring the line incrementally as it grows, holding + // O(1) state instead of a materialized candidate list. The entry walk + // below carries a running advance and the advance through the most + // recent non-whitespace entry, so each candidate's measurement excludes + // trailing whitespace without re-summing from the line start. + int entryIndex = 0; + float runningAdvance = 0; + float measuredAdvance = 0; + + while (this.TryPeekBreak(out LineBreak lineBreak)) { - LineBreak lineBreak = this.lineBreaks[i]; - - // Skip breaks that are already behind the processed portion. + // Candidates at or behind the processed boundary belong to lines + // already produced (or entries consumed by width splits); discard. if (lineBreak.PositionWrap <= this.processed) { + this.ConsumeBreak(); continue; } - // Measure the text up to the adjusted break point. - int measureIndex = lineBreak.PositionMeasure - this.processed; - float advance = this.textLine.MeasureAt(measureIndex); + // Advance the incremental measurement to the candidate's measure + // position, clamped to the remaining line exactly as the previous + // whole-line measurement clamped its index. + int measureTarget = Math.Min(lineBreak.PositionMeasure - this.processed, this.textLine.Count - 1); + while (entryIndex <= measureTarget) + { + GlyphLayoutData entry = this.textLine[entryIndex]; + runningAdvance += entry.ScaledAdvance; + if (!CodePoint.IsWhiteSpace(entry.CodePoint)) + { + measuredAdvance = runningAdvance; + } + + entryIndex++; + } + + float advance = measuredAdvance; if (lineBreak.IsHyphenationBreak) { advance += this.textLine.GetHyphenationMarkerAdvance( - measureIndex - 1, + lineBreak.PositionMeasure - this.processed - 1, this.logicalLine.HyphenationMarkers); } if (advance >= scaledWrappingLength) { - bestBreak ??= lineBreak; + // The overflowing candidate is taken only when nothing earlier + // fit; otherwise it stays buffered as the next line's first + // candidate, replacing the previous full-list rescan. + if (bestBreak is null) + { + bestBreak = lineBreak; + this.ConsumeBreak(); + } + break; } + this.ConsumeBreak(); + bestBreak = lineBreak; + // If it's a mandatory break, stop immediately. if (lineBreak.Required) { - bestBreak = lineBreak; break; } - - // Update the best break. - bestBreak = lineBreak; } if (bestBreak != null) @@ -127,6 +160,79 @@ public bool MoveNext(float wrappingLength) return false; } + /// + /// Exposes the next undiscarded break opportunity without consuming it, pulling + /// from the streaming cursor into the single-candidate buffer when empty. + /// + /// The next break opportunity. + /// while opportunities remain. + private bool TryPeekBreak(out LineBreak lineBreak) + { + if (!this.hasBufferedBreak) + { + if (!this.TryPullBreak(out this.bufferedBreak)) + { + lineBreak = default; + return false; + } + + this.hasBufferedBreak = true; + } + + lineBreak = this.bufferedBreak; + return true; + } + + /// + /// Consumes the buffered break opportunity so the next peek pulls a fresh one. + /// + private void ConsumeBreak() => this.hasBufferedBreak = false; + + /// + /// Pulls the next layout-relevant break opportunity from the retained source + /// text, resuming the streaming UAX #14 enumeration from its captured state. + /// + /// + /// The enumerator is a ref struct, so this class persists its value state + /// between pulls and reconstitutes it over the retained text per candidate. + /// URL solidus tailoring is layout policy and is enabled only here, keeping the + /// enumerator's public constructor Unicode-conformant; soft-hyphen candidates + /// participate only when hyphenation is configured. + /// + /// The next break opportunity. + /// while opportunities remain. + private bool TryPullBreak(out LineBreak lineBreak) + { + LineBreakEnumerator cursor = new(this.logicalLine.SourceText, true, in this.breakCursorState); + while (cursor.MoveNext()) + { + if (cursor.Current.IsHyphenationBreak && !this.includeHyphenationBreaks) + { + continue; + } + + this.breakCursorState = cursor.CaptureState(); + lineBreak = cursor.Current; + return true; + } + + this.breakCursorState = cursor.CaptureState(); + lineBreak = default; + return false; + } + + /// + /// Restarts the streaming break cursor from the start of the retained text. + /// The processed boundary discards the dead prefix on the next scan, which is + /// how browsers requery their break iterator after rewinding an overflowed line. + /// + private void ReseedBreakCursor() + { + LineBreakEnumerator cursor = new(this.logicalLine.SourceText, tailorUrls: true); + this.breakCursorState = cursor.CaptureState(); + this.hasBufferedBreak = false; + } + /// /// Breaks the current remaining line at the supplied break opportunity. /// @@ -176,8 +282,14 @@ private bool BreakAt(LineBreak breakAt, float scaledWrappingLength) this.textLine.TrySplitAt(scaledWrappingLength, out TextLine? overflow)) { // Reinsert the overflow at the beginning of the remaining line. + // Rewinding the processed boundary reinstates source positions + // whose break candidates the cursor has already consumed, so + // the stream restarts and the dead prefix is discarded on the + // next scan. Overflow rewinds are rare, so the restart cost + // never dominates the fill. this.processed -= overflow.Count; remaining.InsertAt(0, overflow); + this.ReseedBreakCursor(); } } @@ -329,6 +441,7 @@ private bool SetCurrent( // Paragraph layout trims trailing breaking whitespace. Editor interaction keeps // ordinary trailing whitespace addressable so typed spaces can advance the caret. this.current = line.Finalize( + this.options.LayoutMode, skipJustification, this.normalizeDecomposedAdvances, preserveTrailingBreakingWhitespace); diff --git a/src/SixLabors.Fonts/TextMeasurer.cs b/src/SixLabors.Fonts/TextMeasurer.cs index ec0fb8bc5..7fdb050dd 100644 --- a/src/SixLabors.Fonts/TextMeasurer.cs +++ b/src/SixLabors.Fonts/TextMeasurer.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.Fonts.Rendering; namespace SixLabors.Fonts; @@ -173,21 +174,37 @@ public static FontRectangle MeasureRenderableBounds(ushort glyphId, GlyphOptions /// /// /// Matching the text-level advance contract, the rectangle is zero-based: the extent the - /// run's advance cells cover at their run origins, reported independent of position and of + /// glyph advance cells cover at their points, reported independent of position and of /// . Glyph ids the font does not contain and glyphs /// that never render are skipped, matching renderer behavior. /// - /// The positioned glyphs. + /// The glyph identifiers. + /// The absolute glyph origins in pixel units. /// The glyph options, including the font and layout mode. /// - /// The zero-based logical advance extent of the run if it was to be rendered, or - /// when no glyph in the run participates in rendering. + /// The zero-based logical advance extent of the glyphs if they were to be rendered, or + /// when no glyph participates in rendering. /// - public static FontRectangle MeasureAdvance(GlyphRun glyphRun, GlyphOptions options) + public static FontRectangle MeasureAdvance(ReadOnlySpan glyphIds, ReadOnlySpan points, GlyphOptions options) { // Match the text-level advance contract: measure the extent the positioned cells // cover, then report it zero-based. - FontRectangle extent = MeasureGlyphRun(glyphRun, options, static (metrics, options) => GetAbsoluteAdvance(metrics, options)); + FontRectangle extent = MeasureGlyphs(glyphIds, points, options, static (metrics, options) => GetAbsoluteAdvance(metrics, options)); + return new FontRectangle(0, 0, extent.Width, extent.Height); + } + + /// + /// Measures the logical advance of shaped glyphs in pixel units. + /// + /// The buffer containing the shaped glyphs. + /// The glyph options supplying the shaping font, resolution, and baseline origin. + /// + /// The zero-based logical advance extent of the glyphs if they were to be rendered, or + /// when no glyph participates in rendering. + /// + public static FontRectangle MeasureAdvance(TextShapingBuffer buffer, GlyphOptions options) + { + FontRectangle extent = MeasureGlyphs(buffer, options, static (metrics, options) => GetAbsoluteAdvance(metrics, options)); return new FontRectangle(0, 0, extent.Width, extent.Height); } @@ -195,44 +212,59 @@ public static FontRectangle MeasureAdvance(GlyphRun glyphRun, GlyphOptions optio /// Measures the union of rendered glyph bounds for positioned glyphs in pixel units. /// /// - /// Each glyph is measured at its own run origin exactly as - /// + /// Each glyph is measured at its supplied point exactly as + /// /// renders it; is replaced per glyph and restored. The - /// result matches the union of the bounding boxes the renderer reports for the same run and + /// result matches the union of the bounding boxes the renderer reports for the same glyphs and /// options. Glyph ids the font does not contain and glyphs that never render are skipped, /// matching renderer behavior. /// - /// The positioned glyphs. + /// The glyph identifiers. + /// The absolute glyph origins in pixel units. /// The glyph options, including the font and layout mode. /// - /// The union of the rendered glyph bounds of the run if it was to be rendered, or - /// when no glyph in the run participates in rendering. + /// The union of the rendered glyph bounds if they were to be rendered, or + /// when no glyph participates in rendering. /// - public static FontRectangle MeasureBounds(GlyphRun glyphRun, GlyphOptions options) - => MeasureGlyphRun(glyphRun, options, static (metrics, options) => GetGlyphBounds(metrics, options)); + public static FontRectangle MeasureBounds(ReadOnlySpan glyphIds, ReadOnlySpan points, GlyphOptions options) + => MeasureGlyphs(glyphIds, points, options, static (metrics, options) => GetGlyphBounds(metrics, options)); + + /// + /// Measures the union of rendered bounds for shaped glyphs in pixel units. + /// + /// The buffer containing the shaped glyphs. + /// The glyph options supplying the shaping font, resolution, and baseline origin. + /// + /// The union of the rendered glyph bounds if they were to be rendered, or + /// when no glyph participates in rendering. + /// + public static FontRectangle MeasureBounds(TextShapingBuffer buffer, GlyphOptions options) + => MeasureGlyphs(buffer, options, static (metrics, options) => GetGlyphBounds(metrics, options)); /// /// Measures the full renderable bounds of positioned glyphs in pixel units. /// - /// - /// Each glyph is measured at its own run origin exactly as - /// - /// renders it; is replaced per glyph and restored. Glyph ids - /// the font does not contain and glyphs that never render are skipped, matching renderer - /// behavior. - /// - /// The positioned glyphs. + /// The glyph identifiers. + /// The absolute glyph origins in pixel units. /// The glyph options, including the font and layout mode. /// - /// The union of the advances placed at their run origins and the rendered glyph bounds of - /// the run if it was to be rendered, or when no glyph in - /// the run participates in rendering. + /// The union of the advances placed at the supplied points and the rendered glyph bounds, + /// or when no glyph participates in rendering. + /// + public static FontRectangle MeasureRenderableBounds(ReadOnlySpan glyphIds, ReadOnlySpan points, GlyphOptions options) + => MeasureGlyphs(glyphIds, points, options, static (metrics, options) => FontRectangle.Union(GetAbsoluteAdvance(metrics, options), GetGlyphBounds(metrics, options))); + + /// + /// Measures the full renderable bounds of shaped glyphs in pixel units. + /// + /// The buffer containing the shaped glyphs. + /// The glyph options supplying the shaping font, resolution, and baseline origin. + /// + /// The union of the positioned advances and rendered glyph bounds, or + /// when no glyph participates in rendering. /// - public static FontRectangle MeasureRenderableBounds(GlyphRun glyphRun, GlyphOptions options) - => MeasureGlyphRun( - glyphRun, - options, - static (metrics, options) => FontRectangle.Union(GetAbsoluteAdvance(metrics, options), GetGlyphBounds(metrics, options))); + public static FontRectangle MeasureRenderableBounds(TextShapingBuffer buffer, GlyphOptions options) + => MeasureGlyphs(buffer, options, static (metrics, options) => FontRectangle.Union(GetAbsoluteAdvance(metrics, options), GetGlyphBounds(metrics, options))); /// public static ReadOnlyMemory GetGlyphMetrics(string text, TextOptions options) @@ -278,34 +310,33 @@ public static GlyphMetrics GetGlyphMetrics(ushort glyphId, GlyphOptions options) } /// - /// Gets the positioned metrics of each glyph in a positioned run in pixel units. + /// Gets the positioned metrics of glyphs at supplied points in pixel units. /// /// /// The metrics are computed directly from the font's cached per-glyph metrics without - /// decoding outlines or running the layout engine. Each glyph is measured at its own run - /// origin exactly as - /// + /// decoding outlines or running the layout engine. Each glyph is measured at its supplied + /// point exactly as + /// /// renders it; is replaced per glyph and restored. One /// entry is returned per input glyph so results correlate with run indices: each entry's /// grapheme index is plus the run index and its /// string index is the run index. Glyph ids the font does not contain, and glyphs that /// never render, produce entries with empty rectangles. /// - /// The positioned glyphs. + /// The glyph identifiers. + /// The absolute glyph origins in pixel units. /// The glyph options, including the font and layout mode. /// A read-only memory region containing one positioned metrics entry per input glyph. - public static ReadOnlyMemory GetGlyphMetrics(GlyphRun glyphRun, GlyphOptions options) + public static ReadOnlyMemory GetGlyphMetrics(ReadOnlySpan glyphIds, ReadOnlySpan points, GlyphOptions options) { - Guard.NotNull(glyphRun, nameof(glyphRun)); Guard.NotNull(options, nameof(options)); + Guard.IsTrue(glyphIds.Length == points.Length, nameof(points), "Glyph id and point counts must match."); - if (glyphRun.Count == 0) + if (glyphIds.IsEmpty) { return ReadOnlyMemory.Empty; } - ReadOnlySpan glyphIds = glyphRun.GlyphIds.Span; - ReadOnlySpan origins = glyphRun.Origins.Span; Vector2 originalOrigin = options.Origin; int originalGraphemeIndex = options.GraphemeIndex; @@ -314,7 +345,7 @@ public static ReadOnlyMemory GetGlyphMetrics(GlyphRun glyphRun, Gl { for (int i = 0; i < glyphIds.Length; i++) { - options.Origin = origins[i]; + options.Origin = points[i]; metrics[i] = CreateGlyphMetrics(glyphIds[i], options, originalGraphemeIndex + i, i); } } @@ -326,6 +357,103 @@ public static ReadOnlyMemory GetGlyphMetrics(GlyphRun glyphRun, Gl return metrics; } + /// + /// Gets the positioned metrics of shaped glyphs in pixel units. + /// + /// The buffer containing the shaped glyphs. + /// The glyph options supplying the shaping font, resolution, and baseline origin. + /// A read-only memory region containing one positioned metrics entry per shaped glyph. + public static ReadOnlyMemory GetGlyphMetrics(TextShapingBuffer buffer, GlyphOptions options) + { + Guard.NotNull(buffer, nameof(buffer)); + Guard.NotNull(options, nameof(options)); + + ReadOnlySpan glyphs = buffer.Glyphs; + ReadOnlySpan lineEnds = buffer.LineEnds; + if (glyphs.IsEmpty) + { + return ReadOnlyMemory.Empty; + } + + Vector2 baselineOrigin = options.Origin; + int originalGraphemeIndex = options.GraphemeIndex; + + // Shaped values are already Font.Size-scaled points. Measurement owns the + // single points-to-device-pixels conversion. + float scale = options.Dpi / 72F; + + GlyphMetrics[] metrics = new GlyphMetrics[glyphs.Length]; + try + { + if (lineEnds.IsEmpty) + { + float penX = 0; + float penY = 0; + for (int i = 0; i < glyphs.Length; i++) + { + ShapedGlyph glyph = glyphs[i]; + + // Shaping offsets and advances remain Y-up until the measurement + // boundary converts them to DPI-scaled output coordinates. + options.Origin = new Vector2( + baselineOrigin.X + ((penX + glyph.Offset.X) * scale), + baselineOrigin.Y - ((penY + glyph.Offset.Y) * scale)); + + // GraphemeIndex identifies the shaped cluster; StringIndex is + // the UTF-16 source boundary. Keep both distinct in the result. + int graphemeIndex = originalGraphemeIndex + glyph.GraphemeIndex; + metrics[i] = CreateGlyphMetrics(glyph.GlyphId, options, graphemeIndex, glyph.StringIndex); + + penX += glyph.AdvanceWidth; + penY += glyph.AdvanceHeight; + } + } + else + { + FontMetrics fontMetrics = options.Font.FontMetrics; + + // Shaping records hard-line glyph boundaries but deliberately does + // not choose a device-space baseline progression. + float lineAdvance = fontMetrics.HorizontalMetrics.LineHeight * options.Font.Size * options.Dpi / fontMetrics.ScaleFactor; + float baselineY = baselineOrigin.Y; + int glyphStart = 0; + for (int lineIndex = 0; lineIndex <= lineEnds.Length; lineIndex++) + { + int glyphEnd = lineIndex < lineEnds.Length ? lineEnds[lineIndex] : glyphs.Length; + float penX = 0; + float penY = 0; + for (int i = glyphStart; i < glyphEnd; i++) + { + ShapedGlyph glyph = glyphs[i]; + + // Shaping offsets and advances remain Y-up until the measurement + // boundary converts them to DPI-scaled output coordinates. + options.Origin = new Vector2( + baselineOrigin.X + ((penX + glyph.Offset.X) * scale), + baselineY - ((penY + glyph.Offset.Y) * scale)); + + int graphemeIndex = originalGraphemeIndex + glyph.GraphemeIndex; + metrics[i] = CreateGlyphMetrics(glyph.GlyphId, options, graphemeIndex, glyph.StringIndex); + + penX += glyph.AdvanceWidth; + penY += glyph.AdvanceHeight; + } + + glyphStart = glyphEnd; + baselineY += lineAdvance; + } + } + } + finally + { + // Measurement temporarily reuses the caller's options to avoid a + // per-glyph allocation; leave the object exactly as it arrived. + options.Origin = baselineOrigin; + } + + return metrics; + } + /// public static ReadOnlyMemory GetIntersections(string text, TextOptions options, float lowerLimit, float upperLimit) => GetIntersections(text.AsSpan(), options, lowerLimit, upperLimit); @@ -393,10 +521,11 @@ public static ReadOnlyMemory GetIntersections(ushort glyphId, GlyphOption /// The intervals are computed from the exact outline geometry the renderer would draw /// (including hinting), each glyph at its own run origin, so text decorations can be broken /// precisely around descenders. Glyphs whose bounds do not touch the band skip outline - /// decoding entirely. The band and the returned x-values share the run origins' coordinate + /// decoding entirely. The band and the returned x-values share the points' coordinate /// space. /// - /// The positioned glyphs. + /// The glyph identifiers. + /// The absolute glyph origins in pixel units. /// The glyph options, including the font and layout mode. /// One edge of the horizontal band. /// The other edge of the horizontal band. @@ -404,13 +533,34 @@ public static ReadOnlyMemory GetIntersections(ushort glyphId, GlyphOption /// A read-only memory region containing merged, x-sorted interval pairs /// (start, end, start, end, ...); empty when no outline crosses the band. /// - public static ReadOnlyMemory GetIntersections(GlyphRun glyphRun, GlyphOptions options, float lowerLimit, float upperLimit) + public static ReadOnlyMemory GetIntersections(ReadOnlySpan glyphIds, ReadOnlySpan points, GlyphOptions options, float lowerLimit, float upperLimit) { - Guard.NotNull(glyphRun, nameof(glyphRun)); Guard.NotNull(options, nameof(options)); GlyphIntersectionCollector collector = new(lowerLimit, upperLimit); - TextRenderer.RenderTo(collector, glyphRun, options); + TextRenderer.RenderTo(collector, glyphIds, points, options); + return collector.BuildIntersections(); + } + + /// + /// Gets the x-axis intervals where shaped glyph outlines cross a horizontal band, + /// in pixel units. + /// + /// The buffer containing the shaped glyphs. + /// The glyph options supplying the shaping font, resolution, and baseline origin. + /// One edge of the horizontal band. + /// The other edge of the horizontal band. + /// + /// A read-only memory region containing merged, x-sorted interval pairs + /// (start, end, start, end, ...); empty when no outline crosses the band. + /// + public static ReadOnlyMemory GetIntersections(TextShapingBuffer buffer, GlyphOptions options, float lowerLimit, float upperLimit) + { + Guard.NotNull(buffer, nameof(buffer)); + Guard.NotNull(options, nameof(options)); + + GlyphIntersectionCollector collector = new(lowerLimit, upperLimit); + TextRenderer.RenderTo(collector, buffer, options); return collector.BuildIntersections(); } @@ -501,24 +651,20 @@ public static ReadOnlyMemory GetLineMetrics(ReadOnlySpan text } /// - /// Measures each positioned glyph of a run at its own origin and unions the results. - /// Mirrors : + /// Measures positioned glyphs at their supplied points and unions the results. + /// Mirrors : /// is replaced per glyph and restored afterwards. /// - /// The positioned glyphs. + /// The glyph identifiers. + /// The absolute glyph origins in pixel units. /// The glyph options, including the font and layout mode. /// The per-glyph measurement to union. /// The union of the per-glyph measurements, or . - private static FontRectangle MeasureGlyphRun( - GlyphRun glyphRun, - GlyphOptions options, - Func measure) + private static FontRectangle MeasureGlyphs(ReadOnlySpan glyphIds, ReadOnlySpan points, GlyphOptions options, Func measure) { - Guard.NotNull(glyphRun, nameof(glyphRun)); Guard.NotNull(options, nameof(options)); + Guard.IsTrue(glyphIds.Length == points.Length, nameof(points), "Glyph id and point counts must match."); - ReadOnlySpan glyphIds = glyphRun.GlyphIds.Span; - ReadOnlySpan origins = glyphRun.Origins.Span; Vector2 originalOrigin = options.Origin; FontRectangle bounds = default; @@ -532,7 +678,7 @@ private static FontRectangle MeasureGlyphRun( continue; } - options.Origin = origins[i]; + options.Origin = points[i]; FontRectangle glyphBounds = measure(metrics, options); bounds = hasBounds ? FontRectangle.Union(bounds, glyphBounds) : glyphBounds; hasBounds = true; @@ -546,6 +692,127 @@ private static FontRectangle MeasureGlyphRun( return hasBounds ? bounds : FontRectangle.Empty; } + /// + /// Measures shaped glyphs at their DPI-scaled output positions and unions the results. + /// + /// The buffer containing the shaped glyphs. + /// The glyph options supplying the shaping font, resolution, and baseline origin. + /// The per-glyph measurement to union. + /// The union of the per-glyph measurements, or . + private static FontRectangle MeasureGlyphs(TextShapingBuffer buffer, GlyphOptions options, Func measure) + { + Guard.NotNull(buffer, nameof(buffer)); + Guard.NotNull(options, nameof(options)); + + ReadOnlySpan glyphs = buffer.Glyphs; + ReadOnlySpan lineEnds = buffer.LineEnds; + + // Keep multiline state out of the common run path. Combining both loops forces + // the JIT to spill the multiline loop's additional SIMD state on every call. + if (!lineEnds.IsEmpty) + { + return MeasureMultilineGlyphs(glyphs, lineEnds, options, measure); + } + + Vector2 baselineOrigin = options.Origin; + float scale = options.Dpi / 72F; + + FontRectangle bounds = default; + bool hasBounds = false; + try + { + float penX = 0; + float penY = 0; + for (int i = 0; i < glyphs.Length; i++) + { + ShapedGlyph glyph = glyphs[i]; + + // Shaping positions stay in Font.Size-scaled Y-up space until this + // measurement boundary converts them to output coordinates. + options.Origin = new Vector2( + baselineOrigin.X + ((penX + glyph.Offset.X) * scale), + baselineOrigin.Y - ((penY + glyph.Offset.Y) * scale)); + + if (TryGetMeasurableGlyphMetrics(glyph.GlyphId, options, out FontGlyphMetrics? metrics)) + { + FontRectangle glyphBounds = measure(metrics, options); + bounds = hasBounds ? FontRectangle.Union(bounds, glyphBounds) : glyphBounds; + hasBounds = true; + } + + penX += glyph.AdvanceWidth; + penY += glyph.AdvanceHeight; + } + } + finally + { + options.Origin = baselineOrigin; + } + + return hasBounds ? bounds : FontRectangle.Empty; + } + + /// + /// Measures shaped glyphs across hard-delimited lines without adding multiline + /// state to the common single-run measurement path. + /// + /// The shaped glyphs in visual line order. + /// The exclusive glyph-end index of each hard-delimited line. + /// The glyph options supplying the shaping font, resolution, and baseline origin. + /// The per-glyph measurement to union. + /// The union of the per-glyph measurements, or . + [MethodImpl(MethodImplOptions.NoInlining)] + private static FontRectangle MeasureMultilineGlyphs(ReadOnlySpan glyphs, ReadOnlySpan lineEnds, GlyphOptions options, Func measure) + { + Vector2 baselineOrigin = options.Origin; + float scale = options.Dpi / 72F; + FontMetrics fontMetrics = options.Font.FontMetrics; + float lineAdvance = fontMetrics.HorizontalMetrics.LineHeight * options.Font.Size * options.Dpi / fontMetrics.ScaleFactor; + float baselineY = baselineOrigin.Y; + int glyphStart = 0; + + FontRectangle bounds = default; + bool hasBounds = false; + try + { + for (int lineIndex = 0; lineIndex <= lineEnds.Length; lineIndex++) + { + int glyphEnd = lineIndex < lineEnds.Length ? lineEnds[lineIndex] : glyphs.Length; + float penX = 0; + float penY = 0; + for (int i = glyphStart; i < glyphEnd; i++) + { + ShapedGlyph glyph = glyphs[i]; + + // Shaping positions stay in Font.Size-scaled Y-up space until this + // measurement boundary converts them to output coordinates. + options.Origin = new Vector2( + baselineOrigin.X + ((penX + glyph.Offset.X) * scale), + baselineY - ((penY + glyph.Offset.Y) * scale)); + + if (TryGetMeasurableGlyphMetrics(glyph.GlyphId, options, out FontGlyphMetrics? metrics)) + { + FontRectangle glyphBounds = measure(metrics, options); + bounds = hasBounds ? FontRectangle.Union(bounds, glyphBounds) : glyphBounds; + hasBounds = true; + } + + penX += glyph.AdvanceWidth; + penY += glyph.AdvanceHeight; + } + + glyphStart = glyphEnd; + baselineY += lineAdvance; + } + } + finally + { + options.Origin = baselineOrigin; + } + + return hasBounds ? bounds : FontRectangle.Empty; + } + /// /// Resolves the cached per-glyph metrics that participate in rendering, mirroring the /// metric selection and skip rules the renderer applies so measurement and rendering @@ -590,8 +857,12 @@ private static GlyphMetrics CreateGlyphMetrics(ushort glyphId, GlyphOptions opti { if (!TryGetMeasurableGlyphMetrics(glyphId, options, out FontGlyphMetrics? metrics)) { + // Keep the requested id even when the font cannot resolve it. Run + // overloads promise one index-correlated entry per input glyph, so the + // empty geometry must not discard the identity of that entry. return new GlyphMetrics( default, + glyphId, FontRectangle.Empty, FontRectangle.Empty, FontRectangle.Empty, @@ -604,6 +875,7 @@ private static GlyphMetrics CreateGlyphMetrics(ushort glyphId, GlyphOptions opti FontRectangle bounds = GetGlyphBounds(metrics, options); return new GlyphMetrics( metrics.CodePoint, + glyphId, advance, bounds, FontRectangle.Union(GetAbsoluteAdvance(metrics, options), bounds), @@ -651,7 +923,10 @@ private static FontRectangle GetGlyphBounds(FontGlyphMetrics metrics, GlyphOptio return metrics.GetBoundingBox( layoutMode, GetAnchoredOrigin(options, layoutMode), - metrics.GetScaledSize(options.Font.Size, options.Dpi)); + metrics.GetScaledSize(options.Font.Size, options.Dpi), + null, + Vector2.Zero, + new Vector2(metrics.AdvanceWidth, metrics.AdvanceHeight)); } /// @@ -668,17 +943,15 @@ private static FontRectangle GetGlyphAdvance(FontGlyphMetrics metrics, GlyphOpti Vector2 scale = new(scaledSize / metrics.ScaleFactor.X, scaledSize / metrics.ScaleFactor.Y); float emHeight = metrics.UnitsPerEm * scale.Y; - switch (options.GetGlyphLayoutMode(metrics.CodePoint)) + return options.GetGlyphLayoutMode(metrics.CodePoint) switch { - case GlyphLayoutMode.Vertical: - return new FontRectangle(0, 0, metrics.AdvanceWidth * scale.X, metrics.AdvanceHeight * scale.Y); - case GlyphLayoutMode.VerticalRotated: - // A rotated glyph advances along the column by its horizontal advance and its - // line box lies across the column. - return new FontRectangle(0, 0, emHeight, metrics.AdvanceWidth * scale.X); - default: - return new FontRectangle(0, 0, metrics.AdvanceWidth * scale.X, emHeight); - } + GlyphLayoutMode.Vertical => new FontRectangle(0, 0, metrics.AdvanceWidth * scale.X, metrics.AdvanceHeight * scale.Y), + + // A rotated glyph advances along the column by its horizontal advance and its + // line box lies across the column. + GlyphLayoutMode.VerticalRotated => new FontRectangle(0, 0, emHeight, metrics.AdvanceWidth * scale.X), + _ => new FontRectangle(0, 0, metrics.AdvanceWidth * scale.X, emHeight), + }; } /// diff --git a/src/SixLabors.Fonts/TextOptions.cs b/src/SixLabors.Fonts/TextOptions.cs index fffd4d8a3..475631613 100644 --- a/src/SixLabors.Fonts/TextOptions.cs +++ b/src/SixLabors.Fonts/TextOptions.cs @@ -1,6 +1,7 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Globalization; using System.Numerics; using SixLabors.Fonts.Tables.AdvancedTypographic; using SixLabors.Fonts.Unicode; @@ -49,6 +50,7 @@ public TextOptions(TextOptions options) this.CustomHyphen = options.CustomHyphen; this.TextDirection = options.TextDirection; this.TextBidiMode = options.TextBidiMode; + this.Script = options.Script; this.TextInteractionMode = options.TextInteractionMode; this.TextAlignment = options.TextAlignment; this.TextJustification = options.TextJustification; @@ -59,6 +61,7 @@ public TextOptions(TextOptions options) this.Tracking = options.Tracking; this.ColorFontSupport = options.ColorFontSupport; this.FeatureTags = new List(options.FeatureTags); + this.Culture = options.Culture; this.TextRuns = new List(options.TextRuns); this.DecorationPositioningMode = options.DecorationPositioningMode; this.TextDecorationSkipInk = options.TextDecorationSkipInk; @@ -229,6 +232,12 @@ public float LineSpacing /// public TextBidiMode TextBidiMode { get; set; } + /// + /// Gets or sets the script applied to the whole shaping request, or + /// when script runs are inferred from the text. + /// + public ScriptClass? Script { get; set; } + /// /// Gets or sets how caret movement and selection model trailing breaking whitespace. /// @@ -292,8 +301,27 @@ public float LineSpacing /// /// Gets or sets the collection of additional feature tags to apply during glyph shaping. /// + /// + /// A text run with a non-null collection + /// replaces this collection over that run. + /// public IReadOnlyList FeatureTags { get; set; } = Array.Empty(); + /// + /// Gets or sets the culture used to select language specific glyph shaping. + /// + /// + /// Fonts can substitute and position glyphs differently per language, for example the + /// Turkish dotless i or Serbian italic letterforms. When set, shaping selects the + /// matching language system within each script's OpenType feature tables, falling back + /// to the font's default language system when the font does not distinguish the + /// language. When the current culture applies, matching the + /// ambient language model of the reference shaping engines; use + /// to express no language preference and + /// always shape with the font's default language system. + /// + public CultureInfo? Culture { get; set; } + /// /// Gets or sets an optional collection of text runs to apply to the body of text. /// diff --git a/src/SixLabors.Fonts/TextRun.cs b/src/SixLabors.Fonts/TextRun.cs index 21e533f16..80ffe5941 100644 --- a/src/SixLabors.Fonts/TextRun.cs +++ b/src/SixLabors.Fonts/TextRun.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Globalization; using System.Runtime.CompilerServices; +using SixLabors.Fonts.Tables.AdvancedTypographic; using SixLabors.Fonts.Unicode; namespace SixLabors.Fonts; @@ -34,6 +36,26 @@ public class TextRun /// public FontWeight? FontWeight { get; set; } + /// + /// Gets or sets the script to use for this run, or to + /// use the script selection supplied by the owning text options. + /// + public ScriptClass? Script { get; set; } + + /// + /// Gets or sets the culture used to select language-specific shaping for this + /// run, or to use the culture supplied by the owning + /// text options. + /// + public CultureInfo? Culture { get; set; } + + /// + /// Gets or sets the additional shaping features for this run, or + /// to use those supplied by the owning text options. + /// A non-null empty collection applies no additional features to the run. + /// + public IReadOnlyList? FeatureTags { get; set; } + /// /// Gets or sets the text attributes applied to this run. /// @@ -133,7 +155,7 @@ internal void ResolveFontWeight(FontWeight? defaultWeight) this.ResolvedFontWeight = this.FontWeight ?? defaultWeight; if (!this.ResolvedFontWeight.HasValue && (font.RequestedStyle & FontStyle.Bold) == FontStyle.Bold) { - this.ResolvedFontWeight = SixLabors.Fonts.FontWeight.Bold; + this.ResolvedFontWeight = Fonts.FontWeight.Bold; } bool applied = false; diff --git a/src/SixLabors.Fonts/TextShaper.Pipeline.cs b/src/SixLabors.Fonts/TextShaper.Pipeline.cs new file mode 100644 index 000000000..168ecbe97 --- /dev/null +++ b/src/SixLabors.Fonts/TextShaper.Pipeline.cs @@ -0,0 +1,1017 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.Fonts.Tables.AdvancedTypographic; +using SixLabors.Fonts.Unicode; + +namespace SixLabors.Fonts; + +/// +/// The shaping pipeline: font-run itemization, bidi analysis, glyph population, GSUB +/// substitution, and GPOS positioning. and the public shaping +/// API both consume this single pipeline; layout composes and positions lines from its +/// output but performs no shaping of its own. +/// +public static partial class TextShaper +{ + /// + /// The pool of reusable pipeline state. A scratch is exclusively owned between + /// and , and the + /// shaped result is copied out by value before the scratch is returned, so nothing + /// pooled escapes a call. Retained scratch storage stays at its high-water mark. + /// + private static readonly ObjectPool ScratchPool = new(new ShapingScratchPooledObjectPolicy()); + + /// + /// Resolves the ordered sequence of instances that cover . + /// + /// + /// If is or empty, a single run covering the entire + /// grapheme range of using is returned. Otherwise the + /// supplied runs are ordered, gaps are filled with default-font runs, and overlapping ranges are trimmed. + /// + /// The text to partition into runs. + /// The text options supplying the default font and optional user-defined runs. + /// The resolved runs that together cover the entire grapheme range of . + internal static IReadOnlyList BuildTextRuns(ReadOnlySpan text, TextOptions options) + { + int start = 0; + int end = text.GetGraphemeCount(); + if (end == 0) + { + return []; + } + + if (options.TextRuns is null || options.TextRuns.Count == 0) + { + TextRun textRun = new() + { + Start = 0, + End = end, + Font = options.Font + }; + + textRun.ResolveFontWeight(options.FontWeight); + return [textRun]; + } + + List textRuns = []; + foreach (TextRun textRun in options.TextRuns.OrderBy(x => x.Start)) + { + // Fill gaps within runs. + if (textRun.Start > start) + { + textRuns.Add(new() + { + Start = start, + End = textRun.Start, + Font = options.Font + }); + } + + // Add the current run, ensuring the font is not null. + textRun.Font ??= options.Font; + + if (textRun.Placeholder.HasValue && textRun.End != textRun.Start) + { + throw new ArgumentException("Placeholder text runs must be zero-length insertion runs.", nameof(options)); + } + + // Ensure that the previous run does not overlap the current. + if (textRuns.Count > 0) + { + int prevIndex = textRuns.Count - 1; + TextRun previous = textRuns[prevIndex]; + previous.End = Math.Min(previous.End, textRun.Start); + } + + textRuns.Add(textRun); + start = textRun.End; + } + + // Add a final run if required. + if (start < end) + { + textRuns.Add(new() + { + Start = start, + End = end, + Font = options.Font + }); + } + + foreach (TextRun textRun in textRuns) + { + textRun.ResolveFontWeight(options.FontWeight); + } + + return textRuns; + } + + /// + /// Materializes the fallback fonts for the pass. Lives apart from the pipeline + /// body so shaping without fallbacks never pays for the construction. + /// + /// The text options carrying the fallback families. + /// The fallback fonts. + private static Font[] BuildFallbackFonts(TextOptions options) + { + IReadOnlyList families = options.FallbackFontFamilies; + Font[] fonts = new Font[families.Count]; + for (int i = 0; i < fonts.Length; i++) + { + fonts[i] = new Font(families[i], options.Font.Size, options.Font.RequestedStyle); + } + + return fonts; + } + + /// + /// Shapes into a scope that owns the pooled pipeline + /// state backing the shaped views. + /// + /// The text to process. + /// The text options used while shaping. + /// + /// The resolved text runs when the caller retains run references beyond the + /// scope, or to let the pass reuse pooled run state. + /// + /// The scoped shaping result. + internal static ShapedTextScope ShapeText( + ReadOnlySpan text, + TextOptions options, + IReadOnlyList? prebuiltRuns) + { + ShapingScratch scratch = ScratchPool.Get(); + try + { + ShapingBuffer shaped = ShapeCore(text, options, scratch, prebuiltRuns, false); + FinalizeDirectionalRuns(shaped, options.LayoutMode, scratch); + return new(ProjectShapedText(shaped, options.LayoutMode, scratch), scratch); + } + catch + { + ScratchPool.Return(scratch); + throw; + } + } + + /// + /// Runs the shaping pipeline through positioning and the post-positioning + /// passes, leaving the positioned glyph records in the returned buffer and the + /// bidi state in the scratch. The projections that copy results out for their + /// consumers sit on top. + /// + /// The text to process. + /// The text options used while shaping. + /// The rented pipeline state. + /// + /// The resolved text runs when the caller retains run references beyond the + /// scratch scope, or to let the pass reuse scratch-owned + /// run state. + /// + /// + /// Whether synthesized vertical origins follow the public shaping contract + /// instead of the browser layout contract. + /// + /// The positioned buffer. + private static ShapingBuffer ShapeCore(ReadOnlySpan text, TextOptions options, ShapingScratch scratch, IReadOnlyList? prebuiltRuns, bool useShapingVerticalOrigin) + { + (ShapingBuffer substitutions, ShapingBuffer positionings) = scratch.Prepare(options); + + // Public shaping and browser-compatible layout use different synthesized + // vertical Y origins. Select that policy once per pass so mark attachment + // materializes the right origin without another pass or per-glyph state. + substitutions.UseShapingVerticalOrigin = useShapingVerticalOrigin; + positionings.UseShapingVerticalOrigin = useShapingVerticalOrigin; + + // Gather the font and fallbacks. + Font[] fallbackFonts = (options.FallbackFontFamilies?.Count > 0) + ? BuildFallbackFonts(options) + : []; + + // Analyse the text for bidi directional runs. + BidiAlgorithm bidi = scratch.BidiAlgorithm; + BidiData bidiData = scratch.BidiData; + int codePointCount; + if (options.TextBidiMode == TextBidiMode.Override && options.TextDirection != TextDirection.Auto) + { + // An explicitly directed run cannot split into internal bidi runs. Only + // its extent is needed; character classes, brackets, and paragraph + // boundaries would be populated and then discarded. + codePointCount = CodePoint.GetCodePointCount(text); + } + else + { + bidiData.Init(text, (sbyte)options.TextDirection); + codePointCount = bidiData.Types.Length; + } + + scratch.ClearBidiRuns(); + if (options.TextBidiMode == TextBidiMode.Override) + { + sbyte runLevel = options.TextDirection == TextDirection.Auto + ? bidi.ResolveEmbeddingLevel(bidiData.Types) + : (sbyte)options.TextDirection; + BidiCharacterType runDirection = runLevel == 1 ? BidiCharacterType.RightToLeft : BidiCharacterType.LeftToRight; + + // A directional-run request is already one higher-level protocol unit. + // Its contents cannot create internal bidi runs, including separators + // and explicit controls, so only Auto direction requires inspection. + scratch.AddBidiRun(new BidiRun(runDirection, runLevel, 0, codePointCount)); + } + else + { + // UAX #9 applies level resolution independently to each paragraph. + // Keeping a run boundary at every newline also prevents font features + // and joining behaviour from crossing that protocol boundary. + ReadOnlySpan paragraphEnds = bidiData.ParagraphEnds; + int paragraphStart = 0; + for (int paragraph = 0; paragraph <= paragraphEnds.Length; paragraph++) + { + int paragraphEnd = paragraph < paragraphEnds.Length ? paragraphEnds[paragraph] : bidiData.Types.Length; + int paragraphLength = paragraphEnd - paragraphStart; + if (paragraphLength == 0) + { + paragraphStart = paragraphEnd; + continue; + } + + // Purely left-to-right text resolves to an even-level run without + // paying for the full bidirectional algorithm. + if (options.TextDirection != TextDirection.RightToLeft && bidiData.IsUniformLeftToRight) + { + scratch.AddBidiRun(new BidiRun(BidiCharacterType.LeftToRight, 0, paragraphStart, paragraphLength)); + } + else + { + ArraySlice paragraphLevels = bidiData.GetTempLevelBuffer(paragraphLength); + ArraySlice paragraphTypes = bidiData.Types.Slice(paragraphStart, paragraphLength); + ArraySlice paragraphBracketTypes = bidiData.PairedBracketTypes.Slice(paragraphStart, paragraphLength); + ArraySlice paragraphBracketValues = bidiData.PairedBracketValues.Slice(paragraphStart, paragraphLength); + bidi.Process(paragraphTypes, paragraphBracketTypes, paragraphBracketValues, (sbyte)options.TextDirection, bidiData.HasBrackets, bidiData.HasEmbeddings, bidiData.HasIsolates, paragraphLevels); + + AppendBidiRuns(scratch, paragraphLevels, paragraphStart); + } + + paragraphStart = paragraphEnd; + } + } + + BidiRun[] bidiRuns = scratch.BidiRuns; + int[] bidiMap = scratch.GetBidiMap(codePointCount); + + // Incrementally build out buffer of glyphs. Both buffers share the run list so + // per-glyph run indices agree when records are seeded across them. Callers + // retaining run references beyond the scratch scope supply their own runs; + // otherwise the synthesized whole-text run reuses scratch state. + bool usesDefaultTextRun = prebuiltRuns is null && !(options.TextRuns?.Count > 0); + IReadOnlyList textRuns = prebuiltRuns ?? ((options.TextRuns?.Count > 0) + ? BuildTextRuns(text, options) + : scratch.GetDefaultTextRuns(options)); + substitutions.SetTextRuns(textRuns); + positionings.SetTextRuns(textRuns); + + // First do multiple font runs using the individual text runs. + bool complete = true; + int textRunIndex = 0; + int codePointIndex = 0; + int stringIndex = 0; + int bidiRunIndex = 0; + + // Single-run fast path: shape and seed one buffer in place and flip it to the + // positioning role, so no record is copied between buffers. When fallback + // glyphs remain and fallback fonts exist, fall through to the general + // cross-buffer seed so the fallback passes can merge into the accumulator. + ShapingBuffer shaped = positionings; + if (textRuns.Count == 1 && !textRuns[0].Placeholder.HasValue) + { + TextRun onlyRun = textRuns[0]; + int graphemeEnd = PopulateAndSubstitute(text, onlyRun.Start, textRuns, ref textRunIndex, ref codePointIndex, ref stringIndex, ref bidiRunIndex, onlyRun.ResolvedFont, bidiRuns, bidiMap, substitutions); + + if (usesDefaultTextRun) + { + // Population has already visited every grapheme, so its final index + // is the exact exclusive end without a separate counting pass. + onlyRun.End = graphemeEnd; + } + + complete = substitutions.SeedMetricsInPlace(onlyRun.ResolvedFont); + + if (complete || fallbackFonts.Length == 0) + { + substitutions.SetRole(ShapingBufferRole.Positioning); + shaped = substitutions; + complete = true; + } + else + { + complete = positionings.TryAdd(onlyRun.ResolvedFont, substitutions); + } + + goto FallbackPasses; + } + + for (int runIndex = 0; runIndex < textRuns.Count; runIndex++) + { + TextRun textRun = textRuns[runIndex]; + if (textRun.Placeholder.HasValue) + { + substitutions.Clear(); + + while (bidiRunIndex < bidiRuns.Length && codePointIndex == bidiRuns[bidiRunIndex].End) + { + bidiRunIndex++; + } + + // Placeholder direction comes from the bidi region at the insertion + // point. If the insertion point is after all source text, use the + // default even/LTR embedding level. + BidiRun placeholderBidiRun = bidiRunIndex < bidiRuns.Length + ? bidiRuns[bidiRunIndex] + : new(BidiCharacterType.LeftToRight, 2, codePointIndex, 0); + + // Placeholder runs are inserted into the layout stream and do not consume + // source graphemes, source codepoints, or bidi runs. The loop position + // is the placeholder's own run index; the populate tracker below may + // lag it between runs. + substitutions.AddPlaceholder( + CodePoint.ObjectReplacementChar, + placeholderBidiRun, + (ushort)runIndex, + codePointIndex); + + complete &= positionings.TryAdd(textRun.ResolvedFont, substitutions); + textRunIndex++; + continue; + } + + if (!DoFontRun( + textRun.Slice(text), + textRun.Start, + textRuns, + ref textRunIndex, + ref codePointIndex, + ref stringIndex, + ref bidiRunIndex, + false, + textRun.ResolvedFont, + bidiRuns, + bidiMap, + substitutions, + positionings)) + { + complete = false; + } + } + + FallbackPasses: + if (!complete) + { + // Finally try our fallback fonts. + // We do a complete run here across the whole buffer. + foreach (Font font in fallbackFonts) + { + textRunIndex = 0; + codePointIndex = 0; + stringIndex = 0; + bidiRunIndex = 0; + if (DoFontRun( + text, + 0, + textRuns, + ref textRunIndex, + ref codePointIndex, + ref stringIndex, + ref bidiRunIndex, + true, + font, + bidiRuns, + bidiMap, + substitutions, + positionings)) + { + break; + } + } + } + + // Update the positions of the glyphs in the completed buffer. + // Each set of metrics is associated with single font and will only be updated + // by that font so it's safe to use a single buffer. + Font? lastFont = null; + for (int i = 0; i < textRuns.Count; i++) + { + TextRun textRun = textRuns[i]; + + Font font = textRun.ResolvedFont; + if (font == lastFont) + { + continue; + } + + font.FontMetrics.UpdatePositions(shaped); + lastFont = font; + } + + foreach (Font font in fallbackFonts) + { + font.FontMetrics.UpdatePositions(shaped); + } + + // Script-specific expansion runs only after every font has finished + // positioning. Process segments from the end so an expansion cannot move + // the not-yet-processed range of an earlier segment. + List<(int Index, int Count, ScriptClass Script, ShapePlan Plan)> segments = shaped.SegmentPlans; + for (int i = segments.Count - 1; i >= 0; i--) + { + (int index, int count, ScriptClass _, ShapePlan plan) = segments[i]; + plan.Shaper.PostprocessGlyphs(shaped, index, count); + } + + HideDefaultIgnorables(shaped); + + return shaped; + } + + /// + /// Finalizes each resolved directional run into HarfBuzz visual glyph order and + /// records its contiguous range without copying or taking ownership of glyphs. + /// + /// The positioned glyph buffer in logical run order. + /// The shaping orientation used for the glyphs. + /// The pooled state receiving run-range metadata. + private static void FinalizeDirectionalRuns(ShapingBuffer shaped, LayoutMode layoutMode, ShapingScratch scratch) + { + int glyphIndex = 0; + BidiRun[] bidiRuns = scratch.BidiRuns; + int[] bidiMap = scratch.BidiMap; + for (int runIndex = 0; runIndex < scratch.BidiRunCount; runIndex++) + { + BidiRun bidiRun = bidiRuns[runIndex]; + int glyphStart = glyphIndex; + while (glyphIndex < shaped.Count) + { + ref GlyphShapingData glyph = ref shaped[glyphIndex]; + bool belongsToRun = glyph.IsPlaceholder + ? shaped.GetPlaceholderBidiRun(glyph.CodePointIndex).Equals(bidiRun) + : bidiMap[glyph.CodePointIndex] == runIndex; + + if (!belongsToRun) + { + break; + } + + glyphIndex++; + } + + ShapedGlyphRange glyphRange = new(glyphStart, glyphIndex - glyphStart); + scratch.SetBidiGlyphRange(runIndex, in glyphRange); + + if ((bidiRun.Level & 1) == 0) + { + continue; + } + + // Browsers copy the shaper's output sequentially because it is already + // in visual order for the directional run. Fonts shapes in + // logical storage, so apply the same proven finalization used by ShapeRun + // once per resolved run before projecting the contiguous arrays. + if (layoutMode.IsVertical()) + { + // Bottom-to-top HarfBuzz shaping reverses graphemes before shaping. + // Reverse their order while retaining the shaped glyph order inside + // each grapheme, exactly as the public directional-run contract does. + shaped.ReverseGraphemeRange(glyphStart, glyphIndex); + } + else + { + // Horizontal and mixed-vertical directional runs use HarfBuzz's + // complete backward stream order, including every positioned glyph + // emitted for one source position. + shaped.ReverseRange(glyphStart, glyphIndex); + } + } + } + + /// + /// Coalesces adjacent equal embedding levels from one paragraph into shaping runs. + /// + /// The shaping state receiving the runs. + /// The resolved levels relative to the paragraph. + /// The paragraph's code point offset in the complete text. + private static void AppendBidiRuns(ShapingScratch scratch, ArraySlice levels, int textStart) + { + if (levels.Length == 0) + { + return; + } + + int startRun = 0; + sbyte runLevel = levels[0]; + for (int i = 1; i < levels.Length; i++) + { + if (levels[i] == runLevel) + { + continue; + } + + BidiCharacterType direction = (runLevel & 0x01) == 0 ? BidiCharacterType.LeftToRight : BidiCharacterType.RightToLeft; + scratch.AddBidiRun(new BidiRun(direction, runLevel, textStart + startRun, i - startRun)); + startRun = i; + runLevel = levels[i]; + } + + BidiCharacterType finalDirection = (runLevel & 0x01) == 0 ? BidiCharacterType.LeftToRight : BidiCharacterType.RightToLeft; + scratch.AddBidiRun(new BidiRun(finalDirection, runLevel, textStart + startRun, levels.Length - startRun)); + } + + /// + /// Copies the shaped result out of the pooled collections: run-constant state + /// deduplicates into a run table and per-glyph state splits into parallel + /// identity and geometry arrays of pure values held by the scratch, so the + /// views stay valid exactly as long as the caller holds the scratch. + /// + /// The positioned buffer. + /// The layout mode used while shaping. + /// The rented pipeline state holding the bidi results and projection storage. + /// The wrapping-independent shaping state, valid within the scratch scope. + private static ShapedText ProjectShapedText(ShapingBuffer shaped, LayoutMode layoutMode, ShapingScratch scratch) + { + uint verticalMask = ShapePlanFeatures.VerticalFeatureMask; + int count = shaped.Count; + (ShapedGlyphInfo[] infos, ShapedGlyphPosition[] positions) = scratch.GetProjection(count); + scratch.ClearRuns(); + + Font? runFont = null; + int runTextRunIndex = -1; + BidiRun runBidiRun = default; + for (int i = 0; i < count; i++) + { + ref GlyphShapingData shaping = ref shaped[i]; + ref ShapingBuffer.GlyphMetricsEntry entry = ref shaped.MetricsAt(i); + ref GlyphShapingPosition shapingPosition = ref shaped.PositionAt(i); + + // Placeholders carry a bidi run of their own, so they always cut a run. + BidiRun shapingBidiRun = shaping.IsPlaceholder + ? shaped.GetPlaceholderBidiRun(shaping.CodePointIndex) + : default; + if (entry.Font != runFont + || shaping.TextRunIndex != runTextRunIndex + || (shaping.IsPlaceholder && !shapingBidiRun.Equals(runBidiRun))) + { + runFont = entry.Font; + runTextRunIndex = shaping.TextRunIndex; + runBidiRun = shapingBidiRun; + scratch.AddRun(new(entry.Font, entry.PointSize, shaped.TextRuns[shaping.TextRunIndex], shapingBidiRun)); + } + + ShapedGlyphFlags flags = ShapedGlyphFlags.None; + if (shaping.IsPlaceholder) + { + flags |= ShapedGlyphFlags.Placeholder; + } + + if (shaping.IsSubstituted) + { + flags |= ShapedGlyphFlags.Substituted; + } + + if (shaping.IsDecomposed) + { + flags |= ShapedGlyphFlags.Decomposed; + } + + if ((shaping.AppliedFeatureMask & verticalMask) != 0) + { + flags |= ShapedGlyphFlags.VerticalSubstituted; + } + + if (shaping.IsCursiveScript) + { + // Layout needs the post-GSUB script classification only when it + // applies tracking. Carry one bit through projection instead of + // retaining the heavier itemization data. + flags |= ShapedGlyphFlags.CursiveScript; + } + + infos[i] = new( + shaping.CodePointIndex, + shaping.CodePoint, + shaping.CodePointCount, + entry.Metrics.GlyphId, + (ushort)(scratch.RunCount - 1), + flags); + + positions[i] = new( + entry.GetAdvanceWidth(in shapingPosition), + entry.GetAdvanceHeight(in shapingPosition), + new Vector2(shapingPosition.Bounds.X, shapingPosition.Bounds.Y), + entry.Metrics.Offset); + } + + return new ShapedText( + scratch.Runs, + infos, + positions, + count, + scratch.BidiRuns, + scratch.BidiGlyphRanges, + scratch.BidiRunCount, + scratch.BidiMap, + layoutMode); + } + + /// + /// Renders default ignorable records invisibly after positioning: both advances + /// and the offset on the axis of movement zero out, and the glyph swaps to its + /// font's invisible glyph. Records whose font offers no invisible glyph are + /// deleted instead. Records a lookup substituted keep their glyphs: the + /// substitution is a deliberate rendering the font produced from the ignorable. + /// + /// The positioned buffer. + private static void HideDefaultIgnorables(ShapingBuffer shaped) + { + if (!shaped.HasDefaultIgnorables) + { + return; + } + + CodePoint space = new(0x0020); + LayoutMode layoutMode = shaped.TextOptions.LayoutMode; + ColorFontSupport colorFontSupport = shaped.TextOptions.ColorFontSupport; + bool isVertical = layoutMode.IsVertical(); + Font? invisibleFont = null; + ushort invisible = 0; + bool hasInvisible = false; + bool hasUnreplaceable = false; + for (int i = 0; i < shaped.Count; i++) + { + ref GlyphShapingData data = ref shaped[i]; + if (!data.IsDefaultIgnorable || data.IsSubstituted) + { + continue; + } + + // Writing through the bounds setters marks them dirty, which is what + // the projection's advance reads honor. The cross-axis offset stays, + // so adjustments from positioning survive. + ref GlyphShapingPosition position = ref shaped.PositionAt(i); + position.Bounds.Width = 0; + position.Bounds.Height = 0; + if (isVertical) + { + position.Bounds.Y = 0; + } + else + { + position.Bounds.X = 0; + } + + ref ShapingBuffer.GlyphMetricsEntry entry = ref shaped.MetricsAt(i); + Font font = entry.Font; + if (!ReferenceEquals(font, invisibleFont)) + { + invisibleFont = font; + hasInvisible = font is not null && font.FontMetrics.TryGetGlyphId(space, out invisible); + } + + if (font is null || !hasInvisible) + { + hasUnreplaceable = true; + continue; + } + + // The projection reads the glyph id and default advance from the + // metrics entry, so the invisible glyph's metrics replace it. + TextRun textRun = shaped.TextRuns[data.TextRunIndex]; + entry.Metrics = font.FontMetrics.GetGlyphMetrics(space, invisible, textRun.TextAttributes, textRun.TextDecorations, layoutMode, colorFontSupport); + shaped.SetGlyphId(i, invisible); + data.IsHidden = true; + } + + if (hasUnreplaceable) + { + shaped.DeleteGlyphsInPlace(static data => data.IsDefaultIgnorable && !data.IsSubstituted && !data.IsHidden); + } + } + + /// + /// Shapes a single font run — maps codepoints in to glyph ids using + /// , then runs GSUB substitution and GPOS positioning. Codepoints that + /// the font cannot map are recorded for a later fallback pass. + /// + /// The run-relative text slice to shape. + /// The starting grapheme index (absolute within the original input). + /// The ordered list of resolved text runs. + /// The index of the current text run; advanced as the enumerator crosses run boundaries. + /// The running codepoint index (absolute within the original input). + /// The running char index (absolute within the original input). + /// The running bidi run index. + /// + /// if this call is the fallback-font pass (in which case unmapped codepoints + /// may still emit .notdef glyphs). + /// + /// The font to shape with. + /// The resolved bidi runs covering the whole input. + /// A codepoint → bidi-run mapping accumulated across shaping passes. + /// The GSUB substitution buffer to write into. + /// The GPOS positioning buffer to write into. + /// + /// if every codepoint mapped successfully; if any + /// codepoint remains unmapped (so a fallback-font pass is needed). + /// + private static bool DoFontRun( + ReadOnlySpan text, + int start, + IReadOnlyList textRuns, + ref int textRunIndex, + ref int codePointIndex, + ref int stringIndex, + ref int bidiRunIndex, + bool isFallbackRun, + Font font, + BidiRun[] bidiRuns, + int[] bidiMap, + ShapingBuffer substitutions, + ShapingBuffer positionings) + { + _ = PopulateAndSubstitute(text, start, textRuns, ref textRunIndex, ref codePointIndex, ref stringIndex, ref bidiRunIndex, font, bidiRuns, bidiMap, substitutions); + + bool result = !isFallbackRun + ? positionings.TryAdd(font, substitutions) + : positionings.TryUpdate(font, substitutions); + return result; + } + + /// + /// Populates the substitution buffer from and runs bidi + /// mirroring and GSUB substitution over it, leaving the shaped records in the + /// buffer for either in-place metrics seeding or a cross-buffer seed. + /// + /// The run-relative text slice to shape. + /// The starting grapheme index (absolute within the original input). + /// The ordered list of resolved text runs. + /// The index of the current text run; advanced as the enumerator crosses run boundaries. + /// The running codepoint index (absolute within the original input). + /// The running char index (absolute within the original input). + /// The running bidi run index. + /// The font to shape with. + /// The resolved bidi runs covering the whole input. + /// A codepoint → bidi-run mapping accumulated across shaping passes. + /// The GSUB substitution buffer to write into. + /// The exclusive grapheme index reached after consuming the text. + private static int PopulateAndSubstitute(ReadOnlySpan text, int start, IReadOnlyList textRuns, ref int textRunIndex, ref int codePointIndex, ref int stringIndex, ref int bidiRunIndex, Font font, BidiRun[] bidiRuns, int[] bidiMap, ShapingBuffer substitutions) + { + // For each run we start with a fresh substitution buffer to avoid + // overwriting the glyph ids. + substitutions.Clear(); + + // A font without variation sequences never consumes the following codepoint + // during glyph lookup, so the per-codepoint lookahead decode is skipped. + bool hasVariationSequences = font.FontMetrics.HasUnicodeVariationSequences; + + // Shaping needs each grapheme's boundary and source slice, but not terminal + // width, emoji, or display flags. The boundary-only mode avoids deriving + // metadata that no shaping operation consumes. + int graphemeIndex = start; + int inputGroupStart = substitutions.Count; + bool previousWasContinuation = false; + bool previousWasRegionalIndicator = false; + bool previousWasZeroWidthJoiner = false; + SpanGraphemeEnumerator graphemeEnumerator = new(text, true); + while (graphemeEnumerator.MoveNext()) + { + ReadOnlySpan grapheme = graphemeEnumerator.CurrentSpan; + int graphemeMax = grapheme.Length - 1; + int graphemeCodePointIndex = 0; + int charIndex = 0; + + while (textRunIndex < textRuns.Count - 1 && graphemeIndex == textRuns[textRunIndex].End) + { + textRunIndex++; + } + + // Now enumerate through each codepoint in the grapheme. + bool skipNextCodePoint = false; + SpanCodePointEnumerator codePointEnumerator = new(grapheme); + while (codePointEnumerator.MoveNext()) + { + CodePoint current = codePointEnumerator.Current; + int currentStringIndex = stringIndex; + stringIndex += current.Utf16SequenceLength; + + uint value = (uint)current.Value; + GraphemeClusterClass graphemeClass = CodePoint.GetGraphemeClusterClass(current); + + // HarfBuzz deliberately uses a smaller continuation rule set than + // the complete Unicode grapheme algorithm. In particular, ZWNJ and + // adjacent Hangul letters keep distinct input starts, while marks, + // emoji modifiers, paired regional indicators, ZWJ emoji sequences, + // half-width voiced marks, and emoji tag characters continue the + // preceding input group. + bool isRegionalIndicator = graphemeClass == GraphemeClusterClass.RegionalIndicator; + bool isZeroWidthJoiner = CodePoint.IsZeroWidthJoiner(current); + bool isContinuation = + + // Combining, spacing-combining, and enclosing marks belong to + // the preceding base character. + CodePoint.IsMark(current) + + // U+1F3FB..U+1F3FF are the five emoji skin-tone modifiers. + || value is >= 0x1F3FB and <= 0x1F3FF + + // Regional indicators form flag pairs. The second indicator + // continues the first; the third starts the next pair. + || (isRegionalIndicator && previousWasRegionalIndicator && !previousWasContinuation) + + // U+200D ZERO WIDTH JOINER connects the characters on either side. + || isZeroWidthJoiner + + // An extended pictographic character following U+200D continues + // the emoji sequence selected by that joiner. + || (previousWasZeroWidthJoiner && graphemeClass == GraphemeClusterClass.ExtendedPictographic) + + // U+FF9E and U+FF9F are the half-width Katakana voiced and + // semi-voiced sound marks. + || value is >= 0xFF9E and <= 0xFF9F + + // U+E0020 TAG SPACE through U+E007F CANCEL TAG encode the + // invisible tag sequences used by emoji subregion flags. + || value is >= 0xE0020 and <= 0xE007F; + + if (!isContinuation) + { + // The preceding group is now complete. Combine its exact input + // starts once, before GSUB can move or expand any of its records. + if (substitutions.Count - inputGroupStart > 1) + { + substitutions.CombineInputStarts(inputGroupStart, substitutions.Count); + } + + inputGroupStart = substitutions.Count; + } + + previousWasContinuation = isContinuation; + previousWasRegionalIndicator = isRegionalIndicator; + previousWasZeroWidthJoiner = isZeroWidthJoiner; + + if (codePointIndex == bidiRuns[bidiRunIndex].End) + { + bidiRunIndex++; + } + + if (skipNextCodePoint) + { + codePointIndex++; + graphemeCodePointIndex++; + continue; + } + + bidiMap[codePointIndex] = bidiRunIndex; + + int charsConsumed = 0; + charIndex += current.Utf16SequenceLength; + CodePoint? next = hasVariationSequences && graphemeCodePointIndex < graphemeMax + ? CodePoint.DecodeFromUtf16At(grapheme, charIndex, out charsConsumed) + : null; + + charIndex += charsConsumed; + + // Get the glyph id for the codepoint and add to the buffer. Every + // codepoint enters the buffer, including unmapped default + // ignorables as the missing glyph: sequence matching treats them + // as transparent and the hide stage replaces them at the end. + _ = substitutions.TryGetGlyphId(font.FontMetrics, current, next, out ushort glyphId, out skipNextCodePoint); + + // Capture all three source coordinates while the input enumerators + // provide them. Later substitutions move, duplicate, or combine the + // complete record without reconstructing source positions. + substitutions.AddGlyph(glyphId, current, (TextDirection)bidiRuns[bidiRunIndex].Direction, (ushort)textRunIndex, codePointIndex, currentStringIndex, graphemeIndex); + + codePointIndex++; + graphemeCodePointIndex++; + } + + graphemeIndex++; + } + + if (substitutions.Count - inputGroupStart > 1) + { + // Complete the final input group because no following codepoint exists + // to close it inside the loop. + substitutions.CombineInputStarts(inputGroupStart, substitutions.Count); + } + + // Apply the simple and complex substitutions. + SubstituteBidiMirrors(font.FontMetrics, substitutions); + + font.FontMetrics.ApplySubstitution(substitutions); + return graphemeIndex; + } + + /// + /// Substitutes mirrored bracket glyphs (for example ()) inside right-to-left + /// bidi runs, per Unicode Bidirectional Algorithm rule L4. Relies on the font's rtlm + /// feature when available and falls back to the Unicode mirror table otherwise. + /// + /// The font metrics used to look up mirrored glyph ids. + /// The substitution buffer whose glyphs will be rewritten in place. + private static void SubstituteBidiMirrors(FontMetrics fontMetrics, ShapingBuffer buffer) + { + for (int i = 0; i < buffer.Count; i++) + { + ref GlyphShapingData data = ref buffer[i]; + + if (data.Direction != TextDirection.RightToLeft) + { + continue; + } + + if (!CodePoint.TryGetBidiMirror(data.CodePoint, out CodePoint mirror)) + { + continue; + } + + if (fontMetrics.TryGetGlyphId(mirror, out ushort glyphId)) + { + buffer.Replace(i, glyphId, KnownFeatureTags.RightToLeftMirroredForms); + } + } + + // TODO: This only replaces certain glyphs. We should investigate the specification further. + // https://www.unicode.org/reports/tr50/#vertical_alternates + if (buffer.TextOptions.LayoutMode.IsHorizontal()) + { + return; + } + + for (int i = 0; i < buffer.Count; i++) + { + ref GlyphShapingData data = ref buffer[i]; + if (CodePoint.GetVerticalOrientationType(data.CodePoint) is VerticalOrientationType.Upright or VerticalOrientationType.TransformUpright) + { + continue; + } + + if (!CodePoint.TryGetVerticalMirror(data.CodePoint, out CodePoint mirror)) + { + continue; + } + + if (fontMetrics.TryGetGlyphId(mirror, out ushort glyphId)) + { + buffer.Replace(i, glyphId, KnownFeatureTags.VerticalAlternates); + } + } + } + + /// + /// A shaping result scoped to the pooled pipeline state backing its views. + /// Disposal returns the state to the pool and ends the views' validity, so + /// consumers copy what they retain before the scope closes. + /// + internal readonly ref struct ShapedTextScope + { + /// + /// The pooled pipeline state owned by the scope. + /// + private readonly ShapingScratch scratch; + + /// + /// Initializes a new instance of the struct. + /// + /// The shaped views. + /// The pooled pipeline state backing the views. + public ShapedTextScope(ShapedText shaped, ShapingScratch scratch) + { + this.Shaped = shaped; + this.scratch = scratch; + } + + /// + /// Gets the wrapping-independent shaping state, valid until disposal. + /// + public ShapedText Shaped { get; } + + /// + /// Returns the pooled pipeline state, ending the views' validity. + /// + public void Dispose() => ScratchPool.Return(this.scratch); + } + + /// + /// The pooling policy for instances: scratch state is + /// reset on acquisition by , so returned + /// instances are always accepted. + /// + private sealed class ShapingScratchPooledObjectPolicy : IPooledObjectPolicy + { + /// + public ShapingScratch Create() => new(); + + /// + public bool Return(ShapingScratch obj) => true; + } +} diff --git a/src/SixLabors.Fonts/TextShaper.cs b/src/SixLabors.Fonts/TextShaper.cs new file mode 100644 index 000000000..a22ac994a --- /dev/null +++ b/src/SixLabors.Fonts/TextShaper.cs @@ -0,0 +1,238 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.Fonts.Tables.AdvancedTypographic; + +namespace SixLabors.Fonts; + +/// +/// Encapsulates logic for shaping text into a positioned glyph stream. +/// +/// +/// +/// The text and its properties are set on a , the +/// buffer is shaped against a font, and the glyphs are read back from it. Shaping +/// applies the font's substitution and positioning features but does not wrap the +/// text. +/// +/// +/// treats the text as unwrapped +/// logical lines separated by hard breaks and resolves mixed-direction text within +/// each line. shapes one directional +/// run that has already been selected by the caller. +/// +/// +/// Advances and offsets are scaled to the supplied font's size. +/// +/// +public static partial class TextShaper +{ + /// + /// Shapes the buffer's text as unwrapped logical lines separated by hard + /// breaks, replacing the buffer's glyphs with each line's visually ordered + /// glyphs. + /// + /// The font to shape against. + /// The buffer holding the line, which receives the glyphs. + public static void Shape(Font font, TextShapingBuffer buffer) + { + Guard.NotNull(font, nameof(font)); + Guard.NotNull(buffer, nameof(buffer)); + + ShapeIntoBuffer(font, buffer, [], TextBidiMode.Normal); + } + + /// + /// Shapes the buffer's text as unwrapped logical lines separated by hard breaks + /// with the given features turned on, replacing the buffer's glyphs with each + /// line's visually ordered glyphs. + /// + /// The font to shape against. + /// The buffer holding the line, which receives the glyphs. + /// The feature tags to turn on for the line. + public static void Shape(Font font, TextShapingBuffer buffer, Tag[] features) + { + Guard.NotNull(font, nameof(font)); + Guard.NotNull(buffer, nameof(buffer)); + Guard.NotNull(features, nameof(features)); + + ShapeIntoBuffer(font, buffer, features, TextBidiMode.Normal); + } + + /// + /// Shapes the buffer's text as one directional run, replacing the buffer's + /// glyphs with the glyphs in reading order. + /// + /// The font to shape against. + /// The buffer holding the run, which receives the glyphs. + public static void ShapeRun(Font font, TextShapingBuffer buffer) + { + Guard.NotNull(font, nameof(font)); + Guard.NotNull(buffer, nameof(buffer)); + + ShapeIntoBuffer(font, buffer, [], TextBidiMode.Override); + } + + /// + /// Shapes the buffer's text as one directional run with the given features + /// turned on, replacing the buffer's glyphs with the glyphs in reading order. + /// + /// The font to shape against. + /// The buffer holding the run, which receives the glyphs. + /// The feature tags to turn on for the run. + public static void ShapeRun(Font font, TextShapingBuffer buffer, Tag[] features) + { + Guard.NotNull(font, nameof(font)); + Guard.NotNull(buffer, nameof(buffer)); + Guard.NotNull(features, nameof(features)); + + ShapeIntoBuffer(font, buffer, features, TextBidiMode.Override); + } + + /// + /// Shapes the buffer under the selected bidirectional contract and publishes + /// the resulting glyphs. + /// + /// The font to shape against. + /// The buffer holding the text and receiving the glyphs. + /// The feature tags to turn on. + /// Whether the text is a logical line or one directional run. + private static void ShapeIntoBuffer(Font font, TextShapingBuffer buffer, Tag[] features, TextBidiMode bidiMode) + { + if (buffer.Text.IsEmpty) + { + _ = buffer.Reserve(0); + buffer.Commit(0); + return; + } + + ShapingScratch scratch = ScratchPool.Get(); + try + { + // Public shaping accepts shaping concerns only. Point size enters the + // projection below; DPI and device coordinates remain renderer inputs. + TextOptions options = scratch.GetShapingOptions(font, buffer.TextDirection, buffer.Language, buffer.Script, buffer.LayoutMode, buffer.KerningMode, features, bidiMode); + + ShapingBuffer shaped = ShapeCore(buffer.Text, options, scratch, null, true); + Span lineEnds = default; + + if (bidiMode == TextBidiMode.Normal) + { + // ShapeCore deliberately leaves positioned records in logical order + // because layout cannot choose visual order until line breaking. This + // API has no soft wrapping, so each newline function fixes a complete + // line on which the shared L2 transformation can run immediately. + ReadOnlySpan paragraphEnds = scratch.BidiData.ParagraphEnds; + lineEnds = buffer.ReserveLineEnds(paragraphEnds.Length); + int glyphStart = 0; + for (int paragraph = 0; paragraph <= paragraphEnds.Length; paragraph++) + { + int codePointEnd = paragraph < paragraphEnds.Length ? paragraphEnds[paragraph] : scratch.BidiData.Length; + int glyphEnd = glyphStart; + while (glyphEnd < shaped.Count && shaped[glyphEnd].CodePointIndex < codePointEnd) + { + glyphEnd++; + } + + BidiReordering.Reorder(shaped, scratch.BidiRuns, scratch.BidiMap, glyphStart, glyphEnd); + if (buffer.LayoutMode.IsVertical()) + { + // HarfBuzz normalizes bottom-to-top runs by reversing whole + // graphemes before shaping. UAX #9 has already reversed every + // glyph record, so restore the shaped order inside each + // right-to-left grapheme without changing its line position. + int graphemeStart = glyphStart; + while (graphemeStart < glyphEnd) + { + ref GlyphShapingData first = ref shaped[graphemeStart]; + int graphemeIndex = first.GraphemeIndex; + int graphemeEnd = graphemeStart + 1; + while (graphemeEnd < glyphEnd && shaped[graphemeEnd].GraphemeIndex == graphemeIndex) + { + graphemeEnd++; + } + + if (first.Direction == TextDirection.RightToLeft) + { + shaped.ReverseRange(graphemeStart, graphemeEnd); + } + + graphemeStart = graphemeEnd; + } + } + + if (paragraph < paragraphEnds.Length) + { + lineEnds[paragraph] = glyphEnd; + } + + glyphStart = glyphEnd; + } + } + else + { + // Reuse the layout pipeline's per-run finalization so the public + // directional-run contract and internal layout cannot diverge. + FinalizeDirectionalRuns(shaped, buffer.LayoutMode, scratch); + } + + int count = shaped.Count; + Span destination = buffer.Reserve(count); + for (int i = 0; i < count; i++) + { + ref GlyphShapingData shaping = ref shaped[i]; + ref ShapingBuffer.GlyphMetricsEntry entry = ref shaped.MetricsAt(i); + ref GlyphShapingPosition position = ref shaped.PositionAt(i); + + // Internal positioning remains in design units. Scale once by the + // supplied Font.Size before publishing, with no caller-side scaling + // and no DPI mixed into the shaping result. + float scale = font.Size / entry.Metrics.UnitsPerEm; + Vector2 offset = (new Vector2(position.Bounds.X, position.Bounds.Y) + entry.Metrics.Offset) * scale; + + // Mixed vertical layout shapes upright characters vertically but + // leaves sideways characters horizontal for the layout rotation. + bool isVertical = AdvancedTypographicUtils.IsVerticalGlyph(shaping.CodePoint, buffer.LayoutMode); + + // Shaping publishes an advance vector in the shaper's Y-up coordinate + // system. The inactive axis is zero; device-space inversion belongs to + // the renderer consuming the result. + float advanceWidth = isVertical ? 0 : entry.GetAdvanceWidth(in position) * scale; + float advanceHeight = isVertical ? -entry.GetAdvanceHeight(in position) * scale : 0; + if (isVertical) + { + // Fonts with vertical metrics place the origin above the glyph + // ink by the authored top side bearing. + float verticalOriginY = entry.Metrics.Bounds.Max.Y + entry.Metrics.TopSideBearing; + VerticalMetrics verticalMetrics = entry.Metrics.FontMetrics.VerticalMetrics; + if (verticalMetrics.Synthesized) + { + // HarfBuzz centers glyph extents within the synthesized + // ascender-to-descender advance when vertical tables are + // absent. Floor preserves its integer midpoint behavior. + float fontAdvance = verticalMetrics.Ascender - verticalMetrics.Descender; + verticalOriginY = entry.Metrics.Bounds.Max.Y + MathF.Floor((fontAdvance - entry.Metrics.Height) * .5F); + } + + // The vertical origin lies at half the horizontal advance and + // at the authored or synthesized Y origin. Subtracting it turns + // the horizontal glyph origin into the vertical shaping origin + // while preserving the public Y-up coordinate system. + offset -= new Vector2(entry.Metrics.AdvanceWidth / 2, verticalOriginY) * scale; + } + + destination[i] = new ShapedGlyph(entry.Metrics.GlyphId, shaping.StringIndex, shaping.GraphemeIndex, advanceWidth, advanceHeight, offset); + } + + // Public shaping has no placeholder text runs, so the paragraph loop's + // shaped-buffer indices are also the published glyph indices. + buffer.Commit(count); + buffer.CommitLineEnds(lineEnds.Length); + } + finally + { + ScratchPool.Return(scratch); + } + } +} diff --git a/src/SixLabors.Fonts/TextShapingBuffer.cs b/src/SixLabors.Fonts/TextShapingBuffer.cs new file mode 100644 index 000000000..765c9a854 --- /dev/null +++ b/src/SixLabors.Fonts/TextShapingBuffer.cs @@ -0,0 +1,206 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; +using SixLabors.Fonts.Unicode; + +namespace SixLabors.Fonts; + +/// +/// A reusable, caller-owned buffer holding text, the properties it is shaped +/// under, and the glyphs shaping produces. Text and properties are set on the +/// buffer, the buffer is shaped, and the glyphs are read back from it. Each shaping +/// call replaces the glyphs. +/// +/// +/// +/// treats the text as +/// unwrapped logical lines separated by hard breaks. +/// +/// +/// treats the text as +/// one directional run. An instance is not thread safe. +/// +/// +/// Glyph identifiers and positions are specific to the and +/// used while shaping. Pass that same font and layout mode +/// in the used to measure or render the shaped buffer. +/// +/// +public sealed class TextShapingBuffer +{ + /// + /// The flat glyph storage. Only the first records are live; + /// capacity beyond the count is retained scratch. + /// + private ShapedGlyph[] glyphs = []; + + /// + /// Glyph-end indices for hard-delimited lines. Only the first + /// values are live. + /// + private int[] lineEnds = []; + + /// + /// The text storage, so a buffer that is refilled from a span does not allocate. + /// + private char[] text = []; + + /// + /// The number of characters of that are live. + /// + private int textLength; + + /// + /// The number of live hard-line boundary indices in . + /// + private int lineEndCount; + + /// + /// Gets the text of the run. + /// + public ReadOnlySpan Text => this.text.AsSpan(0, this.textLength); + + /// + /// Gets or sets the base direction of a logical line, or the direction of a + /// directional run. Defaults to . + /// + public TextDirection TextDirection { get; set; } = TextDirection.Auto; + + /// + /// Gets or sets the language the run is written in, which selects the language + /// specific behaviour of the font's features. + /// + public CultureInfo Language { get; set; } = CultureInfo.InvariantCulture; + + /// + /// Gets or sets the script to use for the entire shaping request, or + /// to infer scripts from the text. + /// + public ScriptClass? Script { get; set; } + + /// + /// Gets or sets the layout mode used to select horizontal or vertical shaping. + /// Defaults to . + /// + public LayoutMode LayoutMode { get; set; } + + /// + /// Gets or sets the kerning mode used while positioning glyphs. + /// Defaults to . + /// + public KerningMode KerningMode { get; set; } + + /// + /// Gets the number of shaped glyphs the last shaping call produced. + /// + public int Count { get; private set; } + + /// + /// Gets the shaped glyphs in visual order within each logical line, or reading + /// order for a directional run. + /// + public ReadOnlySpan Glyphs => this.glyphs.AsSpan(0, this.Count); + + /// + /// Gets the glyph-end indices of hard-delimited lines. An empty span represents + /// one directional run or one logical line. + /// + public ReadOnlySpan LineEnds => this.lineEnds.AsSpan(0, this.lineEndCount); + + /// + /// Gets a read-only reference to the shaped glyph at the given index. + /// + /// The zero-based glyph index. + public ref readonly ShapedGlyph this[int index] + { + get + { + Guard.MustBeBetweenOrEqualTo(index, 0, this.Count - 1, nameof(index)); + return ref this.glyphs[index]; + } + } + + /// + /// Replaces the text of the run, discarding any glyphs already shaped. + /// + /// The text of the run. + public void Add(ReadOnlySpan value) + { + if (this.text.Length < value.Length) + { + this.text = new char[Math.Max(value.Length, Math.Max(64, this.text.Length * 2))]; + } + + value.CopyTo(this.text); + this.textLength = value.Length; + this.Count = 0; + this.lineEndCount = 0; + } + + /// + public void Add(string value) + { + Guard.NotNull(value, nameof(value)); + + this.Add(value.AsSpan()); + } + + /// + /// Removes the text and the glyphs while retaining the storage. + /// + public void Clear() + { + this.Count = 0; + this.textLength = 0; + this.lineEndCount = 0; + } + + /// + /// Begins replacing the glyphs: empties them, ensures capacity for the given + /// record count, and returns the writable storage. The written records become + /// visible when publishes their count. + /// + /// The record capacity to reserve. + /// The writable storage span. + internal Span Reserve(int capacity) + { + this.Count = 0; + this.lineEndCount = 0; + if (this.glyphs.Length < capacity) + { + this.glyphs = new ShapedGlyph[Math.Max(capacity, Math.Max(64, this.glyphs.Length * 2))]; + } + + return this.glyphs.AsSpan(0, capacity); + } + + /// + /// Ensures capacity for hard-line boundary indices and returns their writable + /// storage. + /// + /// The boundary capacity to reserve. + /// The writable boundary span. + internal Span ReserveLineEnds(int capacity) + { + if (this.lineEnds.Length < capacity) + { + this.lineEnds = new int[Math.Max(capacity, Math.Max(4, this.lineEnds.Length * 2))]; + } + + return this.lineEnds.AsSpan(0, capacity); + } + + /// + /// Publishes the number of records written to the reserved storage. + /// + /// The record count. + internal void Commit(int count) => this.Count = count; + + /// + /// Publishes the number of hard-line boundary indices written to the reserved + /// storage. + /// + /// The boundary count. + internal void CommitLineEnds(int count) => this.lineEndCount = count; +} diff --git a/src/SixLabors.Fonts/Unicode/ArabicJoiningClass.cs b/src/SixLabors.Fonts/Unicode/ArabicJoiningClass.cs index 4802745bb..afe27b9be 100644 --- a/src/SixLabors.Fonts/Unicode/ArabicJoiningClass.cs +++ b/src/SixLabors.Fonts/Unicode/ArabicJoiningClass.cs @@ -26,7 +26,7 @@ public ArabicJoiningClass(CodePoint codePoint) { UnicodeCategory category = CodePoint.GetGeneralCategory(codePoint); uint value = UnicodeData.GetJoiningClass((uint)codePoint.Value); - this.JoiningType = GetJoiningType(codePoint, value, category); + this.JoiningType = GetJoiningType(value, category); this.JoiningGroup = (ArabicJoiningGroup)((value >> 16) & 0xFF); } @@ -41,38 +41,18 @@ public ArabicJoiningClass(CodePoint codePoint) public ArabicJoiningGroup JoiningGroup { get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ArabicJoiningType GetJoiningType(CodePoint codePoint, uint value, UnicodeCategory category) + private static ArabicJoiningType GetJoiningType(uint value, UnicodeCategory category) { var type = (ArabicJoiningType)(value & 0xFF); - - // All others not explicitly listed have joining type U - if (type == ArabicJoiningType.NonJoining) + if (type != ArabicJoiningType.Unlisted) { - // 200C; ZERO WIDTH NON-JOINER; U; No_Joining_Group - // 200D; ZERO WIDTH JOINER; C; No_Joining_Group - // 202F; NARROW NO-BREAK SPACE; U; No_Joining_Group - // 2066; LEFT-TO-RIGHT ISOLATE; U; No_Joining_Group - // 2067; RIGHT-TO-LEFT ISOLATE; U; No_Joining_Group - // 2068; FIRST STRONG ISOLATE; U; No_Joining_Group - // 2069; POP DIRECTIONAL ISOLATE; U; No_Joining_Group - if (codePoint.Value is 0x200C - or 0x200D - or 0x202F - or 0x2066 - or 0x2067 - or 0x2068 - or 0x2069) - { - return type; - } - - // Those that are not explicitly listed and that are of General Category Mn, Me, or Cf have joining type T. - if (category is UnicodeCategory.NonSpacingMark or UnicodeCategory.EnclosingMark or UnicodeCategory.Format) - { - type = ArabicJoiningType.Transparent; - } + return type; } - return type; + // A character the joining data does not list is transparent when it is a + // mark or a format character, and does not join otherwise. + return category is UnicodeCategory.NonSpacingMark or UnicodeCategory.EnclosingMark or UnicodeCategory.Format + ? ArabicJoiningType.Transparent + : ArabicJoiningType.NonJoining; } } diff --git a/src/SixLabors.Fonts/Unicode/ArabicJoiningType.cs b/src/SixLabors.Fonts/Unicode/ArabicJoiningType.cs index 4e10d0cee..e7d0ce844 100644 --- a/src/SixLabors.Fonts/Unicode/ArabicJoiningType.cs +++ b/src/SixLabors.Fonts/Unicode/ArabicJoiningType.cs @@ -41,5 +41,13 @@ public enum ArabicJoiningType /// /// Transparent (T): ignored when determining the joining relationship of surrounding characters. /// - Transparent + Transparent, + + /// + /// Non_Joining_Unassigned (X): the character is not listed in the joining data. + /// Its behaviour follows from its general category, resolved where the joining + /// class is read: a mark or a format character is transparent, anything else + /// does not join. + /// + Unlisted } diff --git a/src/SixLabors.Fonts/Unicode/BidiAlgorithm.cs b/src/SixLabors.Fonts/Unicode/BidiAlgorithm.cs index 832543fc5..335f8fac0 100644 --- a/src/SixLabors.Fonts/Unicode/BidiAlgorithm.cs +++ b/src/SixLabors.Fonts/Unicode/BidiAlgorithm.cs @@ -178,12 +178,6 @@ public BidiAlgorithm() { } - /// - /// Gets a per-thread instance that can be re-used as often - /// as necessary. - /// - public static ThreadLocal Instance { get; } = new ThreadLocal(() => new BidiAlgorithm()); - /// /// Gets the resolved levels. /// @@ -267,9 +261,12 @@ public void Process( else { this.ResolvedLevels = this.resolvedLevelsBuffer.Add(this.originalTypes.Length); - this.ResolvedLevels.Fill(this.paragraphEmbeddingLevel); } + // Every character begins at the paragraph level. The explicit and implicit + // rules below replace only the levels to which their conditions apply. + this.ResolvedLevels.Fill(this.paragraphEmbeddingLevel); + // Resolve explicit embedding levels (Rules X1-X8) this.ResolveExplicitEmbeddingLevels(); @@ -312,11 +309,12 @@ public sbyte ResolveEmbeddingLevel(ReadOnlyArraySlice data) case BidiCharacterType.LeftToRightIsolate: case BidiCharacterType.RightToLeftIsolate: // Skip isolate pairs - // (Because we're working with a slice, we need to adjust the indices - // we're using for the isolatePairs map) - if (this.isolatePairs.TryGetValue(data.Start + i, out i)) + // Isolate pairs are indexed relative to the paragraph slice. A + // nested slice therefore needs its offset within that paragraph. + int paragraphOffset = data.Start - this.originalTypes.Start; + if (this.isolatePairs.TryGetValue(paragraphOffset + i, out int isolateEnd)) { - i -= data.Start; + i = isolateEnd - paragraphOffset; } else { diff --git a/src/SixLabors.Fonts/Unicode/BidiData.AsciiTables.Generated.cs b/src/SixLabors.Fonts/Unicode/BidiData.AsciiTables.Generated.cs new file mode 100644 index 000000000..3243d8b60 --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/BidiData.AsciiTables.Generated.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +namespace SixLabors.Fonts.Unicode; + +/// +/// Contains the generated ASCII bidi classification tables. +/// +internal partial class BidiData +{ + /// + /// Gets the bidi character type for every ASCII value as compile-time constant + /// data the runtime reads directly from metadata. ASCII contains no embedding, + /// override, or isolate initiators, so the fast path never needs the flag + /// bookkeeping the general path performs. Generated from the UnicodeData bidi + /// category field; a test asserts the general classification path agrees. + /// + public static ReadOnlySpan AsciiCharacterTypes => + [ + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0B, 0x0A, 0x0B, 0x0C, 0x0A, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x0B, + 0x0C, 0x0D, 0x0D, 0x05, 0x05, 0x05, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x04, 0x07, 0x04, 0x07, 0x07, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x07, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x0D, 0x0D, 0x0D, 0x09 + ]; + + /// + /// Gets the paired bracket type for every ASCII value as compile-time constant + /// data; the ASCII brackets are the parentheses, square brackets, and curly + /// braces. Generated from BidiBrackets; a test asserts the general + /// classification path agrees. + /// + public static ReadOnlySpan AsciiPairedBracketTypes => + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00 + ]; + + /// + /// Gets the paired bracket value for every ASCII value as compile-time constant + /// data: the canonical pairing key the bracket-pair stage matches opening and + /// closing brackets by, zero for non-brackets. Generated from BidiBrackets; a + /// test asserts the general classification path agrees. + /// + public static ReadOnlySpan AsciiPairedBracketValues => + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x5D, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x7D, 0x00, 0x00 + ]; +} diff --git a/src/SixLabors.Fonts/Unicode/BidiData.cs b/src/SixLabors.Fonts/Unicode/BidiData.cs index 6133a6889..420b4dc19 100644 --- a/src/SixLabors.Fonts/Unicode/BidiData.cs +++ b/src/SixLabors.Fonts/Unicode/BidiData.cs @@ -1,21 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.InteropServices; + namespace SixLabors.Fonts.Unicode; /// /// Represents a unicode string and all associated attributes /// for each character required for the Bidi algorithm /// -internal class BidiData +internal partial class BidiData { + /// + /// The carriage return code point. + /// + private const int CarriageReturn = 0x000D; + + /// + /// The line feed code point. + /// + private const int LineFeed = 0x000A; + private ArrayBuilder types; private ArrayBuilder pairedBracketTypes; private ArrayBuilder pairedBracketValues; private ArrayBuilder savedTypes; private ArrayBuilder savedPairedBracketTypes; private ArrayBuilder tempLevelBuffer; - private readonly List paragraphPositions = new(); + private readonly List paragraphEnds = new(); public sbyte ParagraphEmbeddingLevel { get; private set; } @@ -25,6 +37,12 @@ internal class BidiData public bool HasIsolates { get; private set; } + /// + /// Gets the code point positions immediately after each newline function. + /// A carriage-return and line-feed pair contributes one position after the pair. + /// + public ReadOnlySpan ParagraphEnds => CollectionsMarshal.AsSpan(this.paragraphEnds); + /// /// Gets the length of the data held by the BidiData /// @@ -35,6 +53,42 @@ internal class BidiData /// public ArraySlice Types { get; private set; } + /// + /// Gets a value indicating whether every resolved level is provably zero in a + /// left-to-right paragraph, allowing the bidirectional algorithm to be skipped. + /// True only when the text contains no embedding or isolate initiators and no + /// codepoint whose type can raise an embedding level: strong right-to-left letters + /// and Arabic numbers force non-zero levels, and boundary neutrals are excluded + /// conservatively because their resolved levels depend on removed-character + /// handling. Everything else (L, EN, separators, whitespace, neutrals, NSM) keeps + /// level zero. + /// + public bool IsUniformLeftToRight + { + get + { + if (this.HasEmbeddings || this.HasIsolates) + { + return false; + } + + ArraySlice types = this.Types; + for (int i = 0; i < types.Length; i++) + { + BidiCharacterType type = types[i]; + if (type is BidiCharacterType.RightToLeft + or BidiCharacterType.ArabicLetter + or BidiCharacterType.ArabicNumber + or BidiCharacterType.BoundaryNeutral) + { + return false; + } + } + + return true; + } + } + /// /// Gets the paired bracket type for each code point /// @@ -66,7 +120,7 @@ public void Init(ReadOnlySpan text, sbyte paragraphEmbeddingLevel) this.pairedBracketTypes.Length = length; this.pairedBracketValues.Length = length; - this.paragraphPositions.Clear(); + this.paragraphEnds.Clear(); this.ParagraphEmbeddingLevel = paragraphEmbeddingLevel; // Resolve the BidiCharacterType, paired bracket type and paired @@ -76,10 +130,49 @@ public void Init(ReadOnlySpan text, sbyte paragraphEmbeddingLevel) this.HasIsolates = false; int i = 0; + bool previousWasCarriageReturn = false; var codePointEnumerator = new SpanCodePointEnumerator(text); while (codePointEnumerator.MoveNext()) { CodePoint codePoint = codePointEnumerator.Current; + + if (CodePoint.IsNewLine(codePoint)) + { + if (codePoint.Value == LineFeed && previousWasCarriageReturn) + { + // CRLF is one newline function, so extend the boundary recorded + // for CR instead of introducing an empty paragraph between them. + this.paragraphEnds[^1] = i + 1; + } + else + { + this.paragraphEnds.Add(i + 1); + } + } + + previousWasCarriageReturn = codePoint.Value == CarriageReturn; + + // ASCII values answer from the precomputed tables: one classification, + // one bracket type, and one pairing value per code point instead of the + // property lookups below. ASCII contains no embedding or isolate + // initiators, so the flag bookkeeping is skipped as well. + if (codePoint.IsAscii) + { + int asciiValue = codePoint.Value; + this.types[i] = (BidiCharacterType)AsciiCharacterTypes[asciiValue]; + + BidiPairedBracketType asciiPbt = (BidiPairedBracketType)AsciiPairedBracketTypes[asciiValue]; + this.pairedBracketTypes[i] = asciiPbt; + if (asciiPbt != BidiPairedBracketType.None) + { + this.pairedBracketValues[i] = AsciiPairedBracketValues[asciiValue]; + this.HasBrackets = true; + } + + i++; + continue; + } + BidiClass bidi = CodePoint.GetBidiClass(codePoint); // Look up BidiCharacterType diff --git a/src/SixLabors.Fonts/Unicode/BidiRun.cs b/src/SixLabors.Fonts/Unicode/BidiRun.cs index 03a8377d1..4a81c9967 100644 --- a/src/SixLabors.Fonts/Unicode/BidiRun.cs +++ b/src/SixLabors.Fonts/Unicode/BidiRun.cs @@ -29,36 +29,6 @@ public BidiRun(BidiCharacterType direction, int level, int start, int length) public override string ToString() => $"{this.Start} - {this.End} - {this.Direction}"; - public static IEnumerable CoalesceLevels(ReadOnlyArraySlice levels) - { - if (levels.Length == 0) - { - yield break; - } - - int startRun = 0; - sbyte runLevel = levels[0]; - BidiCharacterType direction; - for (int i = 1; i < levels.Length; i++) - { - if (levels[i] == runLevel) - { - continue; - } - - // End of this run - direction = (runLevel & 0x01) == 0 ? BidiCharacterType.LeftToRight : BidiCharacterType.RightToLeft; - yield return new BidiRun(direction, runLevel, startRun, i - startRun); - - // Move to next run - startRun = i; - runLevel = levels[i]; - } - - direction = (runLevel & 0x01) == 0 ? BidiCharacterType.LeftToRight : BidiCharacterType.RightToLeft; - yield return new BidiRun(direction, runLevel, startRun, levels.Length - startRun); - } - public override bool Equals(object? obj) => obj is BidiRun run && this.Equals(run); diff --git a/src/SixLabors.Fonts/Unicode/CodePoint.Normalization.cs b/src/SixLabors.Fonts/Unicode/CodePoint.Normalization.cs new file mode 100644 index 000000000..635f703b2 --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/CodePoint.Normalization.cs @@ -0,0 +1,329 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using SixLabors.Fonts.Unicode.Resources; + +namespace SixLabors.Fonts.Unicode; + +/// +/// Canonical decomposition, canonical composition, and the combining classes that +/// order the marks between the two. +/// +/// Sources. The tables both searches read are generated from the Unicode Character +/// Database; see for which files and which +/// fields. The Hangul arithmetic that stands in for those tables is the Hangul +/// syllable composition and decomposition of UAX #15, and follows +/// _hb_ucd_decompose_hangul and _hb_ucd_compose_hangul in +/// hb-ucd.cc of HarfBuzz 14.2.1, including the order the two cases are tested +/// in. +/// +/// +public readonly partial struct CodePoint +{ + /// + /// The number of bits each scalar value occupies in a packed table entry. + /// + private const int NormalizationEntryShift = 21; + + /// + /// The mask of one scalar value within a packed table entry. + /// + private const ulong NormalizationEntryMask = (1UL << NormalizationEntryShift) - 1; + + /// + /// The number of bytes one packed table entry occupies. + /// + private const int NormalizationEntrySize = sizeof(ulong); + + /// + /// The number of low code-point bits covered by one decomposition index page. + /// + private const int DecompositionPageShift = 7; + + /// + /// The number of bytes occupied by one decomposition page boundary. + /// + private const int DecompositionPageBoundarySize = sizeof(ushort); + + /// + /// The first Hangul leading consonant. + /// + private const uint HangulLeadBase = 0x1100; + + /// + /// The first Hangul vowel. + /// + private const uint HangulVowelBase = 0x1161; + + /// + /// The Hangul trailing consonant base, one below the first trailing consonant + /// so that the zero offset stands for a syllable that has none. + /// + private const uint HangulTrailBase = 0x11A7; + + /// + /// The first Hangul syllable. + /// + private const uint HangulSyllableBase = 0xAC00; + + /// + /// The number of Hangul leading consonants. + /// + private const uint HangulLeadCount = 19; + + /// + /// The number of Hangul vowels. + /// + private const uint HangulVowelCount = 21; + + /// + /// The number of Hangul trailing consonant slots, counting the empty one that + /// stands for a syllable without a trailing consonant. + /// + private const uint HangulTrailCount = 28; + + /// + /// The number of syllables that share one leading consonant. + /// + private const uint HangulVowelTrailCount = HangulVowelCount * HangulTrailCount; + + /// + /// The number of Hangul syllables. + /// + private const uint HangulSyllableCount = HangulLeadCount * HangulVowelTrailCount; + + /// + /// Gets the canonical combining class of the given code point. + /// + /// The code point to evaluate. + /// The canonical combining class. + public static int GetCanonicalCombiningClass(CodePoint codePoint) + => UnicodeData.GetCanonicalCombiningClass(codePoint.value); + + /// + /// Gets the class that orders a mark against the marks around it. + /// + /// This is the canonical combining class with the classes of several scripts + /// renumbered, so that sorting by it leaves the marks of those scripts in the + /// order they are drawn rather than the order the standard assigns. Hebrew, + /// Arabic, Syriac, Telugu, Thai, Lao and Tibetan all order differently from + /// their assigned classes. + /// + /// + /// The code point to evaluate. + /// The ordering class. + public static int GetMarkOrderingClass(CodePoint codePoint) + => UnicodeData.GetMarkOrderingClass(codePoint.value); + + /// + /// Tries to take the given code point apart into the pair of code points it is + /// canonically equivalent to. + /// + /// The code point to take apart. + /// When this method returns, contains the first code point. + /// + /// When this method returns, contains the second code point, or the default when + /// the code point stands for a single other one. + /// + /// if the code point comes apart. + public static bool TryDecompose(CodePoint codePoint, out CodePoint first, out CodePoint second) + { + uint value = codePoint.value; + + // A Hangul syllable comes apart by arithmetic rather than by table, which is + // why the tables leave the eleven thousand of them out. The syllables are + // laid out so that a syllable's index counts, from the outside in, its + // leading consonant, then its vowel, then its trailing consonant, with the + // zero trailing slot standing for a syllable that has none. + // + // Subtracting the base leaves that index. An unsigned compare against the + // count then rejects everything below the base too, because a value below it + // wraps to a very large number, so one compare does the work of two. + uint syllable = value - HangulSyllableBase; + if (syllable < HangulSyllableCount) + { + uint trail = syllable % HangulTrailCount; + if (trail != 0) + { + // The syllable carries a trailing consonant, so it parts into the + // same syllable without one, reached by clearing the trailing slot, + // and that consonant on its own. + first = new CodePoint(HangulSyllableBase + (syllable - trail)); + second = new CodePoint(HangulTrailBase + trail); + } + else + { + // The syllable is a leading consonant and a vowel only, so it parts + // into the two of them. Dividing by the number of syllables that + // share a leading consonant gives which consonant; what remains, + // divided by the trailing slots, gives which vowel. + first = new CodePoint(HangulLeadBase + (syllable / HangulVowelTrailCount)); + second = new CodePoint(HangulVowelBase + ((syllable % HangulVowelTrailCount) / HangulTrailCount)); + } + + return true; + } + + if (value < NormalizationData.FirstDecompositionCodePoint || value > NormalizationData.LastDecompositionCodePoint) + { + // The generated bounds reject the overwhelmingly common ASCII path + // before it constructs spans or reads a page boundary. + first = default; + second = default; + return false; + } + + // Every other character is looked up. The table is ordered by the character + // that decomposes, which the packing puts in the high bits, so the entries + // are searched as the plain integers they are. + ReadOnlySpan entries = NormalizationData.Decompositions; + int low; + int high; + if (value <= char.MaxValue) + { + // The generated boundaries narrow a Basic Multilingual Plane lookup to + // one 128-code-point page. Empty pages have equal boundaries and fail + // immediately; populated pages search only their handful of entries. + ReadOnlySpan pageStarts = NormalizationData.DecompositionPageStarts; + int pageOffset = (int)(value >> DecompositionPageShift) * DecompositionPageBoundarySize; + low = BinaryPrimitives.ReadUInt16LittleEndian(pageStarts[pageOffset..]); + high = BinaryPrimitives.ReadUInt16LittleEndian(pageStarts[(pageOffset + DecompositionPageBoundarySize)..]) - 1; + } + else + { + // Supplementary decompositions are sparse enough that the complete + // table remains smaller than a page index covering every Unicode plane. + low = 0; + high = (entries.Length / NormalizationEntrySize) - 1; + } + + while (low <= high) + { + // The midpoint is computed on unsigned values so that a large table + // cannot overflow the sum into a negative index. + int middle = (int)(((uint)low + (uint)high) >> 1); + ulong entry = ReadEntry(entries, middle); + uint key = (uint)(entry >> (NormalizationEntryShift * 2)); + + if (value < key) + { + high = middle - 1; + } + else if (value > key) + { + low = middle + 1; + } + else + { + // The two parts sit in the lower lanes. A singleton decomposition + // stores zero in the last lane, which the caller reads as absent. + first = new CodePoint((uint)((entry >> NormalizationEntryShift) & NormalizationEntryMask)); + second = new CodePoint((uint)(entry & NormalizationEntryMask)); + return true; + } + } + + first = default; + second = default; + return false; + } + + /// + /// Tries to join the given pair of code points into the single code point they + /// are canonically equivalent to. + /// + /// The first code point. + /// The second code point. + /// When this method returns, contains the joined code point. + /// if the pair joins. + public static bool TryCompose(CodePoint first, CodePoint second, out CodePoint composed) + { + uint a = first.value; + uint b = second.value; + + // Hangul joins by the arithmetic that takes a syllable apart, run backwards, + // and in the same two cases. Each unsigned compare against a count also + // rejects everything below the base it subtracted, because a value below it + // wraps to a very large number. + + // A leading consonant followed by a vowel builds the syllable that has no + // trailing consonant, by laying the two out at their strides. + uint lead = a - HangulLeadBase; + uint vowel = b - HangulVowelBase; + if (lead < HangulLeadCount && vowel < HangulVowelCount) + { + composed = new CodePoint(HangulSyllableBase + (lead * HangulVowelTrailCount) + (vowel * HangulTrailCount)); + return true; + } + + // A syllable followed by a trailing consonant fills that syllable's empty + // trailing slot. The syllable must have an empty one, which is what a zero + // remainder against the trailing stride means; a syllable that already + // carries a trailing consonant takes no second one. + uint syllable = a - HangulSyllableBase; + uint trail = b - HangulTrailBase; + if (syllable < HangulSyllableCount && syllable % HangulTrailCount == 0 + && trail is > 0 and < HangulTrailCount) + { + composed = new CodePoint(a + trail); + return true; + } + + // Every other pair is looked up. The table is ordered by the pair, which the + // packing puts in the high lanes, so packing the pair the same way turns the + // two-value search into one integer comparison per step. + ulong sought = ((ulong)a << NormalizationEntryShift) | b; + ReadOnlySpan entries = NormalizationData.Compositions; + int low = 0; + int high = (entries.Length / NormalizationEntrySize) - 1; + while (low <= high) + { + // The midpoint is computed on unsigned values so that a large table + // cannot overflow the sum into a negative index. + int middle = (int)(((uint)low + (uint)high) >> 1); + ulong entry = ReadEntry(entries, middle); + + // Shifting the joined character out of the low lane leaves exactly the + // packed pair, which is the key being searched for. + ulong key = entry >> NormalizationEntryShift; + + if (sought < key) + { + high = middle - 1; + } + else if (sought > key) + { + low = middle + 1; + } + else + { + // A pair the table holds always joins: a pair that must not be put + // back together is left out of the table when it is generated, so + // nothing needs to be excluded here. + composed = new CodePoint((uint)(entry & NormalizationEntryMask)); + return true; + } + } + + composed = default; + return false; + } + + /// + /// Reads one packed entry from a normalization table. + /// + /// The tables are held as bytes because only a span of a one-byte type is a blob + /// in the assembly's read-only data; a span of a wider type would be a fresh + /// array on every access, allocating once per lookup. Reading the eight bytes back + /// is a single load on a little-endian machine. + /// + /// + /// The table. + /// The zero-based index of the entry. + /// The packed entry. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong ReadEntry(ReadOnlySpan entries, int index) + => BinaryPrimitives.ReadUInt64LittleEndian(entries[(index * NormalizationEntrySize)..]); +} diff --git a/src/SixLabors.Fonts/Unicode/CodePoint.cs b/src/SixLabors.Fonts/Unicode/CodePoint.cs index ed37a4347..a7f4cecf8 100644 --- a/src/SixLabors.Fonts/Unicode/CodePoint.cs +++ b/src/SixLabors.Fonts/Unicode/CodePoint.cs @@ -15,7 +15,7 @@ namespace SixLabors.Fonts.Unicode; /// assuming that the underlying instance is well-formed. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] -public readonly struct CodePoint : IComparable, IComparable, IEquatable +public readonly partial struct CodePoint : IComparable, IComparable, IEquatable { // Supplementary plane code points are encoded as 2 UTF-16 code units private const int MaxUtf16CharsPerCodePoint = 2; diff --git a/src/SixLabors.Fonts/Unicode/LineBreakEnumerator.cs b/src/SixLabors.Fonts/Unicode/LineBreakEnumerator.cs index 2d93d4b4e..d17ceb3cb 100644 --- a/src/SixLabors.Fonts/Unicode/LineBreakEnumerator.cs +++ b/src/SixLabors.Fonts/Unicode/LineBreakEnumerator.cs @@ -182,6 +182,37 @@ internal LineBreakEnumerator(ReadOnlySpan source, bool tailorUrls) this.next = LineBreakCodePoint.CreateSentinel(StartOfText, 0, 0); } + /// + /// Initializes a new instance of the struct that resumes a + /// previously captured enumeration over the same text. + /// + /// + /// The enumerator is a ref struct, so a caller that must survive between pulls cannot hold it + /// directly. Capturing the value state instead lets such a caller continue one enumeration + /// candidate by candidate — the way browsers query their line break iterator lazily during + /// line filling — without restarting the rule window from the start of text. + /// + /// The same source text the state was captured over. + /// Whether to apply layout-level URL solidus tailoring. + /// The state captured from the enumeration being resumed. + internal LineBreakEnumerator(ReadOnlySpan source, bool tailorUrls, in State state) + : this() + { + this.source = source; + this.tailorUrls = tailorUrls; + this.charPosition = state.CharPosition; + this.pointPosition = state.PointPosition; + this.endOfTextPushed = state.EndOfTextPushed; + this.previousBreakPosition = state.PreviousBreakPosition; + this.previous = state.Previous; + this.current = state.Current; + this.next = state.Next; + this.lb8 = state.Lb8; + this.spaces = state.Spaces; + this.regionalIndicatorCount = state.RegionalIndicatorCount; + this.urlTailoringState = state.UrlTailoringState; + } + private enum BreakAction { /// @@ -270,6 +301,28 @@ public bool MoveNext() } } + /// + /// Captures the value state of this enumeration so a later + /// call can resume it. + /// is not part of the state: a resumed enumerator exposes a result only + /// after its own succeeds. + /// + /// The resumable state. + internal readonly State CaptureState() => new() + { + CharPosition = this.charPosition, + PointPosition = this.pointPosition, + EndOfTextPushed = this.endOfTextPushed, + PreviousBreakPosition = this.previousBreakPosition, + Previous = this.previous, + Current = this.current, + Next = this.next, + Lb8 = this.lb8, + Spaces = this.spaces, + RegionalIndicatorCount = this.regionalIndicatorCount, + UrlTailoringState = this.urlTailoringState, + }; + /// /// Decodes the next UTF-16 code point, maps its line break class according to LB1, /// and packages the additional context needed by later rules. @@ -1758,7 +1811,7 @@ private static bool IsAsciiW(CodePoint codePoint) /// ://, or a www. prefix at a host-label boundary. Once a run is URL-like, later /// solidus boundaries in that run can use the tailored behavior without rescanning the text. /// - private struct UrlTailoringState + public struct UrlTailoringState { /// /// Length of the current ASCII URI-scheme candidate, or zero when no scheme is active. @@ -1936,7 +1989,7 @@ private void UpdateWwwPrefixState(CodePoint codePoint, ref bool currentIsUrlLike /// original span, trim trailing whitespace for measurement, and perform bounded lookahead/lookbehind /// without allocating intermediate collections. /// - private struct LineBreakCodePoint + public struct LineBreakCodePoint { /// /// Initializes a new instance of the struct for a real code point. @@ -2085,4 +2138,68 @@ private LineBreakCodePoint(int sentinel, int length, int charEnd) [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool HasValue(int value) => !this.IsSentinel && this.CodePoint.Value == value; } + + /// + /// The complete mutable value state of an enumeration: the source position, the + /// three-code-point rule window, and the cross-boundary rule flags. Every mutable + /// field of the enumerator except the source span must appear here or a resumed + /// enumeration silently diverges from an uninterrupted one. + /// + public struct State + { + /// + /// The captured . + /// + public int CharPosition; + + /// + /// The captured . + /// + public int PointPosition; + + /// + /// The captured . + /// + public bool EndOfTextPushed; + + /// + /// The captured . + /// + public int PreviousBreakPosition; + + /// + /// The captured window item. + /// + public LineBreakCodePoint Previous; + + /// + /// The captured window item. + /// + public LineBreakCodePoint Current; + + /// + /// The captured window item. + /// + public LineBreakCodePoint Next; + + /// + /// The captured flag. + /// + public bool Lb8; + + /// + /// The captured flag. + /// + public bool Spaces; + + /// + /// The captured . + /// + public int RegionalIndicatorCount; + + /// + /// The captured . + /// + public UrlTailoringState UrlTailoringState; + } } diff --git a/src/SixLabors.Fonts/Unicode/MemoryExtensions.cs b/src/SixLabors.Fonts/Unicode/MemoryExtensions.cs index b1554a24d..bae33497c 100644 --- a/src/SixLabors.Fonts/Unicode/MemoryExtensions.cs +++ b/src/SixLabors.Fonts/Unicode/MemoryExtensions.cs @@ -223,8 +223,39 @@ public static int GetTerminalCellWidth(this Span span, TerminalWidthOption /// The number of grapheme clusters. public static int GetGraphemeCount(this ReadOnlySpan span) { + // ASCII text has single-char clusters except the CR LF pair: no ASCII + // character extends, prepends, or joins, and controls break on both sides. + // The count is therefore the length minus the CR LF pairs, found with two + // vectorized scans instead of the boundary state machine. + if (System.Text.Ascii.IsValid(span)) + { + int pairs = 0; + int searchStart = 0; + while (true) + { + int offset = span[searchStart..].IndexOf('\r'); + if (offset < 0) + { + break; + } + + int absolute = searchStart + offset; + if (absolute + 1 < span.Length && span[absolute + 1] == '\n') + { + pairs++; + searchStart = absolute + 2; + } + else + { + searchStart = absolute + 1; + } + } + + return span.Length - pairs; + } + int count = 0; - SpanGraphemeEnumerator enumerator = new(span); + SpanGraphemeEnumerator enumerator = new(span, true); while (enumerator.MoveNext()) { count++; @@ -241,7 +272,7 @@ public static int GetGraphemeCount(this ReadOnlySpan span) public static int GetGraphemeCount(this Span span) { int count = 0; - SpanGraphemeEnumerator enumerator = new(span); + SpanGraphemeEnumerator enumerator = new(span, true); while (enumerator.MoveNext()) { count++; diff --git a/src/SixLabors.Fonts/Unicode/Resources/ArabicFallbackData.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/ArabicFallbackData.Generated.cs new file mode 100644 index 000000000..8343a938b --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/Resources/ArabicFallbackData.Generated.cs @@ -0,0 +1,347 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System; +using System.Buffers.Binary; + +namespace SixLabors.Fonts.Unicode.Resources +{ + /// + /// Provides Arabic presentation forms and required ligatures for fonts without substitution features. + /// + internal static class ArabicFallbackData + { + /// + /// The number of presentation forms stored for each character. + /// + public const int PresentationFormCount = 4; + + /// + /// The number of bytes in one character and its presentation forms. + /// + private const int PresentationEntrySize = sizeof(ushort) * (PresentationFormCount + 1); + + /// + /// The byte offset of the first presentation form in an entry. + /// + private const int FirstPresentationFormOffset = sizeof(ushort); + + /// + /// The number of bytes in one two-character ligature entry. + /// + public const int TwoCharacterLigatureEntrySize = sizeof(ushort) * 3; + + /// + /// The number of bytes in one three-character ligature entry. + /// + public const int ThreeCharacterLigatureEntrySize = sizeof(ushort) * 4; + + /// + /// Gets the requested presentation form for an Arabic character. + /// + /// The Arabic character. + /// The zero-based form index: initial, medial, final, or isolated. + /// The presentation-form character, or zero when no such form exists. + public static ushort GetPresentationForm(int codePoint, int formIndex) + { + ReadOnlySpan forms = PresentationForms; + int low = 0; + int high = (forms.Length / PresentationEntrySize) - 1; + while (low <= high) + { + int middle = low + ((high - low) >> 1); + int offset = middle * PresentationEntrySize; + ushort candidate = BinaryPrimitives.ReadUInt16LittleEndian(forms.Slice(offset, sizeof(ushort))); + if (codePoint < candidate) + { + high = middle - 1; + } + else if (codePoint > candidate) + { + low = middle + 1; + } + else + { + int formOffset = offset + FirstPresentationFormOffset + (formIndex * sizeof(ushort)); + return BinaryPrimitives.ReadUInt16LittleEndian(forms.Slice(formOffset, sizeof(ushort))); + } + } + + return 0; + } + + /// + /// The Arabic character rows in initial, medial, final, and isolated order. + /// + private static ReadOnlySpan PresentationForms => + [ + 0x21, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE, // U+0621 ARABIC LETTER HAMZA ISOLATED FORM + 0x22, 0x06, 0x00, 0x00, 0x00, 0x00, 0x82, 0xFE, 0x81, 0xFE, // U+0622 ARABIC LETTER ALEF WITH MADDA ABOVE + 0x23, 0x06, 0x00, 0x00, 0x00, 0x00, 0x84, 0xFE, 0x83, 0xFE, // U+0623 ARABIC LETTER ALEF WITH HAMZA ABOVE + 0x24, 0x06, 0x00, 0x00, 0x00, 0x00, 0x86, 0xFE, 0x85, 0xFE, // U+0624 ARABIC LETTER WAW WITH HAMZA ABOVE + 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x88, 0xFE, 0x87, 0xFE, // U+0625 ARABIC LETTER ALEF WITH HAMZA BELOW + 0x26, 0x06, 0x8B, 0xFE, 0x8C, 0xFE, 0x8A, 0xFE, 0x89, 0xFE, // U+0626 ARABIC LETTER YEH WITH HAMZA ABOVE + 0x27, 0x06, 0x00, 0x00, 0x00, 0x00, 0x8E, 0xFE, 0x8D, 0xFE, // U+0627 ARABIC LETTER ALEF + 0x28, 0x06, 0x91, 0xFE, 0x92, 0xFE, 0x90, 0xFE, 0x8F, 0xFE, // U+0628 ARABIC LETTER BEH + 0x29, 0x06, 0x00, 0x00, 0x00, 0x00, 0x94, 0xFE, 0x93, 0xFE, // U+0629 ARABIC LETTER TEH MARBUTA + 0x2A, 0x06, 0x97, 0xFE, 0x98, 0xFE, 0x96, 0xFE, 0x95, 0xFE, // U+062A ARABIC LETTER TEH + 0x2B, 0x06, 0x9B, 0xFE, 0x9C, 0xFE, 0x9A, 0xFE, 0x99, 0xFE, // U+062B ARABIC LETTER THEH + 0x2C, 0x06, 0x9F, 0xFE, 0xA0, 0xFE, 0x9E, 0xFE, 0x9D, 0xFE, // U+062C ARABIC LETTER JEEM + 0x2D, 0x06, 0xA3, 0xFE, 0xA4, 0xFE, 0xA2, 0xFE, 0xA1, 0xFE, // U+062D ARABIC LETTER HAH + 0x2E, 0x06, 0xA7, 0xFE, 0xA8, 0xFE, 0xA6, 0xFE, 0xA5, 0xFE, // U+062E ARABIC LETTER KHAH + 0x2F, 0x06, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xFE, 0xA9, 0xFE, // U+062F ARABIC LETTER DAL + 0x30, 0x06, 0x00, 0x00, 0x00, 0x00, 0xAC, 0xFE, 0xAB, 0xFE, // U+0630 ARABIC LETTER THAL + 0x31, 0x06, 0x00, 0x00, 0x00, 0x00, 0xAE, 0xFE, 0xAD, 0xFE, // U+0631 ARABIC LETTER REH + 0x32, 0x06, 0x00, 0x00, 0x00, 0x00, 0xB0, 0xFE, 0xAF, 0xFE, // U+0632 ARABIC LETTER ZAIN + 0x33, 0x06, 0xB3, 0xFE, 0xB4, 0xFE, 0xB2, 0xFE, 0xB1, 0xFE, // U+0633 ARABIC LETTER SEEN + 0x34, 0x06, 0xB7, 0xFE, 0xB8, 0xFE, 0xB6, 0xFE, 0xB5, 0xFE, // U+0634 ARABIC LETTER SHEEN + 0x35, 0x06, 0xBB, 0xFE, 0xBC, 0xFE, 0xBA, 0xFE, 0xB9, 0xFE, // U+0635 ARABIC LETTER SAD + 0x36, 0x06, 0xBF, 0xFE, 0xC0, 0xFE, 0xBE, 0xFE, 0xBD, 0xFE, // U+0636 ARABIC LETTER DAD + 0x37, 0x06, 0xC3, 0xFE, 0xC4, 0xFE, 0xC2, 0xFE, 0xC1, 0xFE, // U+0637 ARABIC LETTER TAH + 0x38, 0x06, 0xC7, 0xFE, 0xC8, 0xFE, 0xC6, 0xFE, 0xC5, 0xFE, // U+0638 ARABIC LETTER ZAH + 0x39, 0x06, 0xCB, 0xFE, 0xCC, 0xFE, 0xCA, 0xFE, 0xC9, 0xFE, // U+0639 ARABIC LETTER AIN + 0x3A, 0x06, 0xCF, 0xFE, 0xD0, 0xFE, 0xCE, 0xFE, 0xCD, 0xFE, // U+063A ARABIC LETTER GHAIN + 0x3B, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+063B + 0x3C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+063C + 0x3D, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+063D + 0x3E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+063E + 0x3F, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+063F + 0x40, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0640 + 0x41, 0x06, 0xD3, 0xFE, 0xD4, 0xFE, 0xD2, 0xFE, 0xD1, 0xFE, // U+0641 ARABIC LETTER FEH + 0x42, 0x06, 0xD7, 0xFE, 0xD8, 0xFE, 0xD6, 0xFE, 0xD5, 0xFE, // U+0642 ARABIC LETTER QAF + 0x43, 0x06, 0xDB, 0xFE, 0xDC, 0xFE, 0xDA, 0xFE, 0xD9, 0xFE, // U+0643 ARABIC LETTER KAF + 0x44, 0x06, 0xDF, 0xFE, 0xE0, 0xFE, 0xDE, 0xFE, 0xDD, 0xFE, // U+0644 ARABIC LETTER LAM + 0x45, 0x06, 0xE3, 0xFE, 0xE4, 0xFE, 0xE2, 0xFE, 0xE1, 0xFE, // U+0645 ARABIC LETTER MEEM + 0x46, 0x06, 0xE7, 0xFE, 0xE8, 0xFE, 0xE6, 0xFE, 0xE5, 0xFE, // U+0646 ARABIC LETTER NOON + 0x47, 0x06, 0xEB, 0xFE, 0xEC, 0xFE, 0xEA, 0xFE, 0xE9, 0xFE, // U+0647 ARABIC LETTER HEH + 0x48, 0x06, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xFE, 0xED, 0xFE, // U+0648 ARABIC LETTER WAW + 0x49, 0x06, 0xE8, 0xFB, 0xE9, 0xFB, 0xF0, 0xFE, 0xEF, 0xFE, // U+0649 ARABIC LETTER + 0x4A, 0x06, 0xF3, 0xFE, 0xF4, 0xFE, 0xF2, 0xFE, 0xF1, 0xFE, // U+064A ARABIC LETTER YEH + 0x4B, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+064B + 0x4C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+064C + 0x4D, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+064D + 0x4E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+064E + 0x4F, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+064F + 0x50, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0650 + 0x51, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0651 + 0x52, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0652 + 0x53, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0653 + 0x54, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0654 + 0x55, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0655 + 0x56, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0656 + 0x57, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0657 + 0x58, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0658 + 0x59, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0659 + 0x5A, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+065A + 0x5B, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+065B + 0x5C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+065C + 0x5D, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+065D + 0x5E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+065E + 0x5F, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+065F + 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0660 + 0x61, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0661 + 0x62, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0662 + 0x63, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0663 + 0x64, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0664 + 0x65, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0665 + 0x66, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0666 + 0x67, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0667 + 0x68, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0668 + 0x69, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0669 + 0x6A, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+066A + 0x6B, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+066B + 0x6C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+066C + 0x6D, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+066D + 0x6E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+066E + 0x6F, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+066F + 0x70, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0670 + 0x71, 0x06, 0x00, 0x00, 0x00, 0x00, 0x51, 0xFB, 0x50, 0xFB, // U+0671 ARABIC LETTER ALEF WASLA + 0x72, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0672 + 0x73, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0673 + 0x74, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0674 + 0x75, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0675 + 0x76, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0676 + 0x77, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDD, 0xFB, // U+0677 ARABIC LETTER U WITH HAMZA ABOVE ISOLATED FORM + 0x78, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0678 + 0x79, 0x06, 0x68, 0xFB, 0x69, 0xFB, 0x67, 0xFB, 0x66, 0xFB, // U+0679 ARABIC LETTER TTEH + 0x7A, 0x06, 0x60, 0xFB, 0x61, 0xFB, 0x5F, 0xFB, 0x5E, 0xFB, // U+067A ARABIC LETTER TTEHEH + 0x7B, 0x06, 0x54, 0xFB, 0x55, 0xFB, 0x53, 0xFB, 0x52, 0xFB, // U+067B ARABIC LETTER BEEH + 0x7C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+067C + 0x7D, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+067D + 0x7E, 0x06, 0x58, 0xFB, 0x59, 0xFB, 0x57, 0xFB, 0x56, 0xFB, // U+067E ARABIC LETTER PEH + 0x7F, 0x06, 0x64, 0xFB, 0x65, 0xFB, 0x63, 0xFB, 0x62, 0xFB, // U+067F ARABIC LETTER TEHEH + 0x80, 0x06, 0x5C, 0xFB, 0x5D, 0xFB, 0x5B, 0xFB, 0x5A, 0xFB, // U+0680 ARABIC LETTER BEHEH + 0x81, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0681 + 0x82, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0682 + 0x83, 0x06, 0x78, 0xFB, 0x79, 0xFB, 0x77, 0xFB, 0x76, 0xFB, // U+0683 ARABIC LETTER NYEH + 0x84, 0x06, 0x74, 0xFB, 0x75, 0xFB, 0x73, 0xFB, 0x72, 0xFB, // U+0684 ARABIC LETTER DYEH + 0x85, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0685 + 0x86, 0x06, 0x7C, 0xFB, 0x7D, 0xFB, 0x7B, 0xFB, 0x7A, 0xFB, // U+0686 ARABIC LETTER TCHEH + 0x87, 0x06, 0x80, 0xFB, 0x81, 0xFB, 0x7F, 0xFB, 0x7E, 0xFB, // U+0687 ARABIC LETTER TCHEHEH + 0x88, 0x06, 0x00, 0x00, 0x00, 0x00, 0x89, 0xFB, 0x88, 0xFB, // U+0688 ARABIC LETTER DDAL + 0x89, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0689 + 0x8A, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+068A + 0x8B, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+068B + 0x8C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x85, 0xFB, 0x84, 0xFB, // U+068C ARABIC LETTER DAHAL + 0x8D, 0x06, 0x00, 0x00, 0x00, 0x00, 0x83, 0xFB, 0x82, 0xFB, // U+068D ARABIC LETTER DDAHAL + 0x8E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x87, 0xFB, 0x86, 0xFB, // U+068E ARABIC LETTER DUL + 0x8F, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+068F + 0x90, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0690 + 0x91, 0x06, 0x00, 0x00, 0x00, 0x00, 0x8D, 0xFB, 0x8C, 0xFB, // U+0691 ARABIC LETTER RREH + 0x92, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0692 + 0x93, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0693 + 0x94, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0694 + 0x95, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0695 + 0x96, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0696 + 0x97, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0697 + 0x98, 0x06, 0x00, 0x00, 0x00, 0x00, 0x8B, 0xFB, 0x8A, 0xFB, // U+0698 ARABIC LETTER JEH + 0x99, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0699 + 0x9A, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+069A + 0x9B, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+069B + 0x9C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+069C + 0x9D, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+069D + 0x9E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+069E + 0x9F, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+069F + 0xA0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06A0 + 0xA1, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06A1 + 0xA2, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06A2 + 0xA3, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06A3 + 0xA4, 0x06, 0x6C, 0xFB, 0x6D, 0xFB, 0x6B, 0xFB, 0x6A, 0xFB, // U+06A4 ARABIC LETTER VEH + 0xA5, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06A5 + 0xA6, 0x06, 0x70, 0xFB, 0x71, 0xFB, 0x6F, 0xFB, 0x6E, 0xFB, // U+06A6 ARABIC LETTER PEHEH + 0xA7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06A7 + 0xA8, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06A8 + 0xA9, 0x06, 0x90, 0xFB, 0x91, 0xFB, 0x8F, 0xFB, 0x8E, 0xFB, // U+06A9 ARABIC LETTER KEHEH + 0xAA, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06AA + 0xAB, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06AB + 0xAC, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06AC + 0xAD, 0x06, 0xD5, 0xFB, 0xD6, 0xFB, 0xD4, 0xFB, 0xD3, 0xFB, // U+06AD ARABIC LETTER NG + 0xAE, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06AE + 0xAF, 0x06, 0x94, 0xFB, 0x95, 0xFB, 0x93, 0xFB, 0x92, 0xFB, // U+06AF ARABIC LETTER GAF + 0xB0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06B0 + 0xB1, 0x06, 0x9C, 0xFB, 0x9D, 0xFB, 0x9B, 0xFB, 0x9A, 0xFB, // U+06B1 ARABIC LETTER NGOEH + 0xB2, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06B2 + 0xB3, 0x06, 0x98, 0xFB, 0x99, 0xFB, 0x97, 0xFB, 0x96, 0xFB, // U+06B3 ARABIC LETTER GUEH + 0xB4, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06B4 + 0xB5, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06B5 + 0xB6, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06B6 + 0xB7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06B7 + 0xB8, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06B8 + 0xB9, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06B9 + 0xBA, 0x06, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFB, 0x9E, 0xFB, // U+06BA ARABIC LETTER NOON GHUNNA + 0xBB, 0x06, 0xA2, 0xFB, 0xA3, 0xFB, 0xA1, 0xFB, 0xA0, 0xFB, // U+06BB ARABIC LETTER RNOON + 0xBC, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06BC + 0xBD, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06BD + 0xBE, 0x06, 0xAC, 0xFB, 0xAD, 0xFB, 0xAB, 0xFB, 0xAA, 0xFB, // U+06BE ARABIC LETTER HEH DOACHASHMEE + 0xBF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06BF + 0xC0, 0x06, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xFB, 0xA4, 0xFB, // U+06C0 ARABIC LETTER HEH WITH YEH ABOVE + 0xC1, 0x06, 0xA8, 0xFB, 0xA9, 0xFB, 0xA7, 0xFB, 0xA6, 0xFB, // U+06C1 ARABIC LETTER HEH GOAL + 0xC2, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06C2 + 0xC3, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06C3 + 0xC4, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06C4 + 0xC5, 0x06, 0x00, 0x00, 0x00, 0x00, 0xE1, 0xFB, 0xE0, 0xFB, // U+06C5 ARABIC LETTER KIRGHIZ OE + 0xC6, 0x06, 0x00, 0x00, 0x00, 0x00, 0xDA, 0xFB, 0xD9, 0xFB, // U+06C6 ARABIC LETTER OE + 0xC7, 0x06, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xFB, 0xD7, 0xFB, // U+06C7 ARABIC LETTER U + 0xC8, 0x06, 0x00, 0x00, 0x00, 0x00, 0xDC, 0xFB, 0xDB, 0xFB, // U+06C8 ARABIC LETTER YU + 0xC9, 0x06, 0x00, 0x00, 0x00, 0x00, 0xE3, 0xFB, 0xE2, 0xFB, // U+06C9 ARABIC LETTER KIRGHIZ YU + 0xCA, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06CA + 0xCB, 0x06, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFB, 0xDE, 0xFB, // U+06CB ARABIC LETTER VE + 0xCC, 0x06, 0xFE, 0xFB, 0xFF, 0xFB, 0xFD, 0xFB, 0xFC, 0xFB, // U+06CC ARABIC LETTER FARSI YEH + 0xCD, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06CD + 0xCE, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06CE + 0xCF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06CF + 0xD0, 0x06, 0xE6, 0xFB, 0xE7, 0xFB, 0xE5, 0xFB, 0xE4, 0xFB, // U+06D0 ARABIC LETTER E + 0xD1, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+06D1 + 0xD2, 0x06, 0x00, 0x00, 0x00, 0x00, 0xAF, 0xFB, 0xAE, 0xFB, // U+06D2 ARABIC LETTER YEH BARREE + 0xD3, 0x06, 0x00, 0x00, 0x00, 0x00, 0xB1, 0xFB, 0xB0, 0xFB, // U+06D3 ARABIC LETTER YEH BARREE WITH HAMZA ABOVE + ]; + + /// + /// The three-character required ligatures. + /// + public static ReadOnlySpan ThreeCharacterLigatures => + [ + 0xDF, 0xFE, 0xE4, 0xFE, 0xA4, 0xFE, 0x88, 0xFD, // ARABIC LIGATURE LAM WITH MEEM WITH HAH INITIAL FORM + 0xDF, 0xFE, 0xE0, 0xFE, 0xEA, 0xFE, 0x01, 0xF2, // PUA ARABIC LIGATURE LELLAH ISOLATED FORM + 0xDF, 0xFE, 0xE4, 0xFE, 0xA0, 0xFE, 0x11, 0xF2, // PUA ARABIC LIGATURE LAM WITH MEEM WITH JEEM INITIAL FORM + ]; + + /// + /// The two-character required ligatures. + /// + public static ReadOnlySpan TwoCharacterLigatures => + [ + 0x91, 0xFE, 0xE2, 0xFE, 0x08, 0xFC, // ARABIC LIGATURE BEH WITH MEEM ISOLATED FORM + 0x91, 0xFE, 0xE4, 0xFE, 0x9F, 0xFC, // ARABIC LIGATURE BEH WITH MEEM INITIAL FORM + 0x91, 0xFE, 0xA0, 0xFE, 0x9C, 0xFC, // ARABIC LIGATURE BEH WITH JEEM INITIAL FORM + 0x91, 0xFE, 0xA4, 0xFE, 0x9D, 0xFC, // ARABIC LIGATURE BEH WITH HAH INITIAL FORM + 0x91, 0xFE, 0xA8, 0xFE, 0x9E, 0xFC, // ARABIC LIGATURE BEH WITH KHAH INITIAL FORM + 0x92, 0xFE, 0xAE, 0xFE, 0x6A, 0xFC, // ARABIC LIGATURE BEH WITH REH FINAL FORM + 0x92, 0xFE, 0xE6, 0xFE, 0x6D, 0xFC, // ARABIC LIGATURE BEH WITH NOON FINAL FORM + 0x92, 0xFE, 0xF2, 0xFE, 0x6F, 0xFC, // ARABIC LIGATURE BEH WITH YEH FINAL FORM + 0x97, 0xFE, 0xE2, 0xFE, 0x0E, 0xFC, // ARABIC LIGATURE TEH WITH MEEM ISOLATED FORM + 0x97, 0xFE, 0xE4, 0xFE, 0xA4, 0xFC, // ARABIC LIGATURE TEH WITH MEEM INITIAL FORM + 0x97, 0xFE, 0xA0, 0xFE, 0xA1, 0xFC, // ARABIC LIGATURE TEH WITH JEEM INITIAL FORM + 0x97, 0xFE, 0xA4, 0xFE, 0xA2, 0xFC, // ARABIC LIGATURE TEH WITH HAH INITIAL FORM + 0x97, 0xFE, 0xA8, 0xFE, 0xA3, 0xFC, // ARABIC LIGATURE TEH WITH KHAH INITIAL FORM + 0x98, 0xFE, 0xAE, 0xFE, 0x70, 0xFC, // ARABIC LIGATURE TEH WITH REH FINAL FORM + 0x98, 0xFE, 0xE6, 0xFE, 0x73, 0xFC, // ARABIC LIGATURE TEH WITH NOON FINAL FORM + 0x98, 0xFE, 0xF2, 0xFE, 0x75, 0xFC, // ARABIC LIGATURE TEH WITH YEH FINAL FORM + 0x9B, 0xFE, 0xE2, 0xFE, 0x12, 0xFC, // ARABIC LIGATURE THEH WITH MEEM ISOLATED FORM + 0x9F, 0xFE, 0xE4, 0xFE, 0xA8, 0xFC, // ARABIC LIGATURE JEEM WITH MEEM INITIAL FORM + 0xA3, 0xFE, 0xE4, 0xFE, 0xAA, 0xFC, // ARABIC LIGATURE HAH WITH MEEM INITIAL FORM + 0xA7, 0xFE, 0xE4, 0xFE, 0xAC, 0xFC, // ARABIC LIGATURE KHAH WITH MEEM INITIAL FORM + 0xB3, 0xFE, 0xE4, 0xFE, 0xB0, 0xFC, // ARABIC LIGATURE SEEN WITH MEEM INITIAL FORM + 0xB7, 0xFE, 0xE4, 0xFE, 0x30, 0xFD, // ARABIC LIGATURE SHEEN WITH MEEM INITIAL FORM + 0xD3, 0xFE, 0xF2, 0xFE, 0x32, 0xFC, // ARABIC LIGATURE FEH WITH YEH ISOLATED FORM + 0xDF, 0xFE, 0x9E, 0xFE, 0x3F, 0xFC, // ARABIC LIGATURE LAM WITH JEEM ISOLATED FORM + 0xDF, 0xFE, 0xA0, 0xFE, 0xC9, 0xFC, // ARABIC LIGATURE LAM WITH JEEM INITIAL FORM + 0xDF, 0xFE, 0xA2, 0xFE, 0x40, 0xFC, // ARABIC LIGATURE LAM WITH HAH ISOLATED FORM + 0xDF, 0xFE, 0xA4, 0xFE, 0xCA, 0xFC, // ARABIC LIGATURE LAM WITH HAH INITIAL FORM + 0xDF, 0xFE, 0xA6, 0xFE, 0x41, 0xFC, // ARABIC LIGATURE LAM WITH KHAH ISOLATED FORM + 0xDF, 0xFE, 0xA8, 0xFE, 0xCB, 0xFC, // ARABIC LIGATURE LAM WITH KHAH INITIAL FORM + 0xDF, 0xFE, 0xE2, 0xFE, 0x42, 0xFC, // ARABIC LIGATURE LAM WITH MEEM ISOLATED FORM + 0xDF, 0xFE, 0xE4, 0xFE, 0xCC, 0xFC, // ARABIC LIGATURE LAM WITH MEEM INITIAL FORM + 0xDF, 0xFE, 0xF0, 0xFE, 0x43, 0xFC, // ARABIC LIGATURE LAM WITH ALEF MAKSURA ISOLATED FORM + 0xDF, 0xFE, 0xF2, 0xFE, 0x44, 0xFC, // ARABIC LIGATURE LAM WITH YEH ISOLATED FORM + 0xDF, 0xFE, 0xEC, 0xFE, 0xCD, 0xFC, // ARABIC LIGATURE LAM WITH HEH INITIAL FORM + 0xDF, 0xFE, 0x82, 0xFE, 0xF5, 0xFE, // ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM + 0xDF, 0xFE, 0x84, 0xFE, 0xF7, 0xFE, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM + 0xDF, 0xFE, 0x88, 0xFE, 0xF9, 0xFE, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM + 0xDF, 0xFE, 0x8E, 0xFE, 0xFB, 0xFE, // ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM + 0xE0, 0xFE, 0xF0, 0xFE, 0x86, 0xFC, // ARABIC LIGATURE LAM WITH ALEF MAKSURA FINAL FORM + 0xE0, 0xFE, 0x82, 0xFE, 0xF6, 0xFE, // ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE FINAL FORM + 0xE0, 0xFE, 0x84, 0xFE, 0xF8, 0xFE, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE FINAL FORM + 0xE0, 0xFE, 0x88, 0xFE, 0xFA, 0xFE, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW FINAL FORM + 0xE0, 0xFE, 0x8E, 0xFE, 0xFC, 0xFE, // ARABIC LIGATURE LAM WITH ALEF FINAL FORM + 0xE3, 0xFE, 0xA0, 0xFE, 0xCE, 0xFC, // ARABIC LIGATURE MEEM WITH JEEM INITIAL FORM + 0xE3, 0xFE, 0xA4, 0xFE, 0xCF, 0xFC, // ARABIC LIGATURE MEEM WITH HAH INITIAL FORM + 0xE3, 0xFE, 0xA8, 0xFE, 0xD0, 0xFC, // ARABIC LIGATURE MEEM WITH KHAH INITIAL FORM + 0xE3, 0xFE, 0xE4, 0xFE, 0xD1, 0xFC, // ARABIC LIGATURE MEEM WITH MEEM INITIAL FORM + 0xE7, 0xFE, 0xE2, 0xFE, 0x4E, 0xFC, // ARABIC LIGATURE NOON WITH MEEM ISOLATED FORM + 0xE7, 0xFE, 0xE4, 0xFE, 0xD5, 0xFC, // ARABIC LIGATURE NOON WITH MEEM INITIAL FORM + 0xE7, 0xFE, 0xA0, 0xFE, 0xD2, 0xFC, // ARABIC LIGATURE NOON WITH JEEM INITIAL FORM + 0xE7, 0xFE, 0xA4, 0xFE, 0xD3, 0xFC, // ARABIC LIGATURE NOON WITH HAH INITIAL FORM + 0xE8, 0xFE, 0xF2, 0xFE, 0x8F, 0xFC, // ARABIC LIGATURE NOON WITH YEH FINAL FORM + 0xF3, 0xFE, 0xA0, 0xFE, 0xDA, 0xFC, // ARABIC LIGATURE YEH WITH JEEM INITIAL FORM + 0xF3, 0xFE, 0xA4, 0xFE, 0xDB, 0xFC, // ARABIC LIGATURE YEH WITH HAH INITIAL FORM + 0xF3, 0xFE, 0xA8, 0xFE, 0xDC, 0xFC, // ARABIC LIGATURE YEH WITH KHAH INITIAL FORM + 0xF3, 0xFE, 0xE4, 0xFE, 0xDD, 0xFC, // ARABIC LIGATURE YEH WITH MEEM INITIAL FORM + 0xF4, 0xFE, 0xAE, 0xFE, 0x91, 0xFC, // ARABIC LIGATURE YEH WITH REH FINAL FORM + 0xF4, 0xFE, 0xE6, 0xFE, 0x94, 0xFC, // ARABIC LIGATURE YEH WITH NOON FINAL FORM + ]; + + /// + /// The mark required ligatures. + /// + public static ReadOnlySpan MarkLigatures => + [ + 0x51, 0x06, 0x4C, 0x06, 0x5E, 0xFC, // ARABIC LIGATURE SHADDA WITH DAMMATAN ISOLATED FORM + 0x51, 0x06, 0x4E, 0x06, 0x60, 0xFC, // ARABIC LIGATURE SHADDA WITH FATHA ISOLATED FORM + 0x51, 0x06, 0x4F, 0x06, 0x61, 0xFC, // ARABIC LIGATURE SHADDA WITH DAMMA ISOLATED FORM + 0x51, 0x06, 0x50, 0x06, 0x62, 0xFC, // ARABIC LIGATURE SHADDA WITH KASRA ISOLATED FORM + 0x51, 0x06, 0x4B, 0x06, 0xEE, 0xF2, // PUA ARABIC LIGATURE SHADDA WITH FATHATAN ISOLATED FORM + ]; + } +} diff --git a/src/SixLabors.Fonts/Unicode/Resources/ArabicLegacyEncodingData.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/ArabicLegacyEncodingData.Generated.cs new file mode 100644 index 000000000..72e1e4627 --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/Resources/ArabicLegacyEncodingData.Generated.cs @@ -0,0 +1,620 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System; +using System.Buffers.Binary; + +namespace SixLabors.Fonts.Unicode.Resources +{ + /// + /// Maps Unicode characters to the private-use codes of legacy Arabic fonts. + /// + internal static class ArabicLegacyEncodingData + { + /// + /// The OS/2 version 0 font-page marker for Simplified Arabic. + /// + public const ushort SimplifiedArabicFontPage = 0xB200; + + /// + /// The OS/2 version 0 font-page marker for Traditional Arabic. + /// + public const ushort TraditionalArabicFontPage = 0xB300; + + /// + /// The number of bytes in one source and mapped-code pair. + /// + public const int MappingEntrySize = 4; + + /// + /// The byte offset of the mapped code within an entry. + /// + public const int MappedCodePointOffset = sizeof(ushort); + + /// + /// Maps a Unicode character to the private-use code used by the selected legacy font page. + /// + /// The legacy font-page marker. + /// The Unicode character to map. + /// The private-use code, or zero when the character has no mapping. + public static ushort GetMappedCodePoint(ushort fontPage, int codePoint) + { + ReadOnlySpan mappings = GetMappings(fontPage); + int low = 0; + int high = (mappings.Length / MappingEntrySize) - 1; + while (low <= high) + { + int middle = low + ((high - low) >> 1); + int offset = middle * MappingEntrySize; + ushort candidate = BinaryPrimitives.ReadUInt16LittleEndian(mappings.Slice(offset, sizeof(ushort))); + if (codePoint < candidate) + { + high = middle - 1; + } + else if (codePoint > candidate) + { + low = middle + 1; + } + else + { + return BinaryPrimitives.ReadUInt16LittleEndian(mappings.Slice(offset + MappedCodePointOffset, sizeof(ushort))); + } + } + + return 0; + } + + /// + /// Gets the packed mappings for the selected legacy font page. + /// + /// The legacy font-page marker. + /// The packed mappings, or an empty span for another font page. + public static ReadOnlySpan GetMappings(ushort fontPage) + => fontPage switch + { + SimplifiedArabicFontPage => SimplifiedMappings, + TraditionalArabicFontPage => TraditionalMappings, + _ => [] + }; + + /// + /// The simplified Arabic mappings. + /// + private static ReadOnlySpan SimplifiedMappings => + [ + 0x20, 0x00, 0x20, 0xF1, // U+0020 SPACE + 0x21, 0x00, 0x21, 0xF1, // U+0021 EXCLAMATION MARK + 0x22, 0x00, 0x22, 0xF1, // U+0022 QUOTATION MARK + 0x25, 0x00, 0x25, 0xF1, // U+0025 PERCENT SIGN + 0x28, 0x00, 0x28, 0xF1, // U+0028 LEFT PARENTHESIS + 0x29, 0x00, 0x29, 0xF1, // U+0029 RIGHT PARENTHESIS + 0x2A, 0x00, 0x2A, 0xF1, // U+002A ASTERISK + 0x2B, 0x00, 0x2B, 0xF1, // U+002B PLUS SIGN + 0x2C, 0x00, 0x5E, 0xF1, // U+002C COMMA + 0x2D, 0x00, 0x2D, 0xF1, // U+002D HYPHEN-MINUS + 0x2E, 0x00, 0x2E, 0xF1, // U+002E FULL STOP + 0x2F, 0x00, 0x2F, 0xF1, // U+002F SOLIDUS + 0x30, 0x00, 0xB0, 0xF1, // U+0030 DIGIT ZERO + 0x31, 0x00, 0xB1, 0xF1, // U+0031 DIGIT ONE + 0x32, 0x00, 0xB2, 0xF1, // U+0032 DIGIT TWO + 0x33, 0x00, 0xB3, 0xF1, // U+0033 DIGIT THREE + 0x34, 0x00, 0xB4, 0xF1, // U+0034 DIGIT FOUR + 0x35, 0x00, 0xB5, 0xF1, // U+0035 DIGIT FIVE + 0x36, 0x00, 0xB6, 0xF1, // U+0036 DIGIT SIX + 0x37, 0x00, 0xB7, 0xF1, // U+0037 DIGIT SEVEN + 0x38, 0x00, 0xB8, 0xF1, // U+0038 DIGIT EIGHT + 0x39, 0x00, 0xB9, 0xF1, // U+0039 DIGIT NINE + 0x3A, 0x00, 0x3A, 0xF1, // U+003A COLON + 0x3B, 0x00, 0x3B, 0xF1, // U+003B SEMICOLON + 0x3D, 0x00, 0x3D, 0xF1, // U+003D EQUALS SIGN + 0x3F, 0x00, 0x3F, 0xF1, // U+003F QUESTION MARK + 0x5B, 0x00, 0x5B, 0xF1, // U+005B LEFT SQUARE BRACKET + 0x5C, 0x00, 0x5C, 0xF1, // U+005C REVERSE SOLIDUS + 0x5D, 0x00, 0x5D, 0xF1, // U+005D RIGHT SQUARE BRACKET + 0xAB, 0x00, 0x23, 0xF1, // U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + 0xBB, 0x00, 0x24, 0xF1, // U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + 0xD7, 0x00, 0x26, 0xF1, // U+00D7 MULTIPLICATION SIGN + 0xF7, 0x00, 0x27, 0xF1, // U+00F7 DIVISION SIGN + 0x0C, 0x06, 0x2C, 0xF1, // U+060C ARABIC COMMA + 0x1B, 0x06, 0x3B, 0xF1, // U+061B ARABIC SEMICOLON + 0x1F, 0x06, 0x3F, 0xF1, // U+061F ARABIC QUESTION MARK + 0x21, 0x06, 0xAD, 0xF1, // U+0621 ARABIC LETTER HAMZA + 0x22, 0x06, 0x45, 0xF1, // U+0622 ARABIC LETTER ALEF WITH MADDA ABOVE + 0x23, 0x06, 0x43, 0xF1, // U+0623 ARABIC LETTER ALEF WITH HAMZA ABOVE + 0x24, 0x06, 0xBB, 0xF1, // U+0624 ARABIC LETTER WAW WITH HAMZA ABOVE + 0x25, 0x06, 0x47, 0xF1, // U+0625 ARABIC LETTER ALEF WITH HAMZA BELOW + 0x26, 0x06, 0xBA, 0xF1, // U+0626 ARABIC LETTER YEH WITH HAMZA ABOVE + 0x27, 0x06, 0x41, 0xF1, // U+0627 ARABIC LETTER ALEF + 0x28, 0x06, 0x4A, 0xF1, // U+0628 ARABIC LETTER BEH + 0x29, 0x06, 0xA9, 0xF1, // U+0629 ARABIC LETTER TEH MARBUTA + 0x2A, 0x06, 0x4C, 0xF1, // U+062A ARABIC LETTER TEH + 0x2B, 0x06, 0x4E, 0xF1, // U+062B ARABIC LETTER THEH + 0x2C, 0x06, 0x51, 0xF1, // U+062C ARABIC LETTER JEEM + 0x2D, 0x06, 0x54, 0xF1, // U+062D ARABIC LETTER HAH + 0x2E, 0x06, 0x57, 0xF1, // U+062E ARABIC LETTER KHAH + 0x2F, 0x06, 0x58, 0xF1, // U+062F ARABIC LETTER DAL + 0x30, 0x06, 0x59, 0xF1, // U+0630 ARABIC LETTER THAL + 0x31, 0x06, 0x5A, 0xF1, // U+0631 ARABIC LETTER REH + 0x32, 0x06, 0x60, 0xF1, // U+0632 ARABIC LETTER ZAIN + 0x33, 0x06, 0x62, 0xF1, // U+0633 ARABIC LETTER SEEN + 0x34, 0x06, 0x64, 0xF1, // U+0634 ARABIC LETTER SHEEN + 0x35, 0x06, 0x66, 0xF1, // U+0635 ARABIC LETTER SAD + 0x36, 0x06, 0x68, 0xF1, // U+0636 ARABIC LETTER DAD + 0x37, 0x06, 0x69, 0xF1, // U+0637 ARABIC LETTER TAH + 0x38, 0x06, 0x6A, 0xF1, // U+0638 ARABIC LETTER ZAH + 0x39, 0x06, 0x6E, 0xF1, // U+0639 ARABIC LETTER AIN + 0x3A, 0x06, 0x72, 0xF1, // U+063A ARABIC LETTER GHAIN + 0x3B, 0x06, 0x00, 0xF1, // U+063B ARABIC LETTER KEHEH WITH TWO DOTS ABOVE + 0x3C, 0x06, 0x00, 0xF1, // U+063C ARABIC LETTER KEHEH WITH THREE DOTS BELOW + 0x3D, 0x06, 0x00, 0xF1, // U+063D ARABIC LETTER FARSI YEH WITH INVERTED V + 0x3E, 0x06, 0x00, 0xF1, // U+063E ARABIC LETTER FARSI YEH WITH TWO DOTS ABOVE + 0x3F, 0x06, 0x00, 0xF1, // U+063F ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE + 0x40, 0x06, 0x5F, 0xF1, // U+0640 ARABIC TATWEEL + 0x41, 0x06, 0x75, 0xF1, // U+0641 ARABIC LETTER FEH + 0x42, 0x06, 0x78, 0xF1, // U+0642 ARABIC LETTER QAF + 0x43, 0x06, 0x7A, 0xF1, // U+0643 ARABIC LETTER KAF + 0x44, 0x06, 0x7C, 0xF1, // U+0644 ARABIC LETTER LAM + 0x45, 0x06, 0x7E, 0xF1, // U+0645 ARABIC LETTER MEEM + 0x46, 0x06, 0xE1, 0xF1, // U+0646 ARABIC LETTER NOON + 0x47, 0x06, 0xA4, 0xF1, // U+0647 ARABIC LETTER HEH + 0x48, 0x06, 0xA5, 0xF1, // U+0648 ARABIC LETTER WAW + 0x49, 0x06, 0xAC, 0xF1, // U+0649 ARABIC LETTER ALEF MAKSURA + 0x4A, 0x06, 0xA8, 0xF1, // U+064A ARABIC LETTER YEH + 0x4B, 0x06, 0xC7, 0xF1, // U+064B ARABIC FATHATAN + 0x4C, 0x06, 0xC8, 0xF1, // U+064C ARABIC DAMMATAN + 0x4D, 0x06, 0xCB, 0xF1, // U+064D ARABIC KASRATAN + 0x4E, 0x06, 0xC4, 0xF1, // U+064E ARABIC FATHA + 0x4F, 0x06, 0xC5, 0xF1, // U+064F ARABIC DAMMA + 0x50, 0x06, 0xCA, 0xF1, // U+0650 ARABIC KASRA + 0x51, 0x06, 0xC9, 0xF1, // U+0651 ARABIC SHADDA + 0x52, 0x06, 0xC6, 0xF1, // U+0652 ARABIC SUKUN + 0x53, 0x06, 0x00, 0xF1, // U+0653 ARABIC MADDAH ABOVE + 0x54, 0x06, 0x00, 0xF1, // U+0654 ARABIC HAMZA ABOVE + 0x55, 0x06, 0x00, 0xF1, // U+0655 ARABIC HAMZA BELOW + 0x56, 0x06, 0x00, 0xF1, // U+0656 ARABIC SUBSCRIPT ALEF + 0x57, 0x06, 0x00, 0xF1, // U+0657 ARABIC INVERTED DAMMA + 0x58, 0x06, 0x00, 0xF1, // U+0658 ARABIC MARK NOON GHUNNA + 0x59, 0x06, 0x00, 0xF1, // U+0659 ARABIC ZWARAKAY + 0x5A, 0x06, 0x00, 0xF1, // U+065A ARABIC VOWEL SIGN SMALL V ABOVE + 0x5B, 0x06, 0x00, 0xF1, // U+065B ARABIC VOWEL SIGN INVERTED SMALL V ABOVE + 0x5C, 0x06, 0x00, 0xF1, // U+065C ARABIC VOWEL SIGN DOT BELOW + 0x5D, 0x06, 0x00, 0xF1, // U+065D ARABIC REVERSED DAMMA + 0x5E, 0x06, 0x00, 0xF1, // U+065E ARABIC FATHA WITH TWO DOTS + 0x60, 0x06, 0x30, 0xF1, // U+0660 ARABIC-INDIC DIGIT ZERO + 0x61, 0x06, 0x31, 0xF1, // U+0661 ARABIC-INDIC DIGIT ONE + 0x62, 0x06, 0x32, 0xF1, // U+0662 ARABIC-INDIC DIGIT TWO + 0x63, 0x06, 0x33, 0xF1, // U+0663 ARABIC-INDIC DIGIT THREE + 0x64, 0x06, 0x34, 0xF1, // U+0664 ARABIC-INDIC DIGIT FOUR + 0x65, 0x06, 0x35, 0xF1, // U+0665 ARABIC-INDIC DIGIT FIVE + 0x66, 0x06, 0x36, 0xF1, // U+0666 ARABIC-INDIC DIGIT SIX + 0x67, 0x06, 0x37, 0xF1, // U+0667 ARABIC-INDIC DIGIT SEVEN + 0x68, 0x06, 0x38, 0xF1, // U+0668 ARABIC-INDIC DIGIT EIGHT + 0x69, 0x06, 0x39, 0xF1, // U+0669 ARABIC-INDIC DIGIT NINE + 0x6B, 0x06, 0x5E, 0xF1, // U+066B ARABIC DECIMAL SEPARATOR + 0x6C, 0x06, 0x5E, 0xF1, // U+066C ARABIC THOUSANDS SEPARATOR + 0x0C, 0x20, 0x0C, 0xF1, // U+200C ZERO WIDTH NON-JOINER + 0x0D, 0x20, 0x0D, 0xF1, // U+200D ZERO WIDTH JOINER + 0x0E, 0x20, 0x0E, 0xF1, // U+200E LEFT-TO-RIGHT MARK + 0x0F, 0x20, 0x0F, 0xF1, // U+200F RIGHT-TO-LEFT MARK + 0x18, 0x20, 0x3C, 0xF1, // U+2018 LEFT SINGLE QUOTATION MARK + 0x19, 0x20, 0x3E, 0xF1, // U+2019 RIGHT SINGLE QUOTATION MARK + 0x81, 0xFE, 0x45, 0xF1, // U+FE81 ARABIC LETTER ALEF WITH MADDA ABOVE ISOLATED FORM + 0x82, 0xFE, 0x46, 0xF1, // U+FE82 ARABIC LETTER ALEF WITH MADDA ABOVE FINAL FORM + 0x83, 0xFE, 0x43, 0xF1, // U+FE83 ARABIC LETTER ALEF WITH HAMZA ABOVE ISOLATED FORM + 0x84, 0xFE, 0x44, 0xF1, // U+FE84 ARABIC LETTER ALEF WITH HAMZA ABOVE FINAL FORM + 0x85, 0xFE, 0xBB, 0xF1, // U+FE85 ARABIC LETTER WAW WITH HAMZA ABOVE ISOLATED FORM + 0x86, 0xFE, 0xBB, 0xF1, // U+FE86 ARABIC LETTER WAW WITH HAMZA ABOVE FINAL FORM + 0x87, 0xFE, 0x47, 0xF1, // U+FE87 ARABIC LETTER ALEF WITH HAMZA BELOW ISOLATED FORM + 0x88, 0xFE, 0x48, 0xF1, // U+FE88 ARABIC LETTER ALEF WITH HAMZA BELOW FINAL FORM + 0x89, 0xFE, 0xBA, 0xF1, // U+FE89 ARABIC LETTER YEH WITH HAMZA ABOVE ISOLATED FORM + 0x8A, 0xFE, 0xAF, 0xF1, // U+FE8A ARABIC LETTER YEH WITH HAMZA ABOVE FINAL FORM + 0x8B, 0xFE, 0xAE, 0xF1, // U+FE8B ARABIC LETTER YEH WITH HAMZA ABOVE INITIAL FORM + 0x8C, 0xFE, 0xAE, 0xF1, // U+FE8C ARABIC LETTER YEH WITH HAMZA ABOVE MEDIAL FORM + 0x8D, 0xFE, 0x41, 0xF1, // U+FE8D ARABIC LETTER ALEF ISOLATED FORM + 0x8E, 0xFE, 0x42, 0xF1, // U+FE8E ARABIC LETTER ALEF FINAL FORM + 0x8F, 0xFE, 0x4A, 0xF1, // U+FE8F ARABIC LETTER BEH ISOLATED FORM + 0x90, 0xFE, 0x4A, 0xF1, // U+FE90 ARABIC LETTER BEH FINAL FORM + 0x91, 0xFE, 0x49, 0xF1, // U+FE91 ARABIC LETTER BEH INITIAL FORM + 0x92, 0xFE, 0x49, 0xF1, // U+FE92 ARABIC LETTER BEH MEDIAL FORM + 0x93, 0xFE, 0xA9, 0xF1, // U+FE93 ARABIC LETTER TEH MARBUTA ISOLATED FORM + 0x94, 0xFE, 0xAA, 0xF1, // U+FE94 ARABIC LETTER TEH MARBUTA FINAL FORM + 0x95, 0xFE, 0x4C, 0xF1, // U+FE95 ARABIC LETTER TEH ISOLATED FORM + 0x96, 0xFE, 0x4C, 0xF1, // U+FE96 ARABIC LETTER TEH FINAL FORM + 0x97, 0xFE, 0x4B, 0xF1, // U+FE97 ARABIC LETTER TEH INITIAL FORM + 0x98, 0xFE, 0x4B, 0xF1, // U+FE98 ARABIC LETTER TEH MEDIAL FORM + 0x99, 0xFE, 0x4E, 0xF1, // U+FE99 ARABIC LETTER THEH ISOLATED FORM + 0x9A, 0xFE, 0x4E, 0xF1, // U+FE9A ARABIC LETTER THEH FINAL FORM + 0x9B, 0xFE, 0x4D, 0xF1, // U+FE9B ARABIC LETTER THEH INITIAL FORM + 0x9C, 0xFE, 0x4D, 0xF1, // U+FE9C ARABIC LETTER THEH MEDIAL FORM + 0x9D, 0xFE, 0x51, 0xF1, // U+FE9D ARABIC LETTER JEEM ISOLATED FORM + 0x9E, 0xFE, 0x50, 0xF1, // U+FE9E ARABIC LETTER JEEM FINAL FORM + 0x9F, 0xFE, 0x4F, 0xF1, // U+FE9F ARABIC LETTER JEEM INITIAL FORM + 0xA0, 0xFE, 0x4F, 0xF1, // U+FEA0 ARABIC LETTER JEEM MEDIAL FORM + 0xA1, 0xFE, 0x54, 0xF1, // U+FEA1 ARABIC LETTER HAH ISOLATED FORM + 0xA2, 0xFE, 0x53, 0xF1, // U+FEA2 ARABIC LETTER HAH FINAL FORM + 0xA3, 0xFE, 0x52, 0xF1, // U+FEA3 ARABIC LETTER HAH INITIAL FORM + 0xA4, 0xFE, 0x52, 0xF1, // U+FEA4 ARABIC LETTER HAH MEDIAL FORM + 0xA5, 0xFE, 0x57, 0xF1, // U+FEA5 ARABIC LETTER KHAH ISOLATED FORM + 0xA6, 0xFE, 0x56, 0xF1, // U+FEA6 ARABIC LETTER KHAH FINAL FORM + 0xA7, 0xFE, 0x55, 0xF1, // U+FEA7 ARABIC LETTER KHAH INITIAL FORM + 0xA8, 0xFE, 0x55, 0xF1, // U+FEA8 ARABIC LETTER KHAH MEDIAL FORM + 0xA9, 0xFE, 0x58, 0xF1, // U+FEA9 ARABIC LETTER DAL ISOLATED FORM + 0xAA, 0xFE, 0x58, 0xF1, // U+FEAA ARABIC LETTER DAL FINAL FORM + 0xAB, 0xFE, 0x59, 0xF1, // U+FEAB ARABIC LETTER THAL ISOLATED FORM + 0xAC, 0xFE, 0x59, 0xF1, // U+FEAC ARABIC LETTER THAL FINAL FORM + 0xAD, 0xFE, 0x5A, 0xF1, // U+FEAD ARABIC LETTER REH ISOLATED FORM + 0xAE, 0xFE, 0x5A, 0xF1, // U+FEAE ARABIC LETTER REH FINAL FORM + 0xAF, 0xFE, 0x60, 0xF1, // U+FEAF ARABIC LETTER ZAIN ISOLATED FORM + 0xB0, 0xFE, 0x60, 0xF1, // U+FEB0 ARABIC LETTER ZAIN FINAL FORM + 0xB1, 0xFE, 0x62, 0xF1, // U+FEB1 ARABIC LETTER SEEN ISOLATED FORM + 0xB2, 0xFE, 0x62, 0xF1, // U+FEB2 ARABIC LETTER SEEN FINAL FORM + 0xB3, 0xFE, 0x61, 0xF1, // U+FEB3 ARABIC LETTER SEEN INITIAL FORM + 0xB4, 0xFE, 0x61, 0xF1, // U+FEB4 ARABIC LETTER SEEN MEDIAL FORM + 0xB5, 0xFE, 0x64, 0xF1, // U+FEB5 ARABIC LETTER SHEEN ISOLATED FORM + 0xB6, 0xFE, 0x64, 0xF1, // U+FEB6 ARABIC LETTER SHEEN FINAL FORM + 0xB7, 0xFE, 0x63, 0xF1, // U+FEB7 ARABIC LETTER SHEEN INITIAL FORM + 0xB8, 0xFE, 0x63, 0xF1, // U+FEB8 ARABIC LETTER SHEEN MEDIAL FORM + 0xB9, 0xFE, 0x66, 0xF1, // U+FEB9 ARABIC LETTER SAD ISOLATED FORM + 0xBA, 0xFE, 0x66, 0xF1, // U+FEBA ARABIC LETTER SAD FINAL FORM + 0xBB, 0xFE, 0x65, 0xF1, // U+FEBB ARABIC LETTER SAD INITIAL FORM + 0xBC, 0xFE, 0x65, 0xF1, // U+FEBC ARABIC LETTER SAD MEDIAL FORM + 0xBD, 0xFE, 0x68, 0xF1, // U+FEBD ARABIC LETTER DAD ISOLATED FORM + 0xBE, 0xFE, 0x68, 0xF1, // U+FEBE ARABIC LETTER DAD FINAL FORM + 0xBF, 0xFE, 0x67, 0xF1, // U+FEBF ARABIC LETTER DAD INITIAL FORM + 0xC0, 0xFE, 0x67, 0xF1, // U+FEC0 ARABIC LETTER DAD MEDIAL FORM + 0xC1, 0xFE, 0x69, 0xF1, // U+FEC1 ARABIC LETTER TAH ISOLATED FORM + 0xC2, 0xFE, 0x69, 0xF1, // U+FEC2 ARABIC LETTER TAH FINAL FORM + 0xC3, 0xFE, 0x69, 0xF1, // U+FEC3 ARABIC LETTER TAH INITIAL FORM + 0xC4, 0xFE, 0x69, 0xF1, // U+FEC4 ARABIC LETTER TAH MEDIAL FORM + 0xC5, 0xFE, 0x6A, 0xF1, // U+FEC5 ARABIC LETTER ZAH ISOLATED FORM + 0xC6, 0xFE, 0x6A, 0xF1, // U+FEC6 ARABIC LETTER ZAH FINAL FORM + 0xC7, 0xFE, 0x6A, 0xF1, // U+FEC7 ARABIC LETTER ZAH INITIAL FORM + 0xC8, 0xFE, 0x6A, 0xF1, // U+FEC8 ARABIC LETTER ZAH MEDIAL FORM + 0xC9, 0xFE, 0x6E, 0xF1, // U+FEC9 ARABIC LETTER AIN ISOLATED FORM + 0xCA, 0xFE, 0x6D, 0xF1, // U+FECA ARABIC LETTER AIN FINAL FORM + 0xCB, 0xFE, 0x6B, 0xF1, // U+FECB ARABIC LETTER AIN INITIAL FORM + 0xCC, 0xFE, 0x6C, 0xF1, // U+FECC ARABIC LETTER AIN MEDIAL FORM + 0xCD, 0xFE, 0x72, 0xF1, // U+FECD ARABIC LETTER GHAIN ISOLATED FORM + 0xCE, 0xFE, 0x71, 0xF1, // U+FECE ARABIC LETTER GHAIN FINAL FORM + 0xCF, 0xFE, 0x6F, 0xF1, // U+FECF ARABIC LETTER GHAIN INITIAL FORM + 0xD0, 0xFE, 0x70, 0xF1, // U+FED0 ARABIC LETTER GHAIN MEDIAL FORM + 0xD1, 0xFE, 0x75, 0xF1, // U+FED1 ARABIC LETTER FEH ISOLATED FORM + 0xD2, 0xFE, 0x75, 0xF1, // U+FED2 ARABIC LETTER FEH FINAL FORM + 0xD3, 0xFE, 0x73, 0xF1, // U+FED3 ARABIC LETTER FEH INITIAL FORM + 0xD4, 0xFE, 0x74, 0xF1, // U+FED4 ARABIC LETTER FEH MEDIAL FORM + 0xD5, 0xFE, 0x78, 0xF1, // U+FED5 ARABIC LETTER QAF ISOLATED FORM + 0xD6, 0xFE, 0x78, 0xF1, // U+FED6 ARABIC LETTER QAF FINAL FORM + 0xD7, 0xFE, 0x76, 0xF1, // U+FED7 ARABIC LETTER QAF INITIAL FORM + 0xD8, 0xFE, 0x77, 0xF1, // U+FED8 ARABIC LETTER QAF MEDIAL FORM + 0xD9, 0xFE, 0x7A, 0xF1, // U+FED9 ARABIC LETTER KAF ISOLATED FORM + 0xDA, 0xFE, 0x7A, 0xF1, // U+FEDA ARABIC LETTER KAF FINAL FORM + 0xDB, 0xFE, 0x79, 0xF1, // U+FEDB ARABIC LETTER KAF INITIAL FORM + 0xDC, 0xFE, 0x79, 0xF1, // U+FEDC ARABIC LETTER KAF MEDIAL FORM + 0xDD, 0xFE, 0x7C, 0xF1, // U+FEDD ARABIC LETTER LAM ISOLATED FORM + 0xDE, 0xFE, 0x7C, 0xF1, // U+FEDE ARABIC LETTER LAM FINAL FORM + 0xDF, 0xFE, 0x7B, 0xF1, // U+FEDF ARABIC LETTER LAM INITIAL FORM + 0xE0, 0xFE, 0x7B, 0xF1, // U+FEE0 ARABIC LETTER LAM MEDIAL FORM + 0xE1, 0xFE, 0x7E, 0xF1, // U+FEE1 ARABIC LETTER MEEM ISOLATED FORM + 0xE2, 0xFE, 0x7E, 0xF1, // U+FEE2 ARABIC LETTER MEEM FINAL FORM + 0xE3, 0xFE, 0x7D, 0xF1, // U+FEE3 ARABIC LETTER MEEM INITIAL FORM + 0xE4, 0xFE, 0x7D, 0xF1, // U+FEE4 ARABIC LETTER MEEM MEDIAL FORM + 0xE5, 0xFE, 0xE1, 0xF1, // U+FEE5 ARABIC LETTER NOON ISOLATED FORM + 0xE6, 0xFE, 0xE1, 0xF1, // U+FEE6 ARABIC LETTER NOON FINAL FORM + 0xE7, 0xFE, 0x7F, 0xF1, // U+FEE7 ARABIC LETTER NOON INITIAL FORM + 0xE8, 0xFE, 0x7F, 0xF1, // U+FEE8 ARABIC LETTER NOON MEDIAL FORM + 0xE9, 0xFE, 0xA4, 0xF1, // U+FEE9 ARABIC LETTER HEH ISOLATED FORM + 0xEA, 0xFE, 0xA3, 0xF1, // U+FEEA ARABIC LETTER HEH FINAL FORM + 0xEB, 0xFE, 0xA1, 0xF1, // U+FEEB ARABIC LETTER HEH INITIAL FORM + 0xEC, 0xFE, 0xA2, 0xF1, // U+FEEC ARABIC LETTER HEH MEDIAL FORM + 0xED, 0xFE, 0xA5, 0xF1, // U+FEED ARABIC LETTER WAW ISOLATED FORM + 0xEE, 0xFE, 0xA5, 0xF1, // U+FEEE ARABIC LETTER WAW FINAL FORM + 0xEF, 0xFE, 0xAC, 0xF1, // U+FEEF ARABIC LETTER ALEF MAKSURA ISOLATED FORM + 0xF0, 0xFE, 0xAB, 0xF1, // U+FEF0 ARABIC LETTER ALEF MAKSURA FINAL FORM + 0xF1, 0xFE, 0xA8, 0xF1, // U+FEF1 ARABIC LETTER YEH ISOLATED FORM + 0xF2, 0xFE, 0xA7, 0xF1, // U+FEF2 ARABIC LETTER YEH FINAL FORM + 0xF3, 0xFE, 0xA6, 0xF1, // U+FEF3 ARABIC LETTER YEH INITIAL FORM + 0xF4, 0xFE, 0xA6, 0xF1, // U+FEF4 ARABIC LETTER YEH MEDIAL FORM + 0xF5, 0xFE, 0xC0, 0xF1, // U+FEF5 ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM + 0xF6, 0xFE, 0xC1, 0xF1, // U+FEF6 ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE FINAL FORM + 0xF7, 0xFE, 0xBE, 0xF1, // U+FEF7 ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM + 0xF8, 0xFE, 0xBF, 0xF1, // U+FEF8 ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE FINAL FORM + 0xF9, 0xFE, 0xC2, 0xF1, // U+FEF9 ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM + 0xFA, 0xFE, 0xC3, 0xF1, // U+FEFA ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW FINAL FORM + 0xFB, 0xFE, 0xBD, 0xF1, // U+FEFB ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM + 0xFC, 0xFE, 0xBC, 0xF1, // U+FEFC ARABIC LIGATURE LAM WITH ALEF FINAL FORM + ]; + + /// + /// The traditional Arabic mappings. + /// + private static ReadOnlySpan TraditionalMappings => + [ + 0x20, 0x00, 0x20, 0xF2, // U+0020 SPACE + 0x21, 0x00, 0x21, 0xF2, // U+0021 EXCLAMATION MARK + 0x22, 0x00, 0x22, 0xF2, // U+0022 QUOTATION MARK + 0x25, 0x00, 0x25, 0xF2, // U+0025 PERCENT SIGN + 0x28, 0x00, 0x28, 0xF2, // U+0028 LEFT PARENTHESIS + 0x29, 0x00, 0x29, 0xF2, // U+0029 RIGHT PARENTHESIS + 0x2A, 0x00, 0x2A, 0xF2, // U+002A ASTERISK + 0x2B, 0x00, 0x2B, 0xF2, // U+002B PLUS SIGN + 0x2C, 0x00, 0x5E, 0xF2, // U+002C COMMA + 0x2D, 0x00, 0x2D, 0xF2, // U+002D HYPHEN-MINUS + 0x2E, 0x00, 0x2E, 0xF2, // U+002E FULL STOP + 0x2F, 0x00, 0x2F, 0xF2, // U+002F SOLIDUS + 0x3A, 0x00, 0x3A, 0xF2, // U+003A COLON + 0x3B, 0x00, 0x3B, 0xF2, // U+003B SEMICOLON + 0x3D, 0x00, 0x3D, 0xF2, // U+003D EQUALS SIGN + 0x3F, 0x00, 0x3F, 0xF2, // U+003F QUESTION MARK + 0x5B, 0x00, 0x5B, 0xF2, // U+005B LEFT SQUARE BRACKET + 0x5D, 0x00, 0x5D, 0xF2, // U+005D RIGHT SQUARE BRACKET + 0xAB, 0x00, 0x23, 0xF2, // U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + 0xBB, 0x00, 0x24, 0xF2, // U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + 0xD7, 0x00, 0x26, 0xF2, // U+00D7 MULTIPLICATION SIGN + 0xF7, 0x00, 0x27, 0xF2, // U+00F7 DIVISION SIGN + 0x0C, 0x06, 0x2C, 0xF2, // U+060C ARABIC COMMA + 0x1B, 0x06, 0x3B, 0xF2, // U+061B ARABIC SEMICOLON + 0x1F, 0x06, 0x3F, 0xF2, // U+061F ARABIC QUESTION MARK + 0x21, 0x06, 0xD5, 0xF2, // U+0621 ARABIC LETTER HAMZA + 0x22, 0x06, 0x45, 0xF2, // U+0622 ARABIC LETTER ALEF WITH MADDA ABOVE + 0x23, 0x06, 0x43, 0xF2, // U+0623 ARABIC LETTER ALEF WITH HAMZA ABOVE + 0x24, 0x06, 0xDA, 0xF2, // U+0624 ARABIC LETTER WAW WITH HAMZA ABOVE + 0x25, 0x06, 0x47, 0xF2, // U+0625 ARABIC LETTER ALEF WITH HAMZA BELOW + 0x26, 0x06, 0xD9, 0xF2, // U+0626 ARABIC LETTER YEH WITH HAMZA ABOVE + 0x27, 0x06, 0x41, 0xF2, // U+0627 ARABIC LETTER ALEF + 0x28, 0x06, 0x4C, 0xF2, // U+0628 ARABIC LETTER BEH + 0x29, 0x06, 0xD1, 0xF2, // U+0629 ARABIC LETTER TEH MARBUTA + 0x2A, 0x06, 0x50, 0xF2, // U+062A ARABIC LETTER TEH + 0x2B, 0x06, 0x54, 0xF2, // U+062B ARABIC LETTER THEH + 0x2C, 0x06, 0x58, 0xF2, // U+062C ARABIC LETTER JEEM + 0x2D, 0x06, 0x60, 0xF2, // U+062D ARABIC LETTER HAH + 0x2E, 0x06, 0x64, 0xF2, // U+062E ARABIC LETTER KHAH + 0x2F, 0x06, 0x65, 0xF2, // U+062F ARABIC LETTER DAL + 0x30, 0x06, 0x67, 0xF2, // U+0630 ARABIC LETTER THAL + 0x31, 0x06, 0x69, 0xF2, // U+0631 ARABIC LETTER REH + 0x32, 0x06, 0x6B, 0xF2, // U+0632 ARABIC LETTER ZAIN + 0x33, 0x06, 0x70, 0xF2, // U+0633 ARABIC LETTER SEEN + 0x34, 0x06, 0x74, 0xF2, // U+0634 ARABIC LETTER SHEEN + 0x35, 0x06, 0x78, 0xF2, // U+0635 ARABIC LETTER SAD + 0x36, 0x06, 0x7E, 0xF2, // U+0636 ARABIC LETTER DAD + 0x37, 0x06, 0xA2, 0xF2, // U+0637 ARABIC LETTER TAH + 0x38, 0x06, 0xA3, 0xF2, // U+0638 ARABIC LETTER ZAH + 0x39, 0x06, 0xAA, 0xF2, // U+0639 ARABIC LETTER AIN + 0x3A, 0x06, 0xAE, 0xF2, // U+063A ARABIC LETTER GHAIN + 0x3B, 0x06, 0x00, 0xF2, // U+063B ARABIC LETTER KEHEH WITH TWO DOTS ABOVE + 0x3C, 0x06, 0x00, 0xF2, // U+063C ARABIC LETTER KEHEH WITH THREE DOTS BELOW + 0x3D, 0x06, 0x00, 0xF2, // U+063D ARABIC LETTER FARSI YEH WITH INVERTED V + 0x3E, 0x06, 0x00, 0xF2, // U+063E ARABIC LETTER FARSI YEH WITH TWO DOTS ABOVE + 0x3F, 0x06, 0x00, 0xF2, // U+063F ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE + 0x40, 0x06, 0x5F, 0xF2, // U+0640 ARABIC TATWEEL + 0x41, 0x06, 0xB2, 0xF2, // U+0641 ARABIC LETTER FEH + 0x42, 0x06, 0xB6, 0xF2, // U+0642 ARABIC LETTER QAF + 0x43, 0x06, 0xBA, 0xF2, // U+0643 ARABIC LETTER KAF + 0x44, 0x06, 0xBE, 0xF2, // U+0644 ARABIC LETTER LAM + 0x45, 0x06, 0xC2, 0xF2, // U+0645 ARABIC LETTER MEEM + 0x46, 0x06, 0xC6, 0xF2, // U+0646 ARABIC LETTER NOON + 0x47, 0x06, 0xCA, 0xF2, // U+0647 ARABIC LETTER HEH + 0x48, 0x06, 0xCB, 0xF2, // U+0648 ARABIC LETTER WAW + 0x49, 0x06, 0xD4, 0xF2, // U+0649 ARABIC LETTER ALEF MAKSURA + 0x4A, 0x06, 0xD0, 0xF2, // U+064A ARABIC LETTER YEH + 0x4B, 0x06, 0xE7, 0xF2, // U+064B ARABIC FATHATAN + 0x4C, 0x06, 0xE8, 0xF2, // U+064C ARABIC DAMMATAN + 0x4D, 0x06, 0xEB, 0xF2, // U+064D ARABIC KASRATAN + 0x4E, 0x06, 0xE4, 0xF2, // U+064E ARABIC FATHA + 0x4F, 0x06, 0xE5, 0xF2, // U+064F ARABIC DAMMA + 0x50, 0x06, 0xEA, 0xF2, // U+0650 ARABIC KASRA + 0x51, 0x06, 0xE9, 0xF2, // U+0651 ARABIC SHADDA + 0x52, 0x06, 0xE6, 0xF2, // U+0652 ARABIC SUKUN + 0x53, 0x06, 0x00, 0xF2, // U+0653 ARABIC MADDAH ABOVE + 0x54, 0x06, 0x00, 0xF2, // U+0654 ARABIC HAMZA ABOVE + 0x55, 0x06, 0x00, 0xF2, // U+0655 ARABIC HAMZA BELOW + 0x56, 0x06, 0x00, 0xF2, // U+0656 ARABIC SUBSCRIPT ALEF + 0x57, 0x06, 0x00, 0xF2, // U+0657 ARABIC INVERTED DAMMA + 0x58, 0x06, 0x00, 0xF2, // U+0658 ARABIC MARK NOON GHUNNA + 0x59, 0x06, 0x00, 0xF2, // U+0659 ARABIC ZWARAKAY + 0x5A, 0x06, 0x00, 0xF2, // U+065A ARABIC VOWEL SIGN SMALL V ABOVE + 0x5B, 0x06, 0x00, 0xF2, // U+065B ARABIC VOWEL SIGN INVERTED SMALL V ABOVE + 0x5C, 0x06, 0x00, 0xF2, // U+065C ARABIC VOWEL SIGN DOT BELOW + 0x5D, 0x06, 0x00, 0xF2, // U+065D ARABIC REVERSED DAMMA + 0x5E, 0x06, 0x00, 0xF2, // U+065E ARABIC FATHA WITH TWO DOTS + 0x60, 0x06, 0x30, 0xF2, // U+0660 ARABIC-INDIC DIGIT ZERO + 0x61, 0x06, 0x31, 0xF2, // U+0661 ARABIC-INDIC DIGIT ONE + 0x62, 0x06, 0x32, 0xF2, // U+0662 ARABIC-INDIC DIGIT TWO + 0x63, 0x06, 0x33, 0xF2, // U+0663 ARABIC-INDIC DIGIT THREE + 0x64, 0x06, 0x34, 0xF2, // U+0664 ARABIC-INDIC DIGIT FOUR + 0x65, 0x06, 0x35, 0xF2, // U+0665 ARABIC-INDIC DIGIT FIVE + 0x66, 0x06, 0x36, 0xF2, // U+0666 ARABIC-INDIC DIGIT SIX + 0x67, 0x06, 0x37, 0xF2, // U+0667 ARABIC-INDIC DIGIT SEVEN + 0x68, 0x06, 0x38, 0xF2, // U+0668 ARABIC-INDIC DIGIT EIGHT + 0x69, 0x06, 0x39, 0xF2, // U+0669 ARABIC-INDIC DIGIT NINE + 0x6B, 0x06, 0x5E, 0xF2, // U+066B ARABIC DECIMAL SEPARATOR + 0x6C, 0x06, 0x5E, 0xF2, // U+066C ARABIC THOUSANDS SEPARATOR + 0x0C, 0x20, 0x0C, 0xF2, // U+200C ZERO WIDTH NON-JOINER + 0x0D, 0x20, 0x0D, 0xF2, // U+200D ZERO WIDTH JOINER + 0x0E, 0x20, 0x0E, 0xF2, // U+200E LEFT-TO-RIGHT MARK + 0x0F, 0x20, 0x0F, 0xF2, // U+200F RIGHT-TO-LEFT MARK + 0x1C, 0x20, 0x3C, 0xF2, // U+201C LEFT DOUBLE QUOTATION MARK + 0x1D, 0x20, 0x3E, 0xF2, // U+201D RIGHT DOUBLE QUOTATION MARK + 0x08, 0xFC, 0x02, 0xF2, // U+FC08 ARABIC LIGATURE BEH WITH MEEM ISOLATED FORM + 0x0A, 0xFC, 0x1D, 0xF2, // U+FC0A ARABIC LIGATURE BEH WITH YEH ISOLATED FORM + 0x0E, 0xFC, 0x03, 0xF2, // U+FC0E ARABIC LIGATURE TEH WITH MEEM ISOLATED FORM + 0x10, 0xFC, 0x1E, 0xF2, // U+FC10 ARABIC LIGATURE TEH WITH YEH ISOLATED FORM + 0x12, 0xFC, 0x04, 0xF2, // U+FC12 ARABIC LIGATURE THEH WITH MEEM ISOLATED FORM + 0x32, 0xFC, 0x9F, 0xF2, // U+FC32 ARABIC LIGATURE FEH WITH YEH ISOLATED FORM + 0x3F, 0xFC, 0x12, 0xF2, // U+FC3F ARABIC LIGATURE LAM WITH JEEM ISOLATED FORM + 0x40, 0xFC, 0x13, 0xF2, // U+FC40 ARABIC LIGATURE LAM WITH HAH ISOLATED FORM + 0x41, 0xFC, 0x14, 0xF2, // U+FC41 ARABIC LIGATURE LAM WITH KHAH ISOLATED FORM + 0x42, 0xFC, 0x05, 0xF2, // U+FC42 ARABIC LIGATURE LAM WITH MEEM ISOLATED FORM + 0x43, 0xFC, 0x95, 0xF2, // U+FC43 ARABIC LIGATURE LAM WITH ALEF MAKSURA ISOLATED FORM + 0x44, 0xFC, 0x1C, 0xF2, // U+FC44 ARABIC LIGATURE LAM WITH YEH ISOLATED FORM + 0x4E, 0xFC, 0x06, 0xF2, // U+FC4E ARABIC LIGATURE NOON WITH MEEM ISOLATED FORM + 0x50, 0xFC, 0x1F, 0xF2, // U+FC50 ARABIC LIGATURE NOON WITH YEH ISOLATED FORM + 0x5E, 0xFC, 0xEF, 0xF2, // U+FC5E ARABIC LIGATURE SHADDA WITH DAMMATAN ISOLATED FORM + 0x60, 0xFC, 0xEC, 0xF2, // U+FC60 ARABIC LIGATURE SHADDA WITH FATHA ISOLATED FORM + 0x61, 0xFC, 0xED, 0xF2, // U+FC61 ARABIC LIGATURE SHADDA WITH DAMMA ISOLATED FORM + 0x62, 0xFC, 0xF0, 0xF2, // U+FC62 ARABIC LIGATURE SHADDA WITH KASRA ISOLATED FORM + 0x6A, 0xFC, 0x15, 0xF2, // U+FC6A ARABIC LIGATURE BEH WITH REH FINAL FORM + 0x6D, 0xFC, 0x92, 0xF2, // U+FC6D ARABIC LIGATURE BEH WITH NOON FINAL FORM + 0x70, 0xFC, 0x16, 0xF2, // U+FC70 ARABIC LIGATURE TEH WITH REH FINAL FORM + 0x73, 0xFC, 0x93, 0xF2, // U+FC73 ARABIC LIGATURE TEH WITH NOON FINAL FORM + 0x91, 0xFC, 0x17, 0xF2, // U+FC91 ARABIC LIGATURE YEH WITH REH FINAL FORM + 0x94, 0xFC, 0x94, 0xF2, // U+FC94 ARABIC LIGATURE YEH WITH NOON FINAL FORM + 0x9C, 0xFC, 0x80, 0xF2, // U+FC9C ARABIC LIGATURE BEH WITH JEEM INITIAL FORM + 0x9D, 0xFC, 0x81, 0xF2, // U+FC9D ARABIC LIGATURE BEH WITH HAH INITIAL FORM + 0x9E, 0xFC, 0x82, 0xF2, // U+FC9E ARABIC LIGATURE BEH WITH KHAH INITIAL FORM + 0x9F, 0xFC, 0x96, 0xF2, // U+FC9F ARABIC LIGATURE BEH WITH MEEM INITIAL FORM + 0xA1, 0xFC, 0x83, 0xF2, // U+FCA1 ARABIC LIGATURE TEH WITH JEEM INITIAL FORM + 0xA2, 0xFC, 0x84, 0xF2, // U+FCA2 ARABIC LIGATURE TEH WITH HAH INITIAL FORM + 0xA3, 0xFC, 0x85, 0xF2, // U+FCA3 ARABIC LIGATURE TEH WITH KHAH INITIAL FORM + 0xA4, 0xFC, 0x97, 0xF2, // U+FCA4 ARABIC LIGATURE TEH WITH MEEM INITIAL FORM + 0xA8, 0xFC, 0x9A, 0xF2, // U+FCA8 ARABIC LIGATURE JEEM WITH MEEM INITIAL FORM + 0xAA, 0xFC, 0x9B, 0xF2, // U+FCAA ARABIC LIGATURE HAH WITH MEEM INITIAL FORM + 0xAC, 0xFC, 0x9C, 0xF2, // U+FCAC ARABIC LIGATURE KHAH WITH MEEM INITIAL FORM + 0xB0, 0xFC, 0x18, 0xF2, // U+FCB0 ARABIC LIGATURE SEEN WITH MEEM INITIAL FORM + 0xC9, 0xFC, 0x86, 0xF2, // U+FCC9 ARABIC LIGATURE LAM WITH JEEM INITIAL FORM + 0xCA, 0xFC, 0x87, 0xF2, // U+FCCA ARABIC LIGATURE LAM WITH HAH INITIAL FORM + 0xCB, 0xFC, 0x88, 0xF2, // U+FCCB ARABIC LIGATURE LAM WITH KHAH INITIAL FORM + 0xCC, 0xFC, 0x9D, 0xF2, // U+FCCC ARABIC LIGATURE LAM WITH MEEM INITIAL FORM + 0xCD, 0xFC, 0x1A, 0xF2, // U+FCCD ARABIC LIGATURE LAM WITH HEH INITIAL FORM + 0xCE, 0xFC, 0x89, 0xF2, // U+FCCE ARABIC LIGATURE MEEM WITH JEEM INITIAL FORM + 0xCF, 0xFC, 0x8A, 0xF2, // U+FCCF ARABIC LIGATURE MEEM WITH HAH INITIAL FORM + 0xD0, 0xFC, 0x8B, 0xF2, // U+FCD0 ARABIC LIGATURE MEEM WITH KHAH INITIAL FORM + 0xD1, 0xFC, 0x9E, 0xF2, // U+FCD1 ARABIC LIGATURE MEEM WITH MEEM INITIAL FORM + 0xD2, 0xFC, 0x8D, 0xF2, // U+FCD2 ARABIC LIGATURE NOON WITH JEEM INITIAL FORM + 0xD3, 0xFC, 0x8E, 0xF2, // U+FCD3 ARABIC LIGATURE NOON WITH HAH INITIAL FORM + 0xD5, 0xFC, 0x98, 0xF2, // U+FCD5 ARABIC LIGATURE NOON WITH MEEM INITIAL FORM + 0xDA, 0xFC, 0x8F, 0xF2, // U+FCDA ARABIC LIGATURE YEH WITH JEEM INITIAL FORM + 0xDB, 0xFC, 0x90, 0xF2, // U+FCDB ARABIC LIGATURE YEH WITH HAH INITIAL FORM + 0xDC, 0xFC, 0x91, 0xF2, // U+FCDC ARABIC LIGATURE YEH WITH KHAH INITIAL FORM + 0xDD, 0xFC, 0x99, 0xF2, // U+FCDD ARABIC LIGATURE YEH WITH MEEM INITIAL FORM + 0x30, 0xFD, 0x19, 0xF2, // U+FD30 ARABIC LIGATURE SHEEN WITH MEEM INITIAL FORM + 0x3E, 0xFD, 0x7B, 0xF2, // U+FD3E ORNATE LEFT PARENTHESIS + 0x3F, 0xFD, 0x7D, 0xF2, // U+FD3F ORNATE RIGHT PARENTHESIS + 0x88, 0xFD, 0x10, 0xF2, // U+FD88 ARABIC LIGATURE LAM WITH MEEM WITH HAH INITIAL FORM + 0x81, 0xFE, 0x45, 0xF2, // U+FE81 ARABIC LETTER ALEF WITH MADDA ABOVE ISOLATED FORM + 0x82, 0xFE, 0x46, 0xF2, // U+FE82 ARABIC LETTER ALEF WITH MADDA ABOVE FINAL FORM + 0x83, 0xFE, 0x43, 0xF2, // U+FE83 ARABIC LETTER ALEF WITH HAMZA ABOVE ISOLATED FORM + 0x84, 0xFE, 0x44, 0xF2, // U+FE84 ARABIC LETTER ALEF WITH HAMZA ABOVE FINAL FORM + 0x85, 0xFE, 0xDA, 0xF2, // U+FE85 ARABIC LETTER WAW WITH HAMZA ABOVE ISOLATED FORM + 0x86, 0xFE, 0xDB, 0xF2, // U+FE86 ARABIC LETTER WAW WITH HAMZA ABOVE FINAL FORM + 0x87, 0xFE, 0x47, 0xF2, // U+FE87 ARABIC LETTER ALEF WITH HAMZA BELOW ISOLATED FORM + 0x88, 0xFE, 0x48, 0xF2, // U+FE88 ARABIC LETTER ALEF WITH HAMZA BELOW FINAL FORM + 0x89, 0xFE, 0xD9, 0xF2, // U+FE89 ARABIC LETTER YEH WITH HAMZA ABOVE ISOLATED FORM + 0x8A, 0xFE, 0xD8, 0xF2, // U+FE8A ARABIC LETTER YEH WITH HAMZA ABOVE FINAL FORM + 0x8B, 0xFE, 0xD6, 0xF2, // U+FE8B ARABIC LETTER YEH WITH HAMZA ABOVE INITIAL FORM + 0x8C, 0xFE, 0xD7, 0xF2, // U+FE8C ARABIC LETTER YEH WITH HAMZA ABOVE MEDIAL FORM + 0x8D, 0xFE, 0x41, 0xF2, // U+FE8D ARABIC LETTER ALEF ISOLATED FORM + 0x8E, 0xFE, 0x42, 0xF2, // U+FE8E ARABIC LETTER ALEF FINAL FORM + 0x8F, 0xFE, 0x4C, 0xF2, // U+FE8F ARABIC LETTER BEH ISOLATED FORM + 0x90, 0xFE, 0x4B, 0xF2, // U+FE90 ARABIC LETTER BEH FINAL FORM + 0x91, 0xFE, 0x49, 0xF2, // U+FE91 ARABIC LETTER BEH INITIAL FORM + 0x92, 0xFE, 0x4A, 0xF2, // U+FE92 ARABIC LETTER BEH MEDIAL FORM + 0x93, 0xFE, 0xD1, 0xF2, // U+FE93 ARABIC LETTER TEH MARBUTA ISOLATED FORM + 0x94, 0xFE, 0xD2, 0xF2, // U+FE94 ARABIC LETTER TEH MARBUTA FINAL FORM + 0x95, 0xFE, 0x50, 0xF2, // U+FE95 ARABIC LETTER TEH ISOLATED FORM + 0x96, 0xFE, 0x4F, 0xF2, // U+FE96 ARABIC LETTER TEH FINAL FORM + 0x97, 0xFE, 0x4D, 0xF2, // U+FE97 ARABIC LETTER TEH INITIAL FORM + 0x98, 0xFE, 0x4E, 0xF2, // U+FE98 ARABIC LETTER TEH MEDIAL FORM + 0x99, 0xFE, 0x54, 0xF2, // U+FE99 ARABIC LETTER THEH ISOLATED FORM + 0x9A, 0xFE, 0x53, 0xF2, // U+FE9A ARABIC LETTER THEH FINAL FORM + 0x9B, 0xFE, 0x51, 0xF2, // U+FE9B ARABIC LETTER THEH INITIAL FORM + 0x9C, 0xFE, 0x52, 0xF2, // U+FE9C ARABIC LETTER THEH MEDIAL FORM + 0x9D, 0xFE, 0x58, 0xF2, // U+FE9D ARABIC LETTER JEEM ISOLATED FORM + 0x9E, 0xFE, 0x57, 0xF2, // U+FE9E ARABIC LETTER JEEM FINAL FORM + 0x9F, 0xFE, 0x55, 0xF2, // U+FE9F ARABIC LETTER JEEM INITIAL FORM + 0xA0, 0xFE, 0x56, 0xF2, // U+FEA0 ARABIC LETTER JEEM MEDIAL FORM + 0xA1, 0xFE, 0x60, 0xF2, // U+FEA1 ARABIC LETTER HAH ISOLATED FORM + 0xA2, 0xFE, 0x5C, 0xF2, // U+FEA2 ARABIC LETTER HAH FINAL FORM + 0xA3, 0xFE, 0x59, 0xF2, // U+FEA3 ARABIC LETTER HAH INITIAL FORM + 0xA4, 0xFE, 0x5A, 0xF2, // U+FEA4 ARABIC LETTER HAH MEDIAL FORM + 0xA5, 0xFE, 0x64, 0xF2, // U+FEA5 ARABIC LETTER KHAH ISOLATED FORM + 0xA6, 0xFE, 0x63, 0xF2, // U+FEA6 ARABIC LETTER KHAH FINAL FORM + 0xA7, 0xFE, 0x61, 0xF2, // U+FEA7 ARABIC LETTER KHAH INITIAL FORM + 0xA8, 0xFE, 0x62, 0xF2, // U+FEA8 ARABIC LETTER KHAH MEDIAL FORM + 0xA9, 0xFE, 0x65, 0xF2, // U+FEA9 ARABIC LETTER DAL ISOLATED FORM + 0xAA, 0xFE, 0x66, 0xF2, // U+FEAA ARABIC LETTER DAL FINAL FORM + 0xAB, 0xFE, 0x67, 0xF2, // U+FEAB ARABIC LETTER THAL ISOLATED FORM + 0xAC, 0xFE, 0x68, 0xF2, // U+FEAC ARABIC LETTER THAL FINAL FORM + 0xAD, 0xFE, 0x69, 0xF2, // U+FEAD ARABIC LETTER REH ISOLATED FORM + 0xAE, 0xFE, 0x6A, 0xF2, // U+FEAE ARABIC LETTER REH FINAL FORM + 0xAF, 0xFE, 0x6B, 0xF2, // U+FEAF ARABIC LETTER ZAIN ISOLATED FORM + 0xB0, 0xFE, 0x6C, 0xF2, // U+FEB0 ARABIC LETTER ZAIN FINAL FORM + 0xB1, 0xFE, 0x70, 0xF2, // U+FEB1 ARABIC LETTER SEEN ISOLATED FORM + 0xB2, 0xFE, 0x6F, 0xF2, // U+FEB2 ARABIC LETTER SEEN FINAL FORM + 0xB3, 0xFE, 0x6D, 0xF2, // U+FEB3 ARABIC LETTER SEEN INITIAL FORM + 0xB4, 0xFE, 0x6E, 0xF2, // U+FEB4 ARABIC LETTER SEEN MEDIAL FORM + 0xB5, 0xFE, 0x74, 0xF2, // U+FEB5 ARABIC LETTER SHEEN ISOLATED FORM + 0xB6, 0xFE, 0x73, 0xF2, // U+FEB6 ARABIC LETTER SHEEN FINAL FORM + 0xB7, 0xFE, 0x71, 0xF2, // U+FEB7 ARABIC LETTER SHEEN INITIAL FORM + 0xB8, 0xFE, 0x72, 0xF2, // U+FEB8 ARABIC LETTER SHEEN MEDIAL FORM + 0xB9, 0xFE, 0x78, 0xF2, // U+FEB9 ARABIC LETTER SAD ISOLATED FORM + 0xBA, 0xFE, 0x77, 0xF2, // U+FEBA ARABIC LETTER SAD FINAL FORM + 0xBB, 0xFE, 0x75, 0xF2, // U+FEBB ARABIC LETTER SAD INITIAL FORM + 0xBC, 0xFE, 0x76, 0xF2, // U+FEBC ARABIC LETTER SAD MEDIAL FORM + 0xBD, 0xFE, 0x7E, 0xF2, // U+FEBD ARABIC LETTER DAD ISOLATED FORM + 0xBE, 0xFE, 0x7C, 0xF2, // U+FEBE ARABIC LETTER DAD FINAL FORM + 0xBF, 0xFE, 0x79, 0xF2, // U+FEBF ARABIC LETTER DAD INITIAL FORM + 0xC0, 0xFE, 0x7A, 0xF2, // U+FEC0 ARABIC LETTER DAD MEDIAL FORM + 0xC1, 0xFE, 0xA2, 0xF2, // U+FEC1 ARABIC LETTER TAH ISOLATED FORM + 0xC2, 0xFE, 0xA1, 0xF2, // U+FEC2 ARABIC LETTER TAH FINAL FORM + 0xC3, 0xFE, 0x7F, 0xF2, // U+FEC3 ARABIC LETTER TAH INITIAL FORM + 0xC4, 0xFE, 0xF1, 0xF2, // U+FEC4 ARABIC LETTER TAH MEDIAL FORM + 0xC5, 0xFE, 0xA6, 0xF2, // U+FEC5 ARABIC LETTER ZAH ISOLATED FORM + 0xC6, 0xFE, 0xA5, 0xF2, // U+FEC6 ARABIC LETTER ZAH FINAL FORM + 0xC7, 0xFE, 0xA3, 0xF2, // U+FEC7 ARABIC LETTER ZAH INITIAL FORM + 0xC8, 0xFE, 0xA4, 0xF2, // U+FEC8 ARABIC LETTER ZAH MEDIAL FORM + 0xC9, 0xFE, 0xAA, 0xF2, // U+FEC9 ARABIC LETTER AIN ISOLATED FORM + 0xCA, 0xFE, 0xA9, 0xF2, // U+FECA ARABIC LETTER AIN FINAL FORM + 0xCB, 0xFE, 0xA7, 0xF2, // U+FECB ARABIC LETTER AIN INITIAL FORM + 0xCC, 0xFE, 0xA8, 0xF2, // U+FECC ARABIC LETTER AIN MEDIAL FORM + 0xCD, 0xFE, 0xAE, 0xF2, // U+FECD ARABIC LETTER GHAIN ISOLATED FORM + 0xCE, 0xFE, 0xAD, 0xF2, // U+FECE ARABIC LETTER GHAIN FINAL FORM + 0xCF, 0xFE, 0xAB, 0xF2, // U+FECF ARABIC LETTER GHAIN INITIAL FORM + 0xD0, 0xFE, 0xAC, 0xF2, // U+FED0 ARABIC LETTER GHAIN MEDIAL FORM + 0xD1, 0xFE, 0xB2, 0xF2, // U+FED1 ARABIC LETTER FEH ISOLATED FORM + 0xD2, 0xFE, 0xB1, 0xF2, // U+FED2 ARABIC LETTER FEH FINAL FORM + 0xD3, 0xFE, 0xAF, 0xF2, // U+FED3 ARABIC LETTER FEH INITIAL FORM + 0xD4, 0xFE, 0xB0, 0xF2, // U+FED4 ARABIC LETTER FEH MEDIAL FORM + 0xD5, 0xFE, 0xB6, 0xF2, // U+FED5 ARABIC LETTER QAF ISOLATED FORM + 0xD6, 0xFE, 0xB5, 0xF2, // U+FED6 ARABIC LETTER QAF FINAL FORM + 0xD7, 0xFE, 0xB3, 0xF2, // U+FED7 ARABIC LETTER QAF INITIAL FORM + 0xD8, 0xFE, 0xB4, 0xF2, // U+FED8 ARABIC LETTER QAF MEDIAL FORM + 0xD9, 0xFE, 0xBA, 0xF2, // U+FED9 ARABIC LETTER KAF ISOLATED FORM + 0xDA, 0xFE, 0xB9, 0xF2, // U+FEDA ARABIC LETTER KAF FINAL FORM + 0xDB, 0xFE, 0xB7, 0xF2, // U+FEDB ARABIC LETTER KAF INITIAL FORM + 0xDC, 0xFE, 0xB8, 0xF2, // U+FEDC ARABIC LETTER KAF MEDIAL FORM + 0xDD, 0xFE, 0xBE, 0xF2, // U+FEDD ARABIC LETTER LAM ISOLATED FORM + 0xDE, 0xFE, 0xBD, 0xF2, // U+FEDE ARABIC LETTER LAM FINAL FORM + 0xDF, 0xFE, 0xBB, 0xF2, // U+FEDF ARABIC LETTER LAM INITIAL FORM + 0xE0, 0xFE, 0xBC, 0xF2, // U+FEE0 ARABIC LETTER LAM MEDIAL FORM + 0xE1, 0xFE, 0xC2, 0xF2, // U+FEE1 ARABIC LETTER MEEM ISOLATED FORM + 0xE2, 0xFE, 0xC1, 0xF2, // U+FEE2 ARABIC LETTER MEEM FINAL FORM + 0xE3, 0xFE, 0xBF, 0xF2, // U+FEE3 ARABIC LETTER MEEM INITIAL FORM + 0xE4, 0xFE, 0xC0, 0xF2, // U+FEE4 ARABIC LETTER MEEM MEDIAL FORM + 0xE5, 0xFE, 0xC6, 0xF2, // U+FEE5 ARABIC LETTER NOON ISOLATED FORM + 0xE6, 0xFE, 0xC5, 0xF2, // U+FEE6 ARABIC LETTER NOON FINAL FORM + 0xE7, 0xFE, 0xC3, 0xF2, // U+FEE7 ARABIC LETTER NOON INITIAL FORM + 0xE8, 0xFE, 0xC4, 0xF2, // U+FEE8 ARABIC LETTER NOON MEDIAL FORM + 0xE9, 0xFE, 0xCA, 0xF2, // U+FEE9 ARABIC LETTER HEH ISOLATED FORM + 0xEA, 0xFE, 0xC9, 0xF2, // U+FEEA ARABIC LETTER HEH FINAL FORM + 0xEB, 0xFE, 0xC7, 0xF2, // U+FEEB ARABIC LETTER HEH INITIAL FORM + 0xEC, 0xFE, 0xC8, 0xF2, // U+FEEC ARABIC LETTER HEH MEDIAL FORM + 0xED, 0xFE, 0xCB, 0xF2, // U+FEED ARABIC LETTER WAW ISOLATED FORM + 0xEE, 0xFE, 0xCC, 0xF2, // U+FEEE ARABIC LETTER WAW FINAL FORM + 0xEF, 0xFE, 0xD4, 0xF2, // U+FEEF ARABIC LETTER ALEF MAKSURA ISOLATED FORM + 0xF0, 0xFE, 0xD3, 0xF2, // U+FEF0 ARABIC LETTER ALEF MAKSURA FINAL FORM + 0xF1, 0xFE, 0xD0, 0xF2, // U+FEF1 ARABIC LETTER YEH ISOLATED FORM + 0xF2, 0xFE, 0xCF, 0xF2, // U+FEF2 ARABIC LETTER YEH FINAL FORM + 0xF3, 0xFE, 0xCD, 0xF2, // U+FEF3 ARABIC LETTER YEH INITIAL FORM + 0xF4, 0xFE, 0xCE, 0xF2, // U+FEF4 ARABIC LETTER YEH MEDIAL FORM + 0xF5, 0xFE, 0xE0, 0xF2, // U+FEF5 ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM + 0xF6, 0xFE, 0xE1, 0xF2, // U+FEF6 ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE FINAL FORM + 0xF7, 0xFE, 0xDE, 0xF2, // U+FEF7 ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM + 0xF8, 0xFE, 0xDF, 0xF2, // U+FEF8 ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE FINAL FORM + 0xF9, 0xFE, 0xE2, 0xF2, // U+FEF9 ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM + 0xFA, 0xFE, 0xE3, 0xF2, // U+FEFA ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW FINAL FORM + 0xFB, 0xFE, 0xDC, 0xF2, // U+FEFB ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM + 0xFC, 0xFE, 0xDD, 0xF2, // U+FEFC ARABIC LIGATURE LAM WITH ALEF FINAL FORM + ]; + } +} diff --git a/src/SixLabors.Fonts/Unicode/Resources/ArabicShapingTrie.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/ArabicShapingTrie.Generated.cs index ae5974603..d5157c23b 100644 --- a/src/SixLabors.Fonts/Unicode/Resources/ArabicShapingTrie.Generated.cs +++ b/src/SixLabors.Fonts/Unicode/Resources/ArabicShapingTrie.Generated.cs @@ -10,151 +10,159 @@ internal static class ArabicShapingTrie { public static ReadOnlySpan Data => new byte[] { - 0, 240, 1, 0, 0, 0, 0, 0, 80, 56, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 72, 2, 0, 0, 80, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, - 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 56, 2, 0, 0, - 64, 2, 0, 0, 104, 2, 0, 0, 112, 2, 0, 0, 120, 2, 0, 0, 128, 2, 0, 0, 136, 2, 0, 0, 144, 2, 0, 0, 152, 2, 0, 0, 160, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 181, 2, 0, 0, 189, 2, 0, 0, 56, 2, 0, 0, 64, 2, 0, 0, 197, 2, 0, 0, 205, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 213, 2, 0, 0, 221, 2, 0, 0, 229, 2, 0, 0, 237, 2, 0, 0, 245, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 252, 2, 0, 0, 4, 3, 0, 0, 4, 3, 0, 0, 6, 3, 0, 0, 13, 3, 0, 0, - 19, 3, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 24, 3, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 96, 9, 0, 0, 96, 9, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, - 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 224, 8, 0, 0, 160, 9, 0, 0, 224, 9, 0, 0, 32, 10, 0, 0, 96, 10, 0, 0, 160, 10, 0, 0, 212, 10, 0, 0, 224, 8, 0, 0, 20, 11, 0, 0, 71, 0, 0, 0, 94, 8, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, - 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 71, 0, 0, 0, 157, 8, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 32, 3, 0, 0, 40, 3, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 48, 3, 0, 0, 54, 3, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 62, 3, 0, 0, 70, 3, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 78, 3, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 82, 3, 0, 0, 90, 3, 0, 0, 96, 3, 0, 0, 104, 3, 0, 0, 108, 3, 0, 0, 116, 3, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 4, 3, 0, 0, 4, 3, 0, 0, 124, 3, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, - 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 56, 2, 0, 0, 55, 2, 1, 0, 55, 2, 1, 0, 55, 2, 1, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 240, 1, 0, 0, 0, 0, 0, 96, 59, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 86, 2, 0, 0, 94, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, + 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 70, 2, 0, 0, + 78, 2, 0, 0, 118, 2, 0, 0, 126, 2, 0, 0, 134, 2, 0, 0, 142, 2, 0, 0, 150, 2, 0, 0, 158, 2, 0, 0, 166, 2, 0, 0, 174, 2, 0, 0, 182, 2, 0, 0, 190, 2, 0, 0, 195, 2, 0, 0, 203, 2, 0, 0, 70, 2, 0, 0, 78, 2, 0, 0, 211, 2, 0, 0, 219, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 227, 2, 0, 0, 235, 2, 0, 0, 243, 2, 0, 0, 251, 2, 0, 0, 3, 3, 0, 0, 11, 3, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 18, 3, 0, 0, 26, 3, 0, 0, 26, 3, 0, 0, 28, 3, 0, 0, 36, 3, 0, 0, + 42, 3, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 47, 3, 0, 0, 52, 3, 0, 0, 70, 2, 0, 0, 59, 3, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 26, 3, 0, 0, + 67, 3, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 152, 9, 0, 0, 152, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, + 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 24, 9, 0, 0, 216, 9, 0, 0, 24, 10, 0, 0, 88, 10, 0, 0, 152, 10, 0, 0, 216, 10, 0, 0, 12, 11, 0, 0, 24, 9, 0, 0, 76, 11, 0, 0, 72, 0, 0, 0, 94, 8, 0, 0, 157, 8, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, + 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 213, 8, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 75, 3, 0, 0, 83, 3, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 91, 3, 0, 0, 97, 3, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 105, 3, 0, 0, 113, 3, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 121, 3, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 125, 3, 0, 0, 132, 3, 0, 0, 138, 3, 0, 0, 146, 3, 0, 0, 150, 3, 0, 0, 158, 3, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 161, 3, 0, 0, 165, 3, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 26, 3, 0, 0, 26, 3, 0, 0, 173, 3, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, + 70, 2, 0, 0, 70, 2, 0, 0, 70, 2, 0, 0, 69, 2, 1, 0, 69, 2, 1, 0, 69, 2, 1, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 27, 0, - 4, 0, 72, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 98, 0, 0, 0, 5, 0, 2, 0, 99, 0, 0, 0, 5, 0, 2, 0, 6, 0, 0, 0, 92, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 18, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 79, 0, 0, 0, 79, 0, 2, 0, 84, 0, 2, 0, 84, 0, 2, 0, 82, 0, 2, 0, 82, 0, 2, 0, 90, 0, 2, 0, 90, 0, 2, 0, 3, 0, - 2, 0, 3, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 12, 0, 2, 0, 12, 0, 2, 0, 12, 0, 3, 0, 72, 0, 2, 0, 14, 0, 2, 0, 77, 0, 2, 0, 25, 0, 2, 0, 30, 0, 2, 0, 70, 0, 2, 0, 73, 0, 2, 0, 22, 0, 0, 0, 98, 0, 2, 0, 99, 0, 2, 0, 99, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 6, 0, 2, 0, 77, 0, 4, 0, 72, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 4, 0, 72, 0, 0, 0, 5, 0, 0, 0, 98, 0, 0, 0, 98, 0, 2, 0, 99, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 18, 0, - 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 18, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, 2, 0, 84, 0, 2, 0, 84, 0, 2, 0, 84, 0, 2, 0, 82, 0, - 2, 0, 82, 0, 2, 0, 90, 0, 2, 0, 3, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 77, 0, 2, 0, 77, 0, 2, 0, 16, 0, 2, 0, 88, 0, 2, 0, 16, 0, 2, 0, 25, 0, 2, 0, 25, 0, 2, 0, 25, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 30, 0, 2, 0, 30, 0, - 2, 0, 30, 0, 2, 0, 30, 0, 2, 0, 73, 0, 2, 0, 73, 0, 2, 0, 73, 0, 2, 0, 73, 0, 2, 0, 75, 0, 2, 0, 29, 0, 2, 0, 18, 0, 0, 0, 92, 0, 2, 0, 23, 0, 2, 0, 23, 0, 0, 0, 93, 0, 0, 0, 98, 0, 0, 0, 98, 0, 0, 0, 98, 0, 0, 0, 98, 0, 0, 0, 98, 0, 0, 0, 98, 0, 0, 0, 98, 0, 0, 0, 98, 0, 2, 0, 12, 0, 0, 0, 101, 0, 2, 0, 12, 0, 0, 0, 98, 0, - 2, 0, 99, 0, 2, 0, 99, 0, 0, 0, 100, 0, 0, 0, 100, 0, 4, 0, 72, 0, 0, 0, 92, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 0, 0, 9, 0, 0, 0, 79, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 84, 0, 2, 0, 82, 0, 2, 0, 3, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 29, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 5, 0, 72, 0, 0, 0, 4, 0, 4, 0, 72, 0, 2, 0, 7, 0, 2, 0, 17, 0, 2, 0, 17, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 21, 0, 0, 0, 89, 0, 0, 0, 104, 0, 2, 0, 24, 0, - 2, 0, 94, 0, 2, 0, 94, 0, 2, 0, 102, 0, 0, 0, 103, 0, 2, 0, 26, 0, 2, 0, 31, 0, 2, 0, 71, 0, 2, 0, 74, 0, 2, 0, 85, 0, 2, 0, 15, 0, 2, 0, 11, 0, 2, 0, 76, 0, 2, 0, 80, 0, 0, 0, 83, 0, 2, 0, 78, 0, 0, 0, 10, 0, 2, 0, 86, 0, 0, 0, 91, 0, 2, 0, 7, 0, 2, 0, 17, 0, 0, 0, 10, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 0, 0, 105, 0, 2, 0, 28, 0, 2, 0, 13, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 18, 0, 2, 0, 18, 0, - 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 79, 0, 2, 0, 84, 0, 2, 0, 3, 0, 2, 0, 3, 0, 2, 0, 3, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 70, 0, 2, 0, 70, 0, 2, 0, 73, 0, 2, 0, 73, 0, 2, 0, 73, 0, 2, 0, 30, 0, 0, 0, 79, 0, 0, 0, 79, 0, 2, 0, 84, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 84, 0, 0, 0, 79, 0, - 2, 0, 18, 0, 0, 0, 5, 0, 0, 0, 5, 0, 2, 0, 12, 0, 2, 0, 12, 0, 2, 0, 99, 0, 0, 0, 98, 0, 0, 0, 98, 0, 2, 0, 8, 0, 2, 0, 8, 0, 2, 0, 18, 0, 2, 0, 84, 0, 2, 0, 84, 0, 2, 0, 25, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, - 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, - 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 3, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, - 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 36, 0, 4, 0, 33, 0, 2, 0, 39, 0, 2, 0, 42, 0, 2, 0, 37, 0, 2, 0, 38, 0, 4, 0, 32, 0, 0, 0, 40, 0, 2, 0, 34, 0, 0, 0, 35, 0, 0, 0, 41, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 3, 0, 72, 0, 3, 0, 72, 0, 3, 0, 72, 0, 2, 0, 96, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 2, 0, 73, 0, 2, 0, 18, 0, 2, 0, 90, 0, 2, 0, 90, 0, 2, 0, 16, 0, 0, 0, 97, 0, 2, 0, 73, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 6, 0, - 2, 0, 6, 0, 2, 0, 18, 0, 2, 0, 90, 0, 2, 0, 14, 0, 2, 0, 77, 0, 2, 0, 30, 0, 2, 0, 70, 0, 2, 0, 99, 0, 2, 0, 99, 0, 0, 0, 79, 0, 0, 0, 98, 0, 0, 0, 81, 0, 4, 0, 72, 0, 0, 0, 9, 0, 2, 0, 82, 0, 2, 0, 16, 0, 0, 0, 87, 0, 0, 0, 79, 0, 2, 0, 3, 0, 2, 0, 25, 0, 2, 0, 77, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 0, 0, 79, 0, - 2, 0, 99, 0, 2, 0, 0, 0, 2, 0, 2, 0, 2, 0, 1, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 18, 0, 2, 0, 16, 0, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 30, 0, 2, 0, 16, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 3, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, - 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 5, 0, 72, 0, 5, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, - 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 3, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 43, 0, 2, 0, 45, 0, 2, 0, 45, 0, 2, 0, 49, 0, 2, 0, 49, 0, 0, 0, 46, 0, 4, 0, 72, 0, 0, 0, 67, 0, 4, 0, 72, 0, 0, 0, 69, 0, 0, 0, 69, 0, 4, 0, 72, 0, 4, 0, 72, 0, 1, 0, 50, 0, 0, 0, 64, 0, 0, 0, 68, 0, 0, 0, 52, 0, 0, 0, 52, 0, 0, 0, 52, 0, 2, 0, 53, 0, 2, 0, 47, 0, 2, 0, 65, 0, - 2, 0, 54, 0, 1, 0, 55, 0, 2, 0, 61, 0, 2, 0, 44, 0, 2, 0, 44, 0, 2, 0, 57, 0, 2, 0, 57, 0, 0, 0, 60, 0, 2, 0, 58, 0, 2, 0, 58, 0, 2, 0, 58, 0, 0, 0, 59, 0, 4, 0, 72, 0, 4, 0, 72, 0, 0, 0, 62, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 56, 0, 2, 0, 48, 0, 2, 0, 63, 0, 2, 0, 66, 0, - 0, 0, 51, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, - 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 1, 0, 72, 0, 2, 0, 72, 0, - 2, 0, 20, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 20, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 19, 0, 2, 0, 72, 0, - 2, 0, 72, 0, 2, 0, 20, 0, 2, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 19, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 0, 0, 9, 0, 2, 0, 90, 0, 2, 0, 25, 0, 4, 0, 72, 0, 2, 0, 95, 0, 2, 0, 99, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, - 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, - 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, - 1, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 5, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, - 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 72, 0, 4, 0, 72, 0, + 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 27, 0, 4, 0, 72, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 98, 0, 0, 0, 5, 0, 2, 0, 99, 0, 0, 0, 5, 0, 2, 0, 6, 0, 0, 0, 92, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 18, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 79, 0, 0, 0, 79, 0, 2, 0, 84, 0, + 2, 0, 84, 0, 2, 0, 82, 0, 2, 0, 82, 0, 2, 0, 90, 0, 2, 0, 90, 0, 2, 0, 3, 0, 2, 0, 3, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 12, 0, 2, 0, 12, 0, 2, 0, 12, 0, 3, 0, 72, 0, 2, 0, 14, 0, 2, 0, 77, 0, 2, 0, 25, 0, 2, 0, 30, 0, 2, 0, 70, 0, 2, 0, 73, 0, 2, 0, 22, 0, 0, 0, 98, 0, 2, 0, 99, 0, 2, 0, 99, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 6, 0, 2, 0, 77, 0, 6, 0, 72, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 4, 0, 72, 0, 0, 0, 5, 0, 0, 0, 98, 0, 0, 0, 98, 0, 2, 0, 99, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, + 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 18, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, 0, 0, 79, 0, + 0, 0, 79, 0, 0, 0, 79, 0, 2, 0, 84, 0, 2, 0, 84, 0, 2, 0, 84, 0, 2, 0, 82, 0, 2, 0, 82, 0, 2, 0, 90, 0, 2, 0, 3, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 77, 0, 2, 0, 77, 0, 2, 0, 16, 0, 2, 0, 88, 0, 2, 0, 16, 0, 2, 0, 25, 0, 2, 0, 25, 0, 2, 0, 25, 0, 2, 0, 16, 0, 2, 0, 16, 0, + 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 30, 0, 2, 0, 30, 0, 2, 0, 30, 0, 2, 0, 30, 0, 2, 0, 73, 0, 2, 0, 73, 0, 2, 0, 73, 0, 2, 0, 73, 0, 2, 0, 75, 0, 2, 0, 29, 0, 2, 0, 18, 0, 0, 0, 92, 0, 2, 0, 23, 0, 2, 0, 23, 0, 0, 0, 93, 0, 0, 0, 98, 0, 0, 0, 98, 0, 0, 0, 98, 0, 0, 0, 98, 0, 0, 0, 98, 0, 0, 0, 98, 0, + 0, 0, 98, 0, 0, 0, 98, 0, 2, 0, 12, 0, 0, 0, 101, 0, 2, 0, 12, 0, 0, 0, 98, 0, 2, 0, 99, 0, 2, 0, 99, 0, 0, 0, 100, 0, 0, 0, 100, 0, 6, 0, 72, 0, 0, 0, 92, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 0, 0, 9, 0, 0, 0, 79, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 84, 0, 2, 0, 82, 0, + 2, 0, 3, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 29, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 5, 0, 72, 0, 0, 0, 4, 0, 6, 0, 72, 0, 2, 0, 7, 0, 2, 0, 17, 0, 2, 0, 17, 0, + 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 21, 0, 0, 0, 89, 0, 0, 0, 104, 0, 2, 0, 24, 0, 2, 0, 94, 0, 2, 0, 94, 0, 2, 0, 102, 0, 0, 0, 103, 0, 2, 0, 26, 0, 2, 0, 31, 0, 2, 0, 71, 0, 2, 0, 74, 0, 2, 0, 85, 0, 2, 0, 15, 0, 2, 0, 11, 0, 2, 0, 76, 0, 2, 0, 80, 0, 0, 0, 83, 0, 2, 0, 78, 0, 0, 0, 10, 0, 2, 0, 86, 0, 0, 0, 91, 0, 2, 0, 7, 0, + 2, 0, 17, 0, 0, 0, 10, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 0, 0, 105, 0, 2, 0, 28, 0, 2, 0, 13, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, + 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 18, 0, 2, 0, 18, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 79, 0, 2, 0, 84, 0, 2, 0, 3, 0, 2, 0, 3, 0, 2, 0, 3, 0, 2, 0, 14, 0, 2, 0, 14, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 16, 0, 2, 0, 70, 0, 2, 0, 70, 0, 2, 0, 73, 0, 2, 0, 73, 0, 2, 0, 73, 0, 2, 0, 30, 0, 0, 0, 79, 0, + 0, 0, 79, 0, 2, 0, 84, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 84, 0, 0, 0, 79, 0, 2, 0, 18, 0, 0, 0, 5, 0, 0, 0, 5, 0, 2, 0, 12, 0, 2, 0, 12, 0, 2, 0, 99, 0, 0, 0, 98, 0, 0, 0, 98, 0, 2, 0, 8, 0, 2, 0, 8, 0, 2, 0, 18, 0, 2, 0, 84, 0, 2, 0, 84, 0, 2, 0, 25, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, + 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 3, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, + 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 36, 0, 4, 0, 33, 0, 2, 0, 39, 0, 2, 0, 42, 0, 2, 0, 37, 0, 2, 0, 38, 0, 4, 0, 32, 0, 0, 0, 40, 0, 2, 0, 34, 0, + 0, 0, 35, 0, 0, 0, 41, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, + 0, 0, 5, 0, 3, 0, 72, 0, 3, 0, 72, 0, 3, 0, 72, 0, 2, 0, 96, 0, 4, 0, 72, 0, 4, 0, 72, 0, 2, 0, 73, 0, 2, 0, 18, 0, 2, 0, 90, 0, 2, 0, 90, 0, 2, 0, 16, 0, 0, 0, 97, 0, 2, 0, 73, 0, 4, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 18, 0, 2, 0, 90, 0, 2, 0, 14, 0, 2, 0, 77, 0, 2, 0, 30, 0, 2, 0, 70, 0, 2, 0, 99, 0, 2, 0, 99, 0, 0, 0, 79, 0, 0, 0, 98, 0, 0, 0, 81, 0, 4, 0, 72, 0, 0, 0, 9, 0, 2, 0, 82, 0, 2, 0, 16, 0, 0, 0, 87, 0, 0, 0, 79, 0, 2, 0, 3, 0, + 2, 0, 25, 0, 2, 0, 77, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 0, 0, 79, 0, 2, 0, 99, 0, 2, 0, 0, 0, 2, 0, 2, 0, 2, 0, 1, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 6, 0, 2, 0, 18, 0, 2, 0, 16, 0, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 18, 0, 2, 0, 18, 0, 2, 0, 30, 0, 2, 0, 16, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 3, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, + 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 5, 0, 72, 0, + 5, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, + 2, 0, 72, 0, 2, 0, 72, 0, 6, 0, 72, 0, 2, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 4, 0, 72, 0, 3, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, + 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 1, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 2, 0, 43, 0, 2, 0, 45, 0, 2, 0, 45, 0, 2, 0, 49, 0, 2, 0, 49, 0, 0, 0, 46, 0, 4, 0, 72, 0, 0, 0, 67, 0, 4, 0, 72, 0, 0, 0, 69, 0, 0, 0, 69, 0, 4, 0, 72, 0, 4, 0, 72, 0, 1, 0, 50, 0, 0, 0, 64, 0, 0, 0, 68, 0, 0, 0, 52, 0, 0, 0, 52, 0, 0, 0, 52, 0, 2, 0, 53, 0, 2, 0, 47, 0, 2, 0, 65, 0, 2, 0, 54, 0, 1, 0, 55, 0, 2, 0, 61, 0, + 2, 0, 44, 0, 2, 0, 44, 0, 2, 0, 57, 0, 2, 0, 57, 0, 0, 0, 60, 0, 2, 0, 58, 0, 2, 0, 58, 0, 2, 0, 58, 0, 0, 0, 59, 0, 4, 0, 72, 0, 4, 0, 72, 0, 0, 0, 62, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 56, 0, 2, 0, 48, 0, 2, 0, 63, 0, 2, 0, 66, 0, 0, 0, 51, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, + 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, + 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 1, 0, 72, 0, 2, 0, 72, 0, 2, 0, 20, 0, 2, 0, 72, 0, 2, 0, 72, 0, + 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 20, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 19, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 20, 0, 2, 0, 72, 0, + 2, 0, 19, 0, 2, 0, 72, 0, 2, 0, 19, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 19, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 0, 0, 9, 0, 2, 0, 90, 0, 2, 0, 25, 0, 6, 0, 72, 0, 2, 0, 95, 0, 2, 0, 99, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, + 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, + 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, + 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 2, 0, 72, 0, 0, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 4, 0, 72, 0, 0, 0, 72, 0, 2, 0, 72, 0, 1, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 4, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 2, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 5, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, + 6, 0, 72, 0, 6, 0, 72, 0, 6, 0, 72, 0, }; } } diff --git a/src/SixLabors.Fonts/Unicode/Resources/CanonicalCombiningClassTrie.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/CanonicalCombiningClassTrie.Generated.cs new file mode 100644 index 000000000..3929e8047 --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/Resources/CanonicalCombiningClassTrie.Generated.cs @@ -0,0 +1,270 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System; + +namespace SixLabors.Fonts.Unicode.Resources +{ + internal static class CanonicalCombiningClassTrie + { + public static ReadOnlySpan Data => new byte[] + { + 0, 240, 1, 0, 0, 0, 0, 0, 64, 99, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 184, 2, 0, 0, 192, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, + 168, 2, 0, 0, 176, 2, 0, 0, 216, 2, 0, 0, 224, 2, 0, 0, 232, 2, 0, 0, 240, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 248, 2, 0, 0, 0, 3, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 4, 3, 0, 0, 12, 3, 0, 0, 20, 3, 0, 0, + 28, 3, 0, 0, 32, 3, 0, 0, 40, 3, 0, 0, 46, 3, 0, 0, 54, 3, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 59, 3, 0, 0, 67, 3, 0, 0, 71, 3, 0, 0, 79, 3, 0, 0, 87, 3, 0, 0, 95, 3, 0, 0, 168, 2, 0, 0, 176, 2, 0, 0, 93, 3, 0, 0, 101, 3, 0, 0, 109, 3, 0, 0, 116, 3, 0, 0, 120, 3, 0, 0, 168, 2, 0, 0, 127, 3, 0, 0, 168, 2, 0, 0, 135, 3, 0, 0, 143, 3, 0, 0, + 168, 2, 0, 0, 151, 3, 0, 0, 159, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 151, 3, 0, 0, 165, 3, 0, 0, 169, 3, 0, 0, 168, 2, 0, 0, 151, 3, 0, 0, 165, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 151, 3, 0, 0, 165, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 151, 3, 0, 0, 165, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 165, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 151, 3, 0, 0, 177, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 151, 3, 0, 0, 165, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 183, 3, 0, 0, 165, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 191, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 194, 3, 0, 0, 201, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 204, 3, 0, 0, 211, 3, 0, 0, 168, 2, 0, 0, 214, 3, 0, 0, 221, 3, 0, 0, + 168, 2, 0, 0, 228, 3, 0, 0, 236, 3, 0, 0, 168, 2, 0, 0, 243, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 246, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 253, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 1, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 9, 4, 0, 0, 15, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 21, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 29, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 32, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 39, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 47, 4, 0, 0, 168, 2, 0, 0, 55, 4, 0, 0, 63, 4, 0, 0, 71, 4, 0, 0, 168, 2, 0, 0, 153, 3, 0, 0, 78, 4, 0, 0, 84, 4, 0, 0, 168, 2, 0, 0, 90, 4, 0, 0, + 168, 2, 0, 0, 97, 4, 0, 0, 168, 2, 0, 0, 102, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 108, 4, 0, 0, 116, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 124, 4, 0, 0, 129, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 137, 4, 0, 0, 145, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 150, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 155, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 163, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 171, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 175, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 182, 4, 0, 0, 190, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 198, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 192, 3, 0, 0, 17, 4, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 78, 4, 0, 0, 206, 4, 0, 0, 168, 2, 0, 0, 212, 4, 0, 0, 216, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 103, 4, 0, 0, 224, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 228, 4, 0, 0, 236, 4, 0, 0, 239, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 165, 3, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 245, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 253, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 32, 11, 0, 0, 32, 11, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 96, 11, 0, 0, 160, 11, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, + 160, 10, 0, 0, 224, 11, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 160, 10, 0, 0, 16, 12, 0, 0, 80, 12, 0, 0, 128, 12, 0, 0, 184, 12, 0, 0, 160, 10, 0, 0, 236, 12, 0, 0, 28, 13, 0, 0, 92, 13, 0, 0, 160, 10, 0, 0, 116, 13, 0, 0, 94, 8, 0, 0, 142, 8, 0, 0, 204, 8, 0, 0, 11, 9, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, + 8, 1, 0, 0, 8, 1, 0, 0, 70, 9, 0, 0, 111, 9, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 175, 9, 0, 0, 8, 1, 0, 0, 8, 1, 0, 0, 228, 9, 0, 0, 8, 1, 0, 0, 36, 10, 0, 0, 94, 10, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 1, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 9, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 12, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 19, 5, 0, 0, 23, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 31, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 38, 5, 0, 0, 168, 2, 0, 0, 44, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 50, 5, 0, 0, 168, 2, 0, 0, 54, 5, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 62, 5, 0, 0, 168, 2, 0, 0, 70, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 192, 3, 0, 0, 74, 5, 0, 0, 168, 2, 0, 0, 82, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 90, 5, 0, 0, 185, 3, 0, 0, 168, 2, 0, 0, 103, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 98, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 101, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 107, 5, 0, 0, 168, 2, 0, 0, 110, 5, 0, 0, 165, 3, 0, 0, 118, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 124, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 132, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 140, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 155, 4, 0, 0, 148, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 155, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 151, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 79, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 82, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 157, 5, 0, 0, 165, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 224, 4, 0, 0, 168, 2, 0, 0, 15, 4, 0, 0, 80, 5, 0, 0, 168, 2, 0, 0, 167, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 155, 4, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 175, 5, 0, 0, 168, 2, 0, 0, 178, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 186, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 191, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 195, 5, 0, 0, 168, 2, 0, 0, 201, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 207, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 212, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 220, 5, 0, 0, 228, 5, 0, 0, 234, 5, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 242, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 250, 5, 0, 0, + 2, 6, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 7, 6, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 201, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 12, 6, 0, 0, 168, 2, 0, 0, 36, 5, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 17, 6, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 22, 6, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 30, 6, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 36, 6, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 43, 6, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, + 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 168, 2, 0, 0, 167, 2, 1, 0, 167, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 232, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 232, 0, 0, 0, 216, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 202, 0, 0, 0, 202, 0, 0, 0, + 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 202, 0, 0, 0, 202, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, + 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 240, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, + 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 232, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 233, 0, 0, 0, 234, 0, 0, 0, 234, 0, 0, 0, 233, 0, 0, 0, 234, 0, 0, 0, 234, 0, 0, 0, 233, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 222, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 222, 0, 0, 0, 228, 0, 0, 0, 230, 0, 0, 0, 10, 0, 0, 0, 11, 0, 0, 0, 12, 0, 0, 0, 13, 0, 0, 0, 14, 0, 0, 0, 15, 0, 0, 0, 16, 0, 0, 0, 17, 0, 0, 0, 18, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, + 20, 0, 0, 0, 21, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 30, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 28, 0, 0, 0, 29, 0, 0, 0, 30, 0, 0, 0, 31, 0, 0, 0, + 32, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 220, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, + 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 27, 0, 0, 0, 28, 0, 0, 0, 29, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 0, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 103, 0, 0, 0, 103, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 0, 0, 0, 107, 0, 0, 0, 107, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 118, 0, 0, 0, 118, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 122, 0, 0, 0, 122, 0, 0, 0, 122, 0, 0, 0, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 0, 0, 0, 130, 0, 0, 0, 0, 0, 0, 0, 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 130, 0, 0, 0, 130, 0, 0, 0, 130, 0, 0, 0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 130, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 228, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, + 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 234, 0, 0, 0, 214, 0, 0, 0, 220, 0, 0, 0, 202, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 232, 0, 0, 0, 228, 0, 0, 0, 228, 0, 0, 0, 220, 0, 0, 0, 218, 0, 0, 0, 230, 0, 0, 0, 233, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218, 0, 0, 0, 228, 0, 0, 0, 232, 0, 0, 0, 222, 0, 0, 0, 224, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, + 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 1, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, + 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 216, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 226, 0, 0, 0, 216, 0, 0, 0, 216, 0, 0, 0, 216, 0, 0, 0, 216, 0, 0, 0, 216, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 232, 0, 0, 0, 232, 0, 0, 0, 220, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, + 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, + 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 230, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + } +} diff --git a/src/SixLabors.Fonts/Unicode/Resources/IndicShapingData.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/IndicShapingData.Generated.cs index 1de555e15..729aa5d54 100644 --- a/src/SixLabors.Fonts/Unicode/Resources/IndicShapingData.Generated.cs +++ b/src/SixLabors.Fonts/Unicode/Resources/IndicShapingData.Generated.cs @@ -9,242 +9,169 @@ namespace SixLabors.Fonts.Unicode.Resources { internal static partial class IndicShapingData { - public static int[][] StateTable => new int[234][] + public static int[][] StateTable => new int[161][] { - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,2,3,4,5,6,7,8,9,10,11,11,12,8,13,14,15,16,17,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,19,20,21,22,23,24,25,0,0,26,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,29,30,31,32,33,34,35,0,0,36,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,39,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,40,0,0,0,41,42,0,43,10,0,0,0,0,0,40,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,45,46,46,8,9,0,0,0,12,8,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,0,45,46,46,8,9,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,47,48,49,50,8,9,10,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,0,0,10,0,0,51,10,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,51 }, - new int[] { 0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,52,53,54,55,56,57,58,0,0,59,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,4,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,2,3,4,5,6,7,8,9,10,11,11,12,8,0,2,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,19,62,21,22,23,24,25,0,0,26,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,63,64,64,8,9,10,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,65,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,66,0,67,67,0,68,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68 }, - new int[] { 0,2,0,0,0,0,0,0,0,0,11,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,70,71,0,0,41,42,0,43,10,0,0,0,0,0,70,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,72,20,73,73,23,24,25,0,0,0,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,74,0,0,0,75,76,0,77,25,0,0,0,0,0,74,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,0,79,80,80,23,24,0,0,0,26,23,0,0,0,0,0,0,0,0,0,0,0,0,79,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,19,20,21,73,23,24,25,0,0,26,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,81,82,83,84,23,24,25,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,0,25,0,0,85,25,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85 }, - new int[] { 0,0,0,0,0,0,0,0,0,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,19,20,73,73,23,24,25,0,0,0,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,86,87,87,23,24,25,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,88,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,89,90,0,0,75,76,0,77,25,0,0,0,0,0,89,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,91,30,92,32,33,34,35,0,0,0,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,93,0,0,0,94,95,0,96,35,0,0,0,0,0,93,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,0,98,99,99,33,34,0,0,0,36,33,0,0,0,0,0,0,0,0,0,0,0,0,98,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,98,99,99,33,34,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,98,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,100,101,102,103,33,34,35,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,0,35,0,0,104,35,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,104 }, - new int[] { 0,0,0,0,0,0,0,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,29,30,92,32,33,34,35,0,0,0,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,105,106,106,33,34,35,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,107,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,108,109,0,0,94,95,0,96,35,0,0,0,0,0,108,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,0,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,110,5,111,112,8,9,10,0,0,113,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,0,0,114,114,0,43,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,40,0,115,0,114,114,0,43,10,0,0,0,0,0,40,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,0,10,0,0,51,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,51 }, - new int[] { 0,116,71,0,0,0,0,0,0,0,0,0,0,0,0,116,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,40,0,0,0,114,42,0,43,10,0,0,0,0,0,40,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,0,46,46,8,117,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,117 }, - new int[] { 0,0,0,0,48,49,50,8,9,10,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,0,0,49,49,8,9,10,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,0,0,46,46,8,9,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,0,118,46,46,8,9,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,0,0,10,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,119,53,55,55,56,57,58,0,0,0,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,120,0,0,0,121,122,0,123,58,0,0,0,0,0,120,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,0,125,126,126,56,57,0,0,0,59,56,0,0,0,0,0,0,0,0,0,0,0,0,125,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,125,126,126,56,57,0,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,125,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,127,128,129,130,56,57,58,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,0,58,0,0,131,58,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131 }, - new int[] { 0,0,0,0,0,0,0,0,0,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,52,53,55,55,56,57,58,0,0,0,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,132,133,133,56,57,58,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,134,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,135,136,0,0,121,122,0,123,58,0,0,0,0,0,135,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,74,3,4,5,137,138,8,139,140,0,11,12,8,0,74,15,0,0,0,0,0,0,0,0,0,141,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,0,0,41,142,0,43,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,143,46,46,8,9,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,143,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,116,71,0,0,41,142,0,43,10,0,0,0,0,0,116,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,0,67,67,0,68,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68 }, - new int[] { 0,0,0,0,0,0,0,0,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68 }, - new int[] { 0,0,0,0,0,69,0,0,144,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,144 }, - new int[] { 0,0,0,0,0,0,0,0,0,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,145,5,146,147,8,9,10,0,0,148,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,0,0,114,114,0,43,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,20,73,73,23,24,25,0,0,0,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,79,80,80,23,24,0,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,79,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,149,20,150,151,23,24,25,0,0,152,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,0,153,153,0,77,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,74,0,154,0,153,153,0,77,25,0,0,0,0,0,74,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,0,0,25,0,0,85,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85 }, - new int[] { 0,155,90,0,0,0,0,0,0,0,0,0,0,0,0,155,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,74,0,0,0,153,76,0,77,25,0,0,0,0,0,74,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,0,0,80,80,23,156,0,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,156 }, - new int[] { 0,0,0,0,82,83,84,23,24,25,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,0,83,83,23,24,25,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,0,80,80,23,24,0,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,157,80,80,23,24,0,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,0,25,0,0,0,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,75,158,0,77,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,0,159,80,80,23,24,0,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,159,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,155,90,0,0,75,158,0,77,25,0,0,0,0,0,155,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,160,20,161,162,23,24,25,0,0,163,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,0,153,153,0,77,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,0,30,92,32,33,34,35,0,0,0,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,98,99,99,33,34,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,98,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,164,30,165,166,33,34,35,0,0,167,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,0,168,168,0,96,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,93,0,169,0,168,168,0,96,35,0,0,0,0,0,93,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,0,0,35,0,0,104,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,104 }, - new int[] { 0,170,109,0,0,0,0,0,0,0,0,0,0,0,0,170,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,93,0,0,0,168,95,0,96,35,0,0,0,0,0,93,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,0,0,99,99,33,171,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,171 }, - new int[] { 0,0,0,0,101,102,103,33,34,35,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,0,102,102,33,34,35,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,0,99,99,33,34,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,172,99,99,33,34,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,0,35,0,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,94,173,0,96,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,0,174,99,99,33,34,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,174,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,170,109,0,0,94,173,0,96,35,0,0,0,0,0,170,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,175,30,176,177,33,34,35,0,0,178,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,0,168,168,0,96,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,39,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,0,45,46,46,8,9,0,0,0,113,8,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,110,5,111,7,8,9,10,0,0,113,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,110,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,0,0,0,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,40,0,0,0,114,114,0,43,10,0,0,0,0,0,40,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,179,0,180,181,0,43,10,0,0,182,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,183,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,53,55,55,56,57,58,0,0,0,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,184,53,185,186,56,57,58,0,0,187,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,0,188,188,0,123,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,120,0,189,0,188,188,0,123,58,0,0,0,0,0,120,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,0,0,58,0,0,131,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131 }, - new int[] { 0,190,136,0,0,0,0,0,0,0,0,0,0,0,0,190,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,120,0,0,0,188,122,0,123,58,0,0,0,0,0,120,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,0,0,126,126,56,191,0,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,191 }, - new int[] { 0,0,0,0,128,129,130,56,57,58,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,0,129,129,56,57,58,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,0,126,126,56,57,0,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,192,126,126,56,57,0,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,0,58,0,0,0,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,121,193,0,123,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,0,194,126,126,56,57,0,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,194,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,190,136,0,0,121,193,0,123,58,0,0,0,0,0,190,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,195,53,196,197,56,57,58,0,0,198,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,0,188,188,0,123,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,0,45,199,199,8,139,25,0,0,12,8,0,0,0,0,0,0,0,0,0,0,0,0,200,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,74,0,154,45,199,199,8,139,25,0,0,0,8,0,74,0,0,0,0,0,0,0,0,0,0,200,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,0,0,140,0,0,201,140,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,201 }, - new int[] { 0,0,0,0,0,0,0,0,0,140,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,202,203,0,0,41,42,0,43,10,0,0,0,0,0,202,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,41,0,114,114,0,43,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,0,114,142,0,43,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,0,69,0,0,0,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,39,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,0,45,46,46,8,9,0,0,0,148,8,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,145,5,146,7,8,9,10,0,0,148,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,145,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,9 }, - new int[] { 0,0,0,72,20,73,73,23,24,25,0,0,0,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,79,80,80,23,24,0,0,0,152,23,0,0,0,0,0,0,0,0,0,0,0,0,79,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,149,20,150,73,23,24,25,0,0,152,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,149,20,73,73,23,24,25,0,0,0,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,0,0,0,0,77,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,74,0,0,0,153,153,0,77,25,0,0,0,0,0,74,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,204,0,205,206,0,77,25,0,0,207,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,208,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,75,0,153,153,0,77,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,0,0,153,158,0,77,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,72,20,73,73,23,24,25,0,0,0,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,0,79,80,80,23,24,0,0,0,163,23,0,0,0,0,0,0,0,0,0,0,0,0,79,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,160,20,161,73,23,24,25,0,0,163,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,160,20,73,73,23,24,25,0,0,0,23,0,0,27,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,24 }, - new int[] { 0,0,0,209,30,92,92,33,34,35,0,0,0,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,98,99,99,33,34,0,0,0,167,33,0,0,0,0,0,0,0,0,0,0,0,0,98,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,164,30,165,92,33,34,35,0,0,167,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,164,30,92,92,33,34,35,0,0,0,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,93,0,0,0,168,168,0,96,35,0,0,0,0,0,93,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,210,0,211,212,0,96,35,0,0,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,214,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,94,0,168,168,0,96,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,0,0,168,173,0,96,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,209,30,92,92,33,34,35,0,0,0,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,0,98,99,99,33,34,0,0,0,178,33,0,0,0,0,0,0,0,0,0,0,0,0,98,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,175,30,176,92,33,34,35,0,0,178,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,175,30,92,92,33,34,35,0,0,0,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,71,0,114,114,0,43,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,0,0,0,0,43,0,0,0,182,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,179,0,180,114,0,43,10,0,0,182,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,179,0,114,114,0,43,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,119,53,55,55,56,57,58,0,0,0,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,125,126,126,56,57,0,0,0,187,56,0,0,0,0,0,0,0,0,0,0,0,0,125,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,184,53,185,55,56,57,58,0,0,187,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,184,53,55,55,56,57,58,0,0,0,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,0,0,0,0,123,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,120,0,0,0,188,188,0,123,58,0,0,0,0,0,120,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,215,0,216,217,0,123,58,0,0,218,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,219,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,121,0,188,188,0,123,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,0,0,188,193,0,123,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,119,53,55,55,56,57,58,0,0,0,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,125,126,126,56,57,0,0,0,198,56,0,0,0,0,0,0,0,0,0,0,0,0,125,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,195,53,196,55,56,57,58,0,0,198,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,195,53,55,55,56,57,58,0,0,0,56,0,0,60,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,57 }, - new int[] { 0,0,0,0,0,46,46,8,220,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,220 }, - new int[] { 0,221,90,0,0,114,42,0,43,10,0,0,0,0,0,221,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,43 }, - new int[] { 0,0,0,0,0,140,0,0,0,140,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,222,5,223,224,8,139,140,0,0,225,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,0,0,226,226,0,227,140,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,227 }, - new int[] { 0,0,0,90,0,153,153,0,77,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,0,0,0,0,0,77,0,0,0,207,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,204,0,205,153,0,77,25,0,0,207,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,204,0,153,153,0,77,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,82,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,30,92,92,33,34,35,0,0,0,33,0,0,37,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,34 }, - new int[] { 0,0,0,109,0,168,168,0,96,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,0,0,0,0,0,96,0,0,0,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,210,0,211,168,0,96,35,0,0,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,210,0,168,168,0,96,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,136,0,188,188,0,123,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,0,0,0,0,0,123,0,0,0,218,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,215,0,216,188,0,123,58,0,0,218,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,215,0,188,188,0,123,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,123 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,25,0,0,85,25,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85 }, - new int[] { 0,0,0,228,5,229,230,8,139,140,0,0,231,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,232,5,233,233,8,139,140,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,0,45,46,46,8,139,0,0,0,225,8,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,222,5,223,233,8,139,140,0,0,225,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,222,5,233,233,8,139,140,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,0,0,0,0,0,227,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,227 }, - new int[] { 0,0,0,0,0,140,0,0,201,140,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,201 }, - new int[] { 0,0,0,232,5,233,233,8,139,140,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,0,45,46,46,8,139,0,0,0,231,8,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,228,5,229,233,8,139,140,0,0,231,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,228,5,233,233,8,139,140,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,0,5,233,233,8,139,140,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,0,139 }, - new int[] { 0,0,0,0,45,46,46,8,139,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,0,0,139 } + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,2,3,4,5,6,7,8,9,10,11,11,12,8,13,14,15,16,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,18,19,20,21,22,23,24,0,0,25,22,0,0,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,27,28,29,30,31,32,33,0,0,34,31,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,36,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,37,0,0,0,38,39,0,40,10,0,0,0,0,0,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40 }, + new int[] { 0,0,0,0,41,42,42,8,9,0,0,0,12,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,0,41,42,42,8,9,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,43,44,45,46,8,9,10,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,0,0,10,0,0,47,10,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47 }, + new int[] { 0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,48,49,50,51,52,53,54,0,0,55,52,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,4,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,2,3,4,5,6,7,8,9,10,11,11,12,8,0,2,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,18,57,20,21,22,23,24,0,0,25,22,0,0,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,58,59,59,8,9,10,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,60,0,61,61,0,62,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,62 }, + new int[] { 0,2,0,0,0,0,0,0,0,0,11,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,64,19,65,65,22,23,24,0,0,0,22,0,0,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,66,0,0,0,67,68,0,69,24,0,0,0,0,0,66,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69 }, + new int[] { 0,0,0,0,70,71,71,22,23,0,0,0,25,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,18,19,20,65,22,23,24,0,0,25,22,0,0,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,72,73,74,75,22,23,24,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,0,24,0,0,76,24,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76 }, + new int[] { 0,0,0,0,0,0,0,0,0,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,18,19,65,65,22,23,24,0,0,0,22,0,0,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,77,78,78,22,23,24,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,79,28,80,30,31,32,33,0,0,0,31,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,81,0,0,0,82,83,0,84,33,0,0,0,0,0,81,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84 }, + new int[] { 0,0,0,0,85,86,86,31,32,0,0,0,34,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,85,86,86,31,32,0,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,87,88,89,90,31,32,33,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,0,33,0,0,91,33,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91 }, + new int[] { 0,0,0,0,0,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,27,28,80,30,31,32,33,0,0,0,31,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,92,93,93,31,32,33,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,94,5,95,96,8,9,10,0,0,97,8,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,0,0,98,98,0,40,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40 }, + new int[] { 0,37,0,99,0,98,98,0,40,10,0,0,0,0,0,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40 }, + new int[] { 0,0,0,0,0,10,0,0,47,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47 }, + new int[] { 0,37,0,0,0,98,39,0,40,10,0,0,0,0,0,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40 }, + new int[] { 0,0,0,0,0,42,42,8,100,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100 }, + new int[] { 0,0,0,0,44,45,46,8,9,10,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,0,0,45,45,8,9,10,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,0,0,42,42,8,9,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,0,101,42,42,8,9,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,0,0,10,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,102,49,51,51,52,53,54,0,0,0,52,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,103,0,0,0,104,105,0,106,54,0,0,0,0,0,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106 }, + new int[] { 0,0,0,0,107,108,108,52,53,0,0,0,55,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,0,107,108,108,52,53,0,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,109,110,111,112,52,53,54,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,0,0,54,0,0,113,54,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,113 }, + new int[] { 0,0,0,0,0,0,0,0,0,54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,48,49,51,51,52,53,54,0,0,0,52,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,0,114,115,115,52,53,54,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,66,3,4,5,116,117,8,118,119,0,11,12,8,0,66,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118 }, + new int[] { 0,0,0,0,0,38,120,0,40,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40 }, + new int[] { 0,0,0,0,121,42,42,8,9,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,0,0,61,61,0,62,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,62 }, + new int[] { 0,0,0,0,0,0,0,0,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,62 }, + new int[] { 0,0,0,0,0,63,0,0,122,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,122 }, + new int[] { 0,0,0,0,0,0,0,0,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,19,65,65,22,23,24,0,0,0,22,0,0,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,70,71,71,22,23,0,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,123,19,124,125,22,23,24,0,0,126,22,0,0,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,0,127,127,0,69,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69 }, + new int[] { 0,66,0,128,0,127,127,0,69,24,0,0,0,0,0,66,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69 }, + new int[] { 0,0,0,0,0,24,0,0,76,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76 }, + new int[] { 0,66,0,0,0,127,68,0,69,24,0,0,0,0,0,66,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69 }, + new int[] { 0,0,0,0,0,71,71,22,129,0,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,129 }, + new int[] { 0,0,0,0,73,74,75,22,23,24,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,0,74,74,22,23,24,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,0,71,71,22,23,0,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,130,71,71,22,23,0,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,0,24,0,0,0,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,67,131,0,69,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69 }, + new int[] { 0,0,0,0,132,71,71,22,23,0,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,28,80,30,31,32,33,0,0,0,31,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,85,86,86,31,32,0,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,133,28,134,135,31,32,33,0,0,136,31,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,0,137,137,0,84,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84 }, + new int[] { 0,81,0,138,0,137,137,0,84,33,0,0,0,0,0,81,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84 }, + new int[] { 0,0,0,0,0,33,0,0,91,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91 }, + new int[] { 0,81,0,0,0,137,83,0,84,33,0,0,0,0,0,81,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84 }, + new int[] { 0,0,0,0,0,86,86,31,139,0,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,139 }, + new int[] { 0,0,0,0,88,89,90,31,32,33,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,0,89,89,31,32,33,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,0,86,86,31,32,0,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,140,86,86,31,32,0,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,0,33,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,82,141,0,84,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84 }, + new int[] { 0,0,0,0,142,86,86,31,32,0,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,36,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,0,41,42,42,8,9,0,0,0,97,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,94,5,95,7,8,9,10,0,0,97,8,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,94,5,7,7,8,9,10,0,0,0,8,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9 }, + new int[] { 0,0,0,0,0,0,0,0,40,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40 }, + new int[] { 0,37,0,0,0,98,98,0,40,10,0,0,0,0,0,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,143,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,49,51,51,52,53,54,0,0,0,52,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,144,49,145,146,52,53,54,0,0,147,52,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,0,0,148,148,0,106,54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106 }, + new int[] { 0,103,0,149,0,148,148,0,106,54,0,0,0,0,0,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106 }, + new int[] { 0,0,0,0,0,54,0,0,113,54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,113 }, + new int[] { 0,103,0,0,0,148,105,0,106,54,0,0,0,0,0,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106 }, + new int[] { 0,0,0,0,0,108,108,52,150,0,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150 }, + new int[] { 0,0,0,0,110,111,112,52,53,54,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,0,0,111,111,52,53,54,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,0,0,108,108,52,53,0,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,0,151,108,108,52,53,0,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,0,0,54,0,0,0,54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,104,152,0,106,54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106 }, + new int[] { 0,0,0,0,153,108,108,52,53,0,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,0,41,154,154,8,118,24,0,0,12,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118 }, + new int[] { 0,66,0,128,41,154,154,8,118,24,0,0,0,8,0,66,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118 }, + new int[] { 0,0,0,0,0,119,0,0,155,119,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,155 }, + new int[] { 0,0,0,0,0,0,0,0,0,119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,38,0,98,98,0,40,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40 }, + new int[] { 0,0,0,0,0,98,120,0,40,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40 }, + new int[] { 0,0,0,0,0,63,0,0,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,64,19,65,65,22,23,24,0,0,0,22,0,0,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,70,71,71,22,23,0,0,0,126,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,123,19,124,65,22,23,24,0,0,126,22,0,0,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,123,19,65,65,22,23,24,0,0,0,22,0,0,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 }, + new int[] { 0,0,0,0,0,0,0,0,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69 }, + new int[] { 0,66,0,0,0,127,127,0,69,24,0,0,0,0,0,66,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,156,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,67,0,127,127,0,69,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69 }, + new int[] { 0,0,0,0,0,127,131,0,69,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69 }, + new int[] { 0,0,0,157,28,80,80,31,32,33,0,0,0,31,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,85,86,86,31,32,0,0,0,136,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,133,28,134,80,31,32,33,0,0,136,31,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,133,28,80,80,31,32,33,0,0,0,31,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,0,0,0,0,84,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84 }, + new int[] { 0,81,0,0,0,137,137,0,84,33,0,0,0,0,0,81,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,158,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,82,0,137,137,0,84,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84 }, + new int[] { 0,0,0,0,0,137,141,0,84,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,102,49,51,51,52,53,54,0,0,0,52,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,0,107,108,108,52,53,0,0,0,147,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,144,49,145,51,52,53,54,0,0,147,52,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,144,49,51,51,52,53,54,0,0,0,52,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, + new int[] { 0,0,0,0,0,0,0,0,106,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106 }, + new int[] { 0,103,0,0,0,148,148,0,106,54,0,0,0,0,0,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,159,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,104,0,148,148,0,106,54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106 }, + new int[] { 0,0,0,0,0,148,152,0,106,54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106 }, + new int[] { 0,0,0,0,0,42,42,8,160,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,160 }, + new int[] { 0,0,0,0,0,119,0,0,0,119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,73,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,28,80,80,31,32,33,0,0,0,31,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,110,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,24,0,0,76,24,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76 } }; public static bool[] AcceptingStates => new bool[] @@ -269,7 +196,6 @@ internal static partial class IndicShapingData false, true, true, - true, false, true, true, @@ -279,7 +205,6 @@ internal static partial class IndicShapingData true, true, true, - true, false, true, true, @@ -295,8 +220,6 @@ internal static partial class IndicShapingData true, false, true, - false, - true, true, false, false, @@ -312,22 +235,17 @@ internal static partial class IndicShapingData true, true, true, - true, false, true, - true, false, true, true, true, - true, - true, false, true, true, true, true, - false, true, false, true, @@ -338,15 +256,11 @@ internal static partial class IndicShapingData true, false, true, - true, - true, - true, false, true, true, true, true, - false, true, false, true, @@ -357,15 +271,11 @@ internal static partial class IndicShapingData true, false, true, - true, - true, - true, false, true, true, false, true, - true, false, false, true, @@ -373,7 +283,6 @@ internal static partial class IndicShapingData true, true, true, - false, true, false, true, @@ -391,25 +300,12 @@ internal static partial class IndicShapingData true, true, true, - true, - true, - true, - true, false, true, true, - true, false, true, - true, - false, - true, - true, - false, false, - true, - true, - true, false, true, true, @@ -419,73 +315,31 @@ internal static partial class IndicShapingData true, false, true, - true, false, false, true, true, - true, false, true, - true, - true, false, true, true, false, true, false, - true, - true, false, true, true, false, - false, - true, - true, - true, - false, - true, - true, - false, - true, - true, - true, - true, - true, - false, - true, - true, - false, - true, true, false, true, - true, - false, - true, - false, - true, - true, - false, - true, - true, - true, false, - true, - true, false, - true, - true, - false, - true, - true, - true, - false + true }; - public static string[][] Tags => new string[234][] + public static string[][] Tags => new string[161][] { Array.Empty(), new string[] { "broken_cluster" }, @@ -505,7 +359,6 @@ internal static partial class IndicShapingData new string[] { "broken_cluster" }, new string[] { "symbol_cluster" }, Array.Empty(), - new string[] { "broken_cluster" }, new string[] { "consonant_syllable" }, new string[] { "consonant_syllable" }, Array.Empty(), @@ -515,7 +368,6 @@ internal static partial class IndicShapingData new string[] { "consonant_syllable" }, new string[] { "consonant_syllable" }, new string[] { "consonant_syllable" }, - new string[] { "consonant_syllable" }, new string[] { "vowel_syllable" }, new string[] { "vowel_syllable" }, Array.Empty(), @@ -525,13 +377,11 @@ internal static partial class IndicShapingData new string[] { "vowel_syllable" }, new string[] { "vowel_syllable" }, new string[] { "vowel_syllable" }, - new string[] { "vowel_syllable" }, new string[] { "broken_cluster" }, new string[] { "broken_cluster" }, new string[] { "broken_cluster" }, new string[] { "broken_cluster" }, new string[] { "broken_cluster" }, - Array.Empty(), new string[] { "broken_cluster" }, Array.Empty(), new string[] { "broken_cluster" }, @@ -548,24 +398,19 @@ internal static partial class IndicShapingData new string[] { "standalone_cluster" }, new string[] { "standalone_cluster" }, new string[] { "standalone_cluster" }, - new string[] { "standalone_cluster" }, new string[] { "consonant_syllable","broken_cluster" }, new string[] { "broken_cluster" }, Array.Empty(), - new string[] { "broken_cluster" }, new string[] { "symbol_cluster" }, Array.Empty(), new string[] { "symbol_cluster" }, new string[] { "symbol_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, new string[] { "consonant_syllable" }, Array.Empty(), new string[] { "consonant_syllable" }, new string[] { "consonant_syllable" }, new string[] { "consonant_syllable" }, new string[] { "consonant_syllable" }, - Array.Empty(), new string[] { "consonant_syllable" }, Array.Empty(), new string[] { "consonant_syllable" }, @@ -575,16 +420,12 @@ internal static partial class IndicShapingData new string[] { "consonant_syllable" }, new string[] { "consonant_syllable" }, Array.Empty(), - new string[] { "consonant_syllable" }, - new string[] { "consonant_syllable" }, - new string[] { "consonant_syllable" }, new string[] { "vowel_syllable" }, Array.Empty(), new string[] { "vowel_syllable" }, new string[] { "vowel_syllable" }, new string[] { "vowel_syllable" }, new string[] { "vowel_syllable" }, - Array.Empty(), new string[] { "vowel_syllable" }, Array.Empty(), new string[] { "vowel_syllable" }, @@ -594,16 +435,12 @@ internal static partial class IndicShapingData new string[] { "vowel_syllable" }, new string[] { "vowel_syllable" }, Array.Empty(), - new string[] { "vowel_syllable" }, - new string[] { "vowel_syllable" }, - new string[] { "vowel_syllable" }, new string[] { "broken_cluster" }, Array.Empty(), new string[] { "broken_cluster" }, new string[] { "broken_cluster" }, Array.Empty(), new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, Array.Empty(), Array.Empty(), new string[] { "standalone_cluster" }, @@ -611,7 +448,6 @@ internal static partial class IndicShapingData new string[] { "standalone_cluster" }, new string[] { "standalone_cluster" }, new string[] { "standalone_cluster" }, - Array.Empty(), new string[] { "standalone_cluster" }, Array.Empty(), new string[] { "standalone_cluster" }, @@ -621,33 +457,20 @@ internal static partial class IndicShapingData new string[] { "standalone_cluster" }, new string[] { "standalone_cluster" }, Array.Empty(), - new string[] { "standalone_cluster" }, - new string[] { "standalone_cluster" }, - new string[] { "standalone_cluster" }, new string[] { "consonant_syllable" }, new string[] { "consonant_syllable" }, new string[] { "consonant_syllable","broken_cluster" }, new string[] { "consonant_syllable","broken_cluster" }, new string[] { "broken_cluster" }, new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, new string[] { "symbol_cluster" }, - new string[] { "broken_cluster" }, - Array.Empty(), - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, new string[] { "consonant_syllable" }, Array.Empty(), new string[] { "consonant_syllable" }, new string[] { "consonant_syllable" }, Array.Empty(), new string[] { "consonant_syllable" }, - new string[] { "consonant_syllable" }, - Array.Empty(), Array.Empty(), - new string[] { "consonant_syllable" }, - new string[] { "consonant_syllable" }, - new string[] { "consonant_syllable" }, Array.Empty(), new string[] { "consonant_syllable" }, new string[] { "consonant_syllable" }, @@ -657,19 +480,10 @@ internal static partial class IndicShapingData new string[] { "vowel_syllable" }, Array.Empty(), new string[] { "vowel_syllable" }, - new string[] { "vowel_syllable" }, Array.Empty(), Array.Empty(), new string[] { "vowel_syllable" }, new string[] { "vowel_syllable" }, - new string[] { "vowel_syllable" }, - Array.Empty(), - new string[] { "vowel_syllable" }, - new string[] { "vowel_syllable" }, - new string[] { "broken_cluster" }, - Array.Empty(), - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, Array.Empty(), new string[] { "standalone_cluster" }, Array.Empty(), @@ -677,50 +491,17 @@ internal static partial class IndicShapingData new string[] { "standalone_cluster" }, Array.Empty(), new string[] { "standalone_cluster" }, - new string[] { "standalone_cluster" }, Array.Empty(), Array.Empty(), new string[] { "standalone_cluster" }, new string[] { "standalone_cluster" }, - new string[] { "standalone_cluster" }, Array.Empty(), - new string[] { "standalone_cluster" }, - new string[] { "standalone_cluster" }, - Array.Empty(), - new string[] { "broken_cluster" }, new string[] { "consonant_syllable","broken_cluster" }, - new string[] { "consonant_syllable","broken_cluster" }, - new string[] { "consonant_syllable","broken_cluster" }, - new string[] { "consonant_syllable" }, - Array.Empty(), - new string[] { "consonant_syllable" }, - new string[] { "consonant_syllable" }, - Array.Empty(), - new string[] { "vowel_syllable" }, - new string[] { "vowel_syllable" }, Array.Empty(), new string[] { "vowel_syllable" }, - new string[] { "vowel_syllable" }, - Array.Empty(), - new string[] { "standalone_cluster" }, - Array.Empty(), - new string[] { "standalone_cluster" }, - new string[] { "standalone_cluster" }, - Array.Empty(), - new string[] { "consonant_syllable" }, - new string[] { "consonant_syllable","broken_cluster" }, - new string[] { "consonant_syllable","broken_cluster" }, Array.Empty(), - new string[] { "consonant_syllable","broken_cluster" }, - new string[] { "consonant_syllable","broken_cluster" }, Array.Empty(), - new string[] { "consonant_syllable","broken_cluster" }, - new string[] { "consonant_syllable","broken_cluster" }, - Array.Empty(), - new string[] { "consonant_syllable","broken_cluster" }, - new string[] { "consonant_syllable","broken_cluster" }, - new string[] { "consonant_syllable","broken_cluster" }, - Array.Empty() + new string[] { "consonant_syllable" } }; } } diff --git a/src/SixLabors.Fonts/Unicode/Resources/IndicShapingData.cs b/src/SixLabors.Fonts/Unicode/Resources/IndicShapingData.cs index 5287b8b47..8e6aebe5c 100644 --- a/src/SixLabors.Fonts/Unicode/Resources/IndicShapingData.cs +++ b/src/SixLabors.Fonts/Unicode/Resources/IndicShapingData.cs @@ -9,41 +9,15 @@ internal static partial class IndicShapingData { /// /// Script shaping category values used for Indic, Khmer, and Myanmar text - /// classification. These values correspond to the category codes used by - /// HarfBuzz in its Indic-style shaping engines, including the extended - /// categories required for Myanmar. + /// classification. /// /// The values serve as the input alphabet for the script syllable machines /// and determine script-specific parsing, reordering, and dotted circle insertion. /// - /// Categories are sourced from the OpenType Script Development Specifications - /// and HarfBuzz's generated Indic tables: - /// - /// Indic specification: - /// https://learn.microsoft.com/en-us/typography/script-development/devanagari - /// - /// General Indic shaper category model and data: - /// https://github.com/harfbuzz/harfbuzz/blob/main/src/hb-ot-shaper-indic.cc - /// https://github.com/harfbuzz/harfbuzz/blob/main/src/hb-ot-shaper-indic-table.hh - /// - /// Khmer specification: - /// https://learn.microsoft.com/en-us/typography/script-development/khmer - /// - /// Myanmar specification: - /// https://learn.microsoft.com/en-us/typography/script-development/myanmar - /// - /// Myanmar machine exports: - /// https://github.com/harfbuzz/harfbuzz/blob/main/src/hb-ot-shaper-myanmar-machine.rl - /// - /// Notes: - /// * X is the default category and always has value 0. - /// * Coeng intentionally shares the same category value as H, matching - /// HarfBuzz behavior for Khmer. - /// * Some values are shared across scripts (for example VAbv, VBlw, VPre, - /// VPst) because the OpenType model for dependent vowels is the same. - /// * Myanmar-specific medial and tone categories begin at 32 and above, - /// matching HarfBuzz's numeric category assignments. /// + /// + /// The values are transcribed from HarfBuzz 14.2.1, src/gen-indic-table.py, symbols category_map, category_overrides, and position_to_category. The shaping categories and overrides are not derivable from the Unicode Character Database. + /// public enum Categories : int { // Core Indic-style categories (shared across scripts where applicable) @@ -54,7 +28,6 @@ public enum Categories : int N = 3, // Nukta H = 4, // Halant (virama) - // Coeng = H, // Khmer Coeng, mapped to H in HarfBuzz ZWNJ = 5, // Zero width non-joiner ZWJ = 6, // Zero width joiner M = 7, // Generic matra / dependent vowel @@ -85,10 +58,9 @@ public enum Categories : int Robatic = 25, // Khmer Robatic sign Xgroup = 26, // Khmer X-group matra sequence Ygroup = 27, // Khmer Y-group matra sequence - Coeng = 28, // Remove once we no longer need it for Khmer // Myanmar-specific categories - // IV = V, // Independent vowel (shares code 2 with V in HarfBuzz) + // IV = V, // Independent vowel (shares code 2 with V) // DB = N, // Dot-below (shares code 3 with N) // GB = Placeholder, // Generic base / placeholder (shares code 10) As = 32, // Asat @@ -101,10 +73,36 @@ public enum Categories : int ML = 41 // Medial Mon La } + /// + /// The shaping categories consumed by the Khmer syllable machine. + /// + /// + /// The category set is transcribed from HarfBuzz 14.2.1, src/hb-ot-shaper-khmer-machine.rl, symbol khmer_syllable_machine. The values are not derivable from the Unicode Character Database. + /// + public enum KhmerCategories : int + { + X = Categories.X, + C = Categories.C, + V = Categories.V, + H = Categories.H, + ZWNJ = Categories.ZWNJ, + ZWJ = Categories.ZWJ, + Placeholder = Categories.Placeholder, + Dotted_Circle = Categories.Dotted_Circle, + Ra = Categories.Ra, + VAbv = Categories.VAbv, + VBlw = Categories.VBlw, + VPre = Categories.VPre, + VPst = Categories.VPst, + Robatic = Categories.Robatic, + Xgroup = Categories.Xgroup, + Ygroup = Categories.Ygroup, + } + // Categories used in the Myanmar shaping engine. // Note: // The OpenType Myanmar spec defines categories D, D0, and P. - // HarfBuzz collapses: + // The source table collapses: // D => GB // D0 => D => GB // P => GB @@ -151,26 +149,27 @@ public enum MyanmarSyllableType NonMyanmar_Cluster = 1 << 2 } - // Visual positions in a syllable from left to right. - [Flags] + // Visual positions in a syllable from left to right. Ordinal values whose order + // is the visual order; zero is reserved as the unassigned sentinel so a default + // syllable record compares unequal to every real position. public enum Positions { - Start = 1 << 0, - Ra_To_Become_Reph = 1 << 1, - Pre_M = 1 << 2, - Pre_C = 1 << 3, - Base_C = 1 << 4, - After_Main = 1 << 5, - Above_C = 1 << 6, - Before_Sub = 1 << 7, - Below_C = 1 << 8, - After_Sub = 1 << 9, - Before_Post = 1 << 10, - Post_C = 1 << 11, - After_Post = 1 << 12, - Final_C = 1 << 13, - SMVD = 1 << 14, - End = 1 << 15 + Start = 1, + Ra_To_Become_Reph = 2, + Pre_M = 3, + Pre_C = 4, + Base_C = 5, + After_Main = 6, + Above_C = 7, + Before_Sub = 8, + Below_C = 9, + After_Sub = 10, + Before_Post = 11, + Post_C = 12, + After_Post = 13, + Final_C = 14, + SMVD = 15, + End = 16 } public enum BasePosition @@ -340,16 +339,6 @@ public enum BlwfMode } }; - public static Dictionary Decompositions { get; } = new() - { - // Khmer - { 0x17BE, new int[] { 0x17C1, 0x17BE } }, - { 0x17BF, new int[] { 0x17C1, 0x17BF } }, - { 0x17C0, new int[] { 0x17C1, 0x17C0 } }, - { 0x17C4, new int[] { 0x17C1, 0x17C4 } }, - { 0x17C5, new int[] { 0x17C1, 0x17C5 } } - }; - public static uint ConsonantFlags { get; } = Flag(Categories.C) | Flag(Categories.Ra) | @@ -375,9 +364,10 @@ public enum BlwfMode Flag(Categories.ZWJ) | Flag(Categories.ZWNJ); - public static uint HalantOrCoengFlags { get; } = - Flag(Categories.H) | - Flag(Categories.Coeng); + /// + /// Gets the bit identifying a halant category. + /// + public static uint HalantFlags { get; } = Flag(Categories.H); /// /// Provides a flag value for the given category. Only valid for categories < 32. diff --git a/src/SixLabors.Fonts/Unicode/Resources/IndicShapingTrie.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/IndicShapingTrie.Generated.cs index 1e3f51ebc..b8a05265e 100644 --- a/src/SixLabors.Fonts/Unicode/Resources/IndicShapingTrie.Generated.cs +++ b/src/SixLabors.Fonts/Unicode/Resources/IndicShapingTrie.Generated.cs @@ -283,7 +283,7 @@ internal static class IndicShapingTrie 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 14, 8, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 15, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 15, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 11, 23, 0, 0, 11, 23, 0, 0, 6, 20, 0, 0, - 6, 20, 0, 0, 8, 21, 0, 0, 8, 21, 0, 0, 3, 22, 0, 0, 14, 9, 0, 0, 6, 20, 0, 0, 6, 20, 0, 0, 6, 20, 0, 0, 14, 9, 0, 0, 8, 3, 0, 0, 14, 8, 0, 0, 15, 28, 0, 0, 6, 32, 0, 0, 11, 38, 0, 0, 15, 36, 0, 0, 8, 37, 0, 0, 8, 35, 0, 0, 4, 1, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, + 6, 20, 0, 0, 8, 21, 0, 0, 8, 21, 0, 0, 3, 22, 0, 0, 14, 9, 0, 0, 6, 20, 0, 0, 6, 20, 0, 0, 6, 20, 0, 0, 14, 9, 0, 0, 8, 3, 0, 0, 14, 8, 0, 0, 15, 4, 0, 0, 6, 32, 0, 0, 11, 38, 0, 0, 15, 36, 0, 0, 8, 37, 0, 0, 8, 35, 0, 0, 4, 1, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 1, 0, 0, 15, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 11, 23, 0, 0, 11, 23, 0, 0, 8, 21, 0, 0, 8, 21, 0, 0, 4, 15, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 8, 38, 0, 0, 8, 38, 0, 0, 8, 41, 0, 0, 4, 1, 0, 0, 11, 23, 0, 0, 11, 39, 0, 0, 11, 39, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 11, 23, 0, 0, 11, 23, 0, 0, 11, 39, 0, 0, 11, 39, 0, 0, 11, 39, 0, 0, 11, 39, 0, 0, 11, 39, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 6, 20, 0, 0, 6, 20, 0, 0, 6, 20, 0, 0, 6, 20, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 8, 37, 0, 0, 11, 23, 0, 0, 3, 22, 0, 0, 6, 20, 0, 0, 6, 20, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 4, 1, 0, 0, 14, 8, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, @@ -303,8 +303,8 @@ internal static class IndicShapingTrie 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 15, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, - 4, 2, 0, 0, 4, 2, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 12, 7, 0, 0, 12, 7, 0, 0, 12, 7, 0, 0, 12, 7, 0, 0, 12, 7, 0, 0, 12, 7, 0, 0, 12, 7, 0, 0, 12, 7, 0, 0, 12, 7, 0, 0, 12, 7, 0, 0, 12, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 12, 7, 0, 0, 12, 7, 0, 0, 6, 3, 0, 0, 14, 8, 0, 0, 12, 7, 0, 0, 6, 12, 0, 0, 6, 12, 0, 0, - 14, 8, 0, 0, 4, 16, 0, 0, 12, 7, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 12, 7, 0, 0, 15, 28, 0, 0, 14, 8, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 10, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 14, 17, 0, 0, 14, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, + 4, 2, 0, 0, 4, 2, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 11, 23, 0, 0, 6, 20, 0, 0, 6, 20, 0, 0, 6, 20, 0, 0, 6, 20, 0, 0, 8, 21, 0, 0, 8, 21, 0, 0, 8, 21, 0, 0, 6, 20, 0, 0, 11, 23, 0, 0, 11, 23, 0, 0, 3, 22, 0, 0, 3, 22, 0, 0, 3, 22, 0, 0, 11, 23, 0, 0, 11, 23, 0, 0, 6, 26, 0, 0, 11, 27, 0, 0, 11, 27, 0, 0, 6, 25, 0, 0, 6, 25, 0, 0, + 6, 26, 0, 0, 6, 25, 0, 0, 6, 26, 0, 0, 6, 26, 0, 0, 6, 26, 0, 0, 6, 26, 0, 0, 6, 26, 0, 0, 15, 4, 0, 0, 6, 27, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 10, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 14, 17, 0, 0, 6, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -317,7 +317,7 @@ internal static class IndicShapingTrie 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 1, 0, 0, 4, 16, 0, 0, - 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 0, 0, 0, 0, 15, 28, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, + 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 0, 0, 0, 0, 15, 4, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 6, 3, 0, 0, 6, 3, 0, 0, 6, 3, 0, 0, 6, 3, 0, 0, 6, 3, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 8, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, @@ -327,13 +327,13 @@ internal static class IndicShapingTrie 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 6, 0, 0, 0, 8, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 14, 8, 0, 0, 4, 16, 0, 0, 14, 8, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, - 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 15, 28, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 14, 17, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 16, 0, 0, + 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 15, 4, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 14, 17, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 6, 3, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 11, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 8, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 15, 0, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 17, 0, 0, 14, 17, 0, 0, 14, 17, 0, 0, - 14, 17, 0, 0, 14, 9, 0, 0, 14, 17, 0, 0, 14, 17, 0, 0, 14, 17, 0, 0, 14, 17, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 14, 9, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, + 14, 17, 0, 0, 14, 9, 0, 0, 14, 17, 0, 0, 14, 17, 0, 0, 14, 17, 0, 0, 14, 17, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 14, 9, 0, 0, 15, 18, 0, 0, 15, 18, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 14, 8, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, @@ -390,7 +390,7 @@ internal static class IndicShapingTrie 4, 1, 0, 0, 11, 39, 0, 0, 6, 3, 0, 0, 11, 3, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 6, 3, 0, 0, 15, 0, 0, 0, 6, 3, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, - 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 14, 8, 0, 0, 15, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 14, 8, 0, 0, 15, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, @@ -434,7 +434,7 @@ internal static class IndicShapingTrie 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 1, 0, 0, 5, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, - 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 0, 0, 8, 3, 0, 0, 8, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 28, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, + 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 0, 0, 8, 3, 0, 0, 8, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 4, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, @@ -461,7 +461,7 @@ internal static class IndicShapingTrie 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, - 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 15, 28, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, + 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 15, 4, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 8, 3, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, @@ -484,7 +484,7 @@ internal static class IndicShapingTrie 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 14, 17, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, - 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 0, 0, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 15, 28, 0, 0, 6, 14, 0, 0, 14, 8, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 0, 0, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 15, 4, 0, 0, 6, 14, 0, 0, 14, 8, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 9, 0, 0, 14, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 8, 4, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 8, 3, 0, 0, 14, 17, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, @@ -509,16 +509,16 @@ internal static class IndicShapingTrie 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 8, 4, 0, 0, 8, 3, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, - 9, 7, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 9, 7, 0, 0, 15, 28, 0, 0, 6, 0, 0, 0, 4, 16, 0, 0, 6, 14, 0, 0, 4, 16, 0, 0, 8, 3, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 7, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 9, 7, 0, 0, 15, 4, 0, 0, 6, 0, 0, 0, 4, 16, 0, 0, 6, 14, 0, 0, 4, 16, 0, 0, 8, 3, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 8, 4, 0, 0, 14, 17, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 14, 8, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 15, 18, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 10, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, - 15, 0, 0, 0, 15, 0, 0, 0, 4, 10, 0, 0, 15, 0, 0, 0, 15, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, + 15, 0, 0, 0, 15, 0, 0, 0, 4, 10, 0, 0, 15, 0, 0, 0, 15, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 6, 14, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, - 15, 28, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 14, 17, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 15, 4, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 14, 17, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, @@ -528,15 +528,15 @@ internal static class IndicShapingTrie 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 0, 0, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 4, 16, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, - 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 8, 3, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 15, 28, 0, 0, 11, 14, 0, 0, 4, 16, 0, 0, + 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 8, 3, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 15, 4, 0, 0, 11, 14, 0, 0, 4, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, - 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 15, 28, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 15, 4, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 10, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 8, 0, 0, 14, 8, 0, 0, 6, 14, 0, 0, 14, 8, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, - 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 15, 28, 0, 0, 15, 0, 0, 0, + 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 4, 1, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 7, 0, 0, 2, 7, 0, 0, 9, 7, 0, 0, 9, 7, 0, 0, 15, 4, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 4, 10, 0, 0, 6, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, diff --git a/src/SixLabors.Fonts/Unicode/Resources/KhmerShapingCategoryMap.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/KhmerShapingCategoryMap.Generated.cs new file mode 100644 index 000000000..bfb70f660 --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/Resources/KhmerShapingCategoryMap.Generated.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System; + +namespace SixLabors.Fonts.Unicode.Resources +{ + internal static partial class KhmerShapingData + { + /// + /// Maps sparse Khmer shaping categories to the state machine alphabet. + /// + /// + /// The symbol order follows tests/harfbuzz/src/hb-ot-shaper-khmer-machine.rl, + /// symbol khmer_syllable_machine. It is not derivable from the Unicode Character Database. + /// + public static ReadOnlySpan CategoryToSymbolIds => + [ + 0, // X + 1, // C + 2, // V + 0, // N + 3, // H + 4, // ZWNJ + 5, // ZWJ + 0, // M + 0, // SM + 0, // A + 6, // Placeholder + 7, // Dotted_Circle + 0, // RS + 0, // MPst + 0, // Repha + 8, // Ra + 0, // CM + 0, // Symbol + 0, // CS + 0, // Unassigned + 9, // VAbv + 10, // VBlw + 11, // VPre + 12, // VPst + 0, // Unassigned + 13, // Robatic + 14, // Xgroup + 15, // Ygroup + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // As + 0, // Unassigned + 0, // Unassigned + 0, // MH + 0, // MR + 0, // MW + 0, // MY + 0, // PT + 0, // VS + 0, // ML + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // Unassigned + 0, // SMPst + ]; + } +} diff --git a/src/SixLabors.Fonts/Unicode/Resources/KhmerShapingData.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/KhmerShapingData.Generated.cs new file mode 100644 index 000000000..0dc8caec4 --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/Resources/KhmerShapingData.Generated.cs @@ -0,0 +1,156 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System; +using System.Collections.Generic; + +namespace SixLabors.Fonts.Unicode.Resources +{ + internal static partial class KhmerShapingData + { + public static int[][] StateTable => new int[44][] + { + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 2,3,3,4,5,5,6,6,3,7,8,9,10,11,12,13 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,14,15,15,0,0,0,16,17,18,19,6,20,21 }, + new int[] { 0,22,22,0,0,0,0,0,22,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,23,23,0,0,0,7,0,0,0,0,12,0 }, + new int[] { 0,0,0,14,24,24,0,0,0,16,17,18,19,25,20,21 }, + new int[] { 0,0,0,26,27,27,0,0,0,0,0,0,10,0,7,13 }, + new int[] { 0,0,0,26,28,28,0,0,0,7,0,0,10,0,8,13 }, + new int[] { 0,0,0,26,29,29,0,0,0,7,8,0,10,0,9,13 }, + new int[] { 0,0,0,26,30,30,0,0,0,0,0,0,0,0,10,13 }, + new int[] { 0,0,0,4,5,5,0,0,0,7,8,9,10,0,12,13 }, + new int[] { 0,0,0,26,5,5,0,0,0,7,8,9,10,0,12,13 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13 }, + new int[] { 0,31,31,0,0,0,0,0,31,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,32,32,0,0,0,16,0,0,0,6,20,0 }, + new int[] { 0,0,0,33,34,34,0,0,0,0,0,0,19,0,16,21 }, + new int[] { 0,0,0,33,35,35,0,0,0,16,0,0,19,0,17,21 }, + new int[] { 0,0,0,33,36,36,0,0,0,16,17,0,19,0,18,21 }, + new int[] { 0,0,0,33,37,37,0,0,0,0,0,0,0,0,19,21 }, + new int[] { 0,0,0,33,24,24,0,0,0,16,17,18,19,0,20,21 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21 }, + new int[] { 0,0,0,4,38,38,0,0,0,7,8,9,10,11,12,13 }, + new int[] { 0,0,0,0,23,23,0,0,0,0,0,0,0,0,12,0 }, + new int[] { 0,0,0,0,32,32,0,0,0,16,0,0,0,0,20,0 }, + new int[] { 0,0,0,14,24,24,0,0,0,16,17,18,19,0,20,21 }, + new int[] { 0,13,13,0,0,0,0,0,13,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,27,27,0,0,0,0,0,0,0,0,7,0 }, + new int[] { 0,0,0,0,39,39,0,0,0,7,0,0,0,0,8,0 }, + new int[] { 0,0,0,0,40,40,0,0,0,7,0,0,0,0,9,0 }, + new int[] { 0,0,0,0,30,30,0,0,0,0,0,0,0,0,10,0 }, + new int[] { 0,0,0,14,41,41,0,0,0,16,17,18,19,25,20,21 }, + new int[] { 0,0,0,0,32,32,0,0,0,0,0,0,0,0,20,0 }, + new int[] { 0,21,21,0,0,0,0,0,21,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,34,34,0,0,0,0,0,0,0,0,16,0 }, + new int[] { 0,0,0,0,42,42,0,0,0,16,0,0,0,0,17,0 }, + new int[] { 0,0,0,0,43,43,0,0,0,16,0,0,0,0,18,0 }, + new int[] { 0,0,0,0,37,37,0,0,0,0,0,0,0,0,19,0 }, + new int[] { 0,0,0,0,23,23,0,0,0,7,0,0,0,11,12,0 }, + new int[] { 0,0,0,0,39,39,0,0,0,0,0,0,0,0,8,0 }, + new int[] { 0,0,0,0,40,40,0,0,0,0,0,0,0,0,9,0 }, + new int[] { 0,0,0,0,32,32,0,0,0,16,0,0,0,25,20,0 }, + new int[] { 0,0,0,0,42,42,0,0,0,0,0,0,0,0,17,0 }, + new int[] { 0,0,0,0,43,43,0,0,0,0,0,0,0,0,18,0 } + }; + + public static bool[] AcceptingStates => new bool[] + { + false, + true, + true, + true, + true, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + true, + true, + true, + true, + true, + true, + false, + false, + true, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + }; + + public static string[][] Tags => new string[44][] + { + Array.Empty(), + new string[] { "broken_cluster" }, + new string[] { "non_khmer_cluster" }, + new string[] { "consonant_syllable" }, + new string[] { "broken_cluster" }, + Array.Empty(), + new string[] { "consonant_syllable" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "consonant_syllable" }, + Array.Empty(), + new string[] { "consonant_syllable" }, + new string[] { "consonant_syllable" }, + new string[] { "consonant_syllable" }, + new string[] { "consonant_syllable" }, + new string[] { "consonant_syllable" }, + new string[] { "consonant_syllable" }, + new string[] { "broken_cluster" }, + Array.Empty(), + Array.Empty(), + new string[] { "consonant_syllable" }, + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + new string[] { "consonant_syllable" }, + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty(), + Array.Empty() + }; + } +} diff --git a/src/SixLabors.Fonts/Unicode/Resources/MarkOrderingData.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/MarkOrderingData.Generated.cs new file mode 100644 index 000000000..eadff344a --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/Resources/MarkOrderingData.Generated.cs @@ -0,0 +1,187 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System; + +namespace SixLabors.Fonts.Unicode.Resources +{ + /// + /// The classes that order a mark against the marks around it. + /// + internal static class MarkOrderingData + { + /// + /// Gets the order given to the canonical combining class at each index. Every + /// class orders as itself except where a script draws its marks in an order its + /// assigned classes do not give, and those are renumbered so that one sort + /// leaves the marks of every script in the order they are drawn. + /// + public static ReadOnlySpan Classes => new byte[] + { + 0, // ccc 0, Not_Reordered + 1, // ccc 1, Overlay + 2, 3, 4, 5, 6, // ccc 2..6 + 7, // ccc 7, Nukta + 8, // ccc 8, Kana_Voicing + 9, // ccc 9, Virama + + // Hebrew. + 22, // ccc 10, sheva + 15, // ccc 11, hataf segol + 16, // ccc 12, hataf patah + 17, // ccc 13, hataf qamats + 23, // ccc 14, hiriq + 18, // ccc 15, tsere + 19, // ccc 16, segol + 20, // ccc 17, patah + 21, // ccc 18, qamats & qamats qatan + 14, // ccc 19, holam & holam haser for vav + 24, // ccc 20, qubuts + 12, // ccc 21, dagesh + 25, // ccc 22, meteg + 13, // ccc 23, rafe + 10, // ccc 24, shin dot + 11, // ccc 25, sin dot + 26, // ccc 26, point varika + + // Arabic. + 28, // ccc 27, fathatan + 29, // ccc 28, dammatan + 30, // ccc 29, kasratan + 31, // ccc 30, fatha + 32, // ccc 31, damma + 33, // ccc 32, kasra + 27, // ccc 33, shadda + 34, // ccc 34, sukun + 35, // ccc 35, superscript alef + + // Syriac. + 36, // ccc 36, superscript alaph + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, // ccc 37..46 + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, // ccc 47..56 + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, // ccc 57..66 + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, // ccc 67..76 + 77, 78, 79, 80, 81, 82, 83, // ccc 77..83 + + // Telugu. + 4, // ccc 84, length mark + 85, 86, 87, 88, 89, 90, // ccc 85..90 + 5, // ccc 91, ai length mark + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, // ccc 92..101 + 102, // ccc 102..102 + + // Thai. + 3, // ccc 103, sara u / sara uu + 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, // ccc 104..113 + 114, 115, 116, 117, // ccc 114..117 + + // Lao. + 118, // ccc 118, sign u / sign uu + 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, // ccc 119..128 + + // Tibetan. + 129, // ccc 129, sign aa + 132, // ccc 130, sign i + 131, // ccc 131..131 + 131, // ccc 132, sign u + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, // ccc 133..142 + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, // ccc 143..152 + 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, // ccc 153..162 + 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, // ccc 163..172 + 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, // ccc 173..182 + 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, // ccc 183..192 + 193, 194, 195, 196, 197, 198, 199, // ccc 193..199 + 200, // ccc 200, Attached_Below_Left + 201, // ccc 201..201 + 202, // ccc 202, Attached_Below + 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, // ccc 203..212 + 213, // ccc 213..213 + 214, // ccc 214, Attached_Above + 215, // ccc 215..215 + 216, // ccc 216, Attached_Above_Right + 217, // ccc 217..217 + 218, // ccc 218, Below_Left + 219, // ccc 219..219 + 220, // ccc 220, Below + 221, // ccc 221..221 + 222, // ccc 222, Below_Right + 223, // ccc 223..223 + 224, // ccc 224, Left + 225, // ccc 225..225 + 226, // ccc 226, Right + 227, // ccc 227..227 + 228, // ccc 228, Above_Left + 229, // ccc 229..229 + 230, // ccc 230, Above + 231, // ccc 231..231 + 232, // ccc 232, Above_Right + 233, // ccc 233, Double_Below + 234, // ccc 234, Double_Above + 235, 236, 237, 238, 239, // ccc 235..239 + 240, // ccc 240, Iota_Subscript + 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, // ccc 241..250 + 251, 252, 253, 254, // ccc 251..254 + 255, // ccc 255, Invalid + }; + + /// + /// Tries to get the order of a character that orders by where it is drawn rather + /// than by its class. + /// + /// + /// Written as a switch rather than a table because there are only a handful, and + /// because a span of anything wider than a byte is a fresh array on every access: + /// a table here would allocate once per character shaped. + /// + /// The code point to look up. + /// When this method returns, contains the order. + /// when the character orders by where it is drawn. + public static bool TryGetOverride(uint codePoint, out byte order) + { + switch (codePoint) + { + // TIBETAN MARK TSA -PHRU + case 0x0F39: + order = 127; + return true; + + // TIBETAN SYMBOL PADMA GDAN + case 0x0FC6: + order = 254; + return true; + + // TAI THAM SIGN SAKOT + case 0x1A60: + order = 254; + return true; + + default: + order = 0; + return false; + } + } + + /// + /// Determines whether an Arabic mark modifies the combining mark that follows it. + /// + /// The code point to test. + /// when the code point is a modifier combining mark; otherwise, . + public static bool IsArabicModifierCombiningMark(uint codePoint) + => codePoint is + 0x0654 or // ARABIC HAMZA ABOVE + 0x0655 or // ARABIC HAMZA BELOW + 0x0658 or // ARABIC MARK NOON GHUNNA + 0x06DC or // ARABIC SMALL HIGH SEEN + 0x06E3 or // ARABIC SMALL LOW SEEN + 0x06E7 or // ARABIC SMALL HIGH YEH + 0x06E8 or // ARABIC SMALL HIGH NOON + 0x08CA or // ARABIC SMALL HIGH FARSI YEH + 0x08CB or // ARABIC SMALL HIGH YEH BARREE WITH TWO DOTS BELOW + 0x08CD or // ARABIC SMALL HIGH ZAH + 0x08CE or // ARABIC LARGE ROUND DOT ABOVE + 0x08CF or // ARABIC LARGE ROUND DOT BELOW + 0x08D3 or // ARABIC SMALL LOW WAW + 0x08F3; // ARABIC SMALL HIGH WAW + } +} diff --git a/src/SixLabors.Fonts/Unicode/Resources/NormalizationData.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/NormalizationData.Generated.cs new file mode 100644 index 000000000..bf532ed5f --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/Resources/NormalizationData.Generated.cs @@ -0,0 +1,3635 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System; + +namespace SixLabors.Fonts.Unicode.Resources +{ + /// + /// The canonical decomposition and composition tables used when normalizing + /// text before it is shaped. + /// + internal static class NormalizationData + { + /// + /// The first code point represented by the canonical decomposition table. + /// + public const uint FirstDecompositionCodePoint = 0xC0; + + /// + /// The last code point represented by the canonical decomposition table. + /// + public const uint LastDecompositionCodePoint = 0x2FA1D; + + /// + /// Gets the entry boundaries that divide the canonical decompositions by Basic Multilingual Plane page. + /// + /// The value at each page is the first decomposition entry in that page, and the following value is its + /// exclusive end. Equal values identify a page containing no decompositions. + /// + /// + /// Each value occupies two bytes, least significant first, and is read with + /// . + /// + /// + public static ReadOnlySpan DecompositionPageStarts => new byte[] + { + 0x00, 0x00, // U+0000 + 0x00, 0x00, // U+0080 + 0x35, 0x00, // U+0100 + 0xA1, 0x00, // U+0180 + 0xD0, 0x00, // U+0200 + 0xFC, 0x00, // U+0280 + 0xFC, 0x00, // U+0300 + 0x02, 0x01, // U+0380 + 0x1A, 0x01, // U+0400 + 0x2C, 0x01, // U+0480 + 0x4E, 0x01, // U+0500 + 0x4E, 0x01, // U+0580 + 0x4E, 0x01, // U+0600 + 0x53, 0x01, // U+0680 + 0x56, 0x01, // U+0700 + 0x56, 0x01, // U+0780 + 0x56, 0x01, // U+0800 + 0x56, 0x01, // U+0880 + 0x56, 0x01, // U+0900 + 0x61, 0x01, // U+0980 + 0x66, 0x01, // U+0A00 + 0x6C, 0x01, // U+0A80 + 0x6C, 0x01, // U+0B00 + 0x71, 0x01, // U+0B80 + 0x75, 0x01, // U+0C00 + 0x76, 0x01, // U+0C80 + 0x7B, 0x01, // U+0D00 + 0x7E, 0x01, // U+0D80 + 0x82, 0x01, // U+0E00 + 0x82, 0x01, // U+0E80 + 0x82, 0x01, // U+0F00 + 0x8C, 0x01, // U+0F80 + 0x93, 0x01, // U+1000 + 0x94, 0x01, // U+1080 + 0x94, 0x01, // U+1100 + 0x94, 0x01, // U+1180 + 0x94, 0x01, // U+1200 + 0x94, 0x01, // U+1280 + 0x94, 0x01, // U+1300 + 0x94, 0x01, // U+1380 + 0x94, 0x01, // U+1400 + 0x94, 0x01, // U+1480 + 0x94, 0x01, // U+1500 + 0x94, 0x01, // U+1580 + 0x94, 0x01, // U+1600 + 0x94, 0x01, // U+1680 + 0x94, 0x01, // U+1700 + 0x94, 0x01, // U+1780 + 0x94, 0x01, // U+1800 + 0x94, 0x01, // U+1880 + 0x94, 0x01, // U+1900 + 0x94, 0x01, // U+1980 + 0x94, 0x01, // U+1A00 + 0x94, 0x01, // U+1A80 + 0x94, 0x01, // U+1B00 + 0x9F, 0x01, // U+1B80 + 0x9F, 0x01, // U+1C00 + 0x9F, 0x01, // U+1C80 + 0x9F, 0x01, // U+1D00 + 0x9F, 0x01, // U+1D80 + 0x9F, 0x01, // U+1E00 + 0x1F, 0x02, // U+1E80 + 0x94, 0x02, // U+1F00 + 0x06, 0x03, // U+1F80 + 0x79, 0x03, // U+2000 + 0x7B, 0x03, // U+2080 + 0x7B, 0x03, // U+2100 + 0x7E, 0x03, // U+2180 + 0x84, 0x03, // U+2200 + 0x98, 0x03, // U+2280 + 0xAA, 0x03, // U+2300 + 0xAC, 0x03, // U+2380 + 0xAC, 0x03, // U+2400 + 0xAC, 0x03, // U+2480 + 0xAC, 0x03, // U+2500 + 0xAC, 0x03, // U+2580 + 0xAC, 0x03, // U+2600 + 0xAC, 0x03, // U+2680 + 0xAC, 0x03, // U+2700 + 0xAC, 0x03, // U+2780 + 0xAC, 0x03, // U+2800 + 0xAC, 0x03, // U+2880 + 0xAC, 0x03, // U+2900 + 0xAC, 0x03, // U+2980 + 0xAC, 0x03, // U+2A00 + 0xAC, 0x03, // U+2A80 + 0xAD, 0x03, // U+2B00 + 0xAD, 0x03, // U+2B80 + 0xAD, 0x03, // U+2C00 + 0xAD, 0x03, // U+2C80 + 0xAD, 0x03, // U+2D00 + 0xAD, 0x03, // U+2D80 + 0xAD, 0x03, // U+2E00 + 0xAD, 0x03, // U+2E80 + 0xAD, 0x03, // U+2F00 + 0xAD, 0x03, // U+2F80 + 0xAD, 0x03, // U+3000 + 0xC6, 0x03, // U+3080 + 0xE7, 0x03, // U+3100 + 0xE7, 0x03, // U+3180 + 0xE7, 0x03, // U+3200 + 0xE7, 0x03, // U+3280 + 0xE7, 0x03, // U+3300 + 0xE7, 0x03, // U+3380 + 0xE7, 0x03, // U+3400 + 0xE7, 0x03, // U+3480 + 0xE7, 0x03, // U+3500 + 0xE7, 0x03, // U+3580 + 0xE7, 0x03, // U+3600 + 0xE7, 0x03, // U+3680 + 0xE7, 0x03, // U+3700 + 0xE7, 0x03, // U+3780 + 0xE7, 0x03, // U+3800 + 0xE7, 0x03, // U+3880 + 0xE7, 0x03, // U+3900 + 0xE7, 0x03, // U+3980 + 0xE7, 0x03, // U+3A00 + 0xE7, 0x03, // U+3A80 + 0xE7, 0x03, // U+3B00 + 0xE7, 0x03, // U+3B80 + 0xE7, 0x03, // U+3C00 + 0xE7, 0x03, // U+3C80 + 0xE7, 0x03, // U+3D00 + 0xE7, 0x03, // U+3D80 + 0xE7, 0x03, // U+3E00 + 0xE7, 0x03, // U+3E80 + 0xE7, 0x03, // U+3F00 + 0xE7, 0x03, // U+3F80 + 0xE7, 0x03, // U+4000 + 0xE7, 0x03, // U+4080 + 0xE7, 0x03, // U+4100 + 0xE7, 0x03, // U+4180 + 0xE7, 0x03, // U+4200 + 0xE7, 0x03, // U+4280 + 0xE7, 0x03, // U+4300 + 0xE7, 0x03, // U+4380 + 0xE7, 0x03, // U+4400 + 0xE7, 0x03, // U+4480 + 0xE7, 0x03, // U+4500 + 0xE7, 0x03, // U+4580 + 0xE7, 0x03, // U+4600 + 0xE7, 0x03, // U+4680 + 0xE7, 0x03, // U+4700 + 0xE7, 0x03, // U+4780 + 0xE7, 0x03, // U+4800 + 0xE7, 0x03, // U+4880 + 0xE7, 0x03, // U+4900 + 0xE7, 0x03, // U+4980 + 0xE7, 0x03, // U+4A00 + 0xE7, 0x03, // U+4A80 + 0xE7, 0x03, // U+4B00 + 0xE7, 0x03, // U+4B80 + 0xE7, 0x03, // U+4C00 + 0xE7, 0x03, // U+4C80 + 0xE7, 0x03, // U+4D00 + 0xE7, 0x03, // U+4D80 + 0xE7, 0x03, // U+4E00 + 0xE7, 0x03, // U+4E80 + 0xE7, 0x03, // U+4F00 + 0xE7, 0x03, // U+4F80 + 0xE7, 0x03, // U+5000 + 0xE7, 0x03, // U+5080 + 0xE7, 0x03, // U+5100 + 0xE7, 0x03, // U+5180 + 0xE7, 0x03, // U+5200 + 0xE7, 0x03, // U+5280 + 0xE7, 0x03, // U+5300 + 0xE7, 0x03, // U+5380 + 0xE7, 0x03, // U+5400 + 0xE7, 0x03, // U+5480 + 0xE7, 0x03, // U+5500 + 0xE7, 0x03, // U+5580 + 0xE7, 0x03, // U+5600 + 0xE7, 0x03, // U+5680 + 0xE7, 0x03, // U+5700 + 0xE7, 0x03, // U+5780 + 0xE7, 0x03, // U+5800 + 0xE7, 0x03, // U+5880 + 0xE7, 0x03, // U+5900 + 0xE7, 0x03, // U+5980 + 0xE7, 0x03, // U+5A00 + 0xE7, 0x03, // U+5A80 + 0xE7, 0x03, // U+5B00 + 0xE7, 0x03, // U+5B80 + 0xE7, 0x03, // U+5C00 + 0xE7, 0x03, // U+5C80 + 0xE7, 0x03, // U+5D00 + 0xE7, 0x03, // U+5D80 + 0xE7, 0x03, // U+5E00 + 0xE7, 0x03, // U+5E80 + 0xE7, 0x03, // U+5F00 + 0xE7, 0x03, // U+5F80 + 0xE7, 0x03, // U+6000 + 0xE7, 0x03, // U+6080 + 0xE7, 0x03, // U+6100 + 0xE7, 0x03, // U+6180 + 0xE7, 0x03, // U+6200 + 0xE7, 0x03, // U+6280 + 0xE7, 0x03, // U+6300 + 0xE7, 0x03, // U+6380 + 0xE7, 0x03, // U+6400 + 0xE7, 0x03, // U+6480 + 0xE7, 0x03, // U+6500 + 0xE7, 0x03, // U+6580 + 0xE7, 0x03, // U+6600 + 0xE7, 0x03, // U+6680 + 0xE7, 0x03, // U+6700 + 0xE7, 0x03, // U+6780 + 0xE7, 0x03, // U+6800 + 0xE7, 0x03, // U+6880 + 0xE7, 0x03, // U+6900 + 0xE7, 0x03, // U+6980 + 0xE7, 0x03, // U+6A00 + 0xE7, 0x03, // U+6A80 + 0xE7, 0x03, // U+6B00 + 0xE7, 0x03, // U+6B80 + 0xE7, 0x03, // U+6C00 + 0xE7, 0x03, // U+6C80 + 0xE7, 0x03, // U+6D00 + 0xE7, 0x03, // U+6D80 + 0xE7, 0x03, // U+6E00 + 0xE7, 0x03, // U+6E80 + 0xE7, 0x03, // U+6F00 + 0xE7, 0x03, // U+6F80 + 0xE7, 0x03, // U+7000 + 0xE7, 0x03, // U+7080 + 0xE7, 0x03, // U+7100 + 0xE7, 0x03, // U+7180 + 0xE7, 0x03, // U+7200 + 0xE7, 0x03, // U+7280 + 0xE7, 0x03, // U+7300 + 0xE7, 0x03, // U+7380 + 0xE7, 0x03, // U+7400 + 0xE7, 0x03, // U+7480 + 0xE7, 0x03, // U+7500 + 0xE7, 0x03, // U+7580 + 0xE7, 0x03, // U+7600 + 0xE7, 0x03, // U+7680 + 0xE7, 0x03, // U+7700 + 0xE7, 0x03, // U+7780 + 0xE7, 0x03, // U+7800 + 0xE7, 0x03, // U+7880 + 0xE7, 0x03, // U+7900 + 0xE7, 0x03, // U+7980 + 0xE7, 0x03, // U+7A00 + 0xE7, 0x03, // U+7A80 + 0xE7, 0x03, // U+7B00 + 0xE7, 0x03, // U+7B80 + 0xE7, 0x03, // U+7C00 + 0xE7, 0x03, // U+7C80 + 0xE7, 0x03, // U+7D00 + 0xE7, 0x03, // U+7D80 + 0xE7, 0x03, // U+7E00 + 0xE7, 0x03, // U+7E80 + 0xE7, 0x03, // U+7F00 + 0xE7, 0x03, // U+7F80 + 0xE7, 0x03, // U+8000 + 0xE7, 0x03, // U+8080 + 0xE7, 0x03, // U+8100 + 0xE7, 0x03, // U+8180 + 0xE7, 0x03, // U+8200 + 0xE7, 0x03, // U+8280 + 0xE7, 0x03, // U+8300 + 0xE7, 0x03, // U+8380 + 0xE7, 0x03, // U+8400 + 0xE7, 0x03, // U+8480 + 0xE7, 0x03, // U+8500 + 0xE7, 0x03, // U+8580 + 0xE7, 0x03, // U+8600 + 0xE7, 0x03, // U+8680 + 0xE7, 0x03, // U+8700 + 0xE7, 0x03, // U+8780 + 0xE7, 0x03, // U+8800 + 0xE7, 0x03, // U+8880 + 0xE7, 0x03, // U+8900 + 0xE7, 0x03, // U+8980 + 0xE7, 0x03, // U+8A00 + 0xE7, 0x03, // U+8A80 + 0xE7, 0x03, // U+8B00 + 0xE7, 0x03, // U+8B80 + 0xE7, 0x03, // U+8C00 + 0xE7, 0x03, // U+8C80 + 0xE7, 0x03, // U+8D00 + 0xE7, 0x03, // U+8D80 + 0xE7, 0x03, // U+8E00 + 0xE7, 0x03, // U+8E80 + 0xE7, 0x03, // U+8F00 + 0xE7, 0x03, // U+8F80 + 0xE7, 0x03, // U+9000 + 0xE7, 0x03, // U+9080 + 0xE7, 0x03, // U+9100 + 0xE7, 0x03, // U+9180 + 0xE7, 0x03, // U+9200 + 0xE7, 0x03, // U+9280 + 0xE7, 0x03, // U+9300 + 0xE7, 0x03, // U+9380 + 0xE7, 0x03, // U+9400 + 0xE7, 0x03, // U+9480 + 0xE7, 0x03, // U+9500 + 0xE7, 0x03, // U+9580 + 0xE7, 0x03, // U+9600 + 0xE7, 0x03, // U+9680 + 0xE7, 0x03, // U+9700 + 0xE7, 0x03, // U+9780 + 0xE7, 0x03, // U+9800 + 0xE7, 0x03, // U+9880 + 0xE7, 0x03, // U+9900 + 0xE7, 0x03, // U+9980 + 0xE7, 0x03, // U+9A00 + 0xE7, 0x03, // U+9A80 + 0xE7, 0x03, // U+9B00 + 0xE7, 0x03, // U+9B80 + 0xE7, 0x03, // U+9C00 + 0xE7, 0x03, // U+9C80 + 0xE7, 0x03, // U+9D00 + 0xE7, 0x03, // U+9D80 + 0xE7, 0x03, // U+9E00 + 0xE7, 0x03, // U+9E80 + 0xE7, 0x03, // U+9F00 + 0xE7, 0x03, // U+9F80 + 0xE7, 0x03, // U+A000 + 0xE7, 0x03, // U+A080 + 0xE7, 0x03, // U+A100 + 0xE7, 0x03, // U+A180 + 0xE7, 0x03, // U+A200 + 0xE7, 0x03, // U+A280 + 0xE7, 0x03, // U+A300 + 0xE7, 0x03, // U+A380 + 0xE7, 0x03, // U+A400 + 0xE7, 0x03, // U+A480 + 0xE7, 0x03, // U+A500 + 0xE7, 0x03, // U+A580 + 0xE7, 0x03, // U+A600 + 0xE7, 0x03, // U+A680 + 0xE7, 0x03, // U+A700 + 0xE7, 0x03, // U+A780 + 0xE7, 0x03, // U+A800 + 0xE7, 0x03, // U+A880 + 0xE7, 0x03, // U+A900 + 0xE7, 0x03, // U+A980 + 0xE7, 0x03, // U+AA00 + 0xE7, 0x03, // U+AA80 + 0xE7, 0x03, // U+AB00 + 0xE7, 0x03, // U+AB80 + 0xE7, 0x03, // U+AC00 + 0xE7, 0x03, // U+AC80 + 0xE7, 0x03, // U+AD00 + 0xE7, 0x03, // U+AD80 + 0xE7, 0x03, // U+AE00 + 0xE7, 0x03, // U+AE80 + 0xE7, 0x03, // U+AF00 + 0xE7, 0x03, // U+AF80 + 0xE7, 0x03, // U+B000 + 0xE7, 0x03, // U+B080 + 0xE7, 0x03, // U+B100 + 0xE7, 0x03, // U+B180 + 0xE7, 0x03, // U+B200 + 0xE7, 0x03, // U+B280 + 0xE7, 0x03, // U+B300 + 0xE7, 0x03, // U+B380 + 0xE7, 0x03, // U+B400 + 0xE7, 0x03, // U+B480 + 0xE7, 0x03, // U+B500 + 0xE7, 0x03, // U+B580 + 0xE7, 0x03, // U+B600 + 0xE7, 0x03, // U+B680 + 0xE7, 0x03, // U+B700 + 0xE7, 0x03, // U+B780 + 0xE7, 0x03, // U+B800 + 0xE7, 0x03, // U+B880 + 0xE7, 0x03, // U+B900 + 0xE7, 0x03, // U+B980 + 0xE7, 0x03, // U+BA00 + 0xE7, 0x03, // U+BA80 + 0xE7, 0x03, // U+BB00 + 0xE7, 0x03, // U+BB80 + 0xE7, 0x03, // U+BC00 + 0xE7, 0x03, // U+BC80 + 0xE7, 0x03, // U+BD00 + 0xE7, 0x03, // U+BD80 + 0xE7, 0x03, // U+BE00 + 0xE7, 0x03, // U+BE80 + 0xE7, 0x03, // U+BF00 + 0xE7, 0x03, // U+BF80 + 0xE7, 0x03, // U+C000 + 0xE7, 0x03, // U+C080 + 0xE7, 0x03, // U+C100 + 0xE7, 0x03, // U+C180 + 0xE7, 0x03, // U+C200 + 0xE7, 0x03, // U+C280 + 0xE7, 0x03, // U+C300 + 0xE7, 0x03, // U+C380 + 0xE7, 0x03, // U+C400 + 0xE7, 0x03, // U+C480 + 0xE7, 0x03, // U+C500 + 0xE7, 0x03, // U+C580 + 0xE7, 0x03, // U+C600 + 0xE7, 0x03, // U+C680 + 0xE7, 0x03, // U+C700 + 0xE7, 0x03, // U+C780 + 0xE7, 0x03, // U+C800 + 0xE7, 0x03, // U+C880 + 0xE7, 0x03, // U+C900 + 0xE7, 0x03, // U+C980 + 0xE7, 0x03, // U+CA00 + 0xE7, 0x03, // U+CA80 + 0xE7, 0x03, // U+CB00 + 0xE7, 0x03, // U+CB80 + 0xE7, 0x03, // U+CC00 + 0xE7, 0x03, // U+CC80 + 0xE7, 0x03, // U+CD00 + 0xE7, 0x03, // U+CD80 + 0xE7, 0x03, // U+CE00 + 0xE7, 0x03, // U+CE80 + 0xE7, 0x03, // U+CF00 + 0xE7, 0x03, // U+CF80 + 0xE7, 0x03, // U+D000 + 0xE7, 0x03, // U+D080 + 0xE7, 0x03, // U+D100 + 0xE7, 0x03, // U+D180 + 0xE7, 0x03, // U+D200 + 0xE7, 0x03, // U+D280 + 0xE7, 0x03, // U+D300 + 0xE7, 0x03, // U+D380 + 0xE7, 0x03, // U+D400 + 0xE7, 0x03, // U+D480 + 0xE7, 0x03, // U+D500 + 0xE7, 0x03, // U+D580 + 0xE7, 0x03, // U+D600 + 0xE7, 0x03, // U+D680 + 0xE7, 0x03, // U+D700 + 0xE7, 0x03, // U+D780 + 0xE7, 0x03, // U+D800 + 0xE7, 0x03, // U+D880 + 0xE7, 0x03, // U+D900 + 0xE7, 0x03, // U+D980 + 0xE7, 0x03, // U+DA00 + 0xE7, 0x03, // U+DA80 + 0xE7, 0x03, // U+DB00 + 0xE7, 0x03, // U+DB80 + 0xE7, 0x03, // U+DC00 + 0xE7, 0x03, // U+DC80 + 0xE7, 0x03, // U+DD00 + 0xE7, 0x03, // U+DD80 + 0xE7, 0x03, // U+DE00 + 0xE7, 0x03, // U+DE80 + 0xE7, 0x03, // U+DF00 + 0xE7, 0x03, // U+DF80 + 0xE7, 0x03, // U+E000 + 0xE7, 0x03, // U+E080 + 0xE7, 0x03, // U+E100 + 0xE7, 0x03, // U+E180 + 0xE7, 0x03, // U+E200 + 0xE7, 0x03, // U+E280 + 0xE7, 0x03, // U+E300 + 0xE7, 0x03, // U+E380 + 0xE7, 0x03, // U+E400 + 0xE7, 0x03, // U+E480 + 0xE7, 0x03, // U+E500 + 0xE7, 0x03, // U+E580 + 0xE7, 0x03, // U+E600 + 0xE7, 0x03, // U+E680 + 0xE7, 0x03, // U+E700 + 0xE7, 0x03, // U+E780 + 0xE7, 0x03, // U+E800 + 0xE7, 0x03, // U+E880 + 0xE7, 0x03, // U+E900 + 0xE7, 0x03, // U+E980 + 0xE7, 0x03, // U+EA00 + 0xE7, 0x03, // U+EA80 + 0xE7, 0x03, // U+EB00 + 0xE7, 0x03, // U+EB80 + 0xE7, 0x03, // U+EC00 + 0xE7, 0x03, // U+EC80 + 0xE7, 0x03, // U+ED00 + 0xE7, 0x03, // U+ED80 + 0xE7, 0x03, // U+EE00 + 0xE7, 0x03, // U+EE80 + 0xE7, 0x03, // U+EF00 + 0xE7, 0x03, // U+EF80 + 0xE7, 0x03, // U+F000 + 0xE7, 0x03, // U+F080 + 0xE7, 0x03, // U+F100 + 0xE7, 0x03, // U+F180 + 0xE7, 0x03, // U+F200 + 0xE7, 0x03, // U+F280 + 0xE7, 0x03, // U+F300 + 0xE7, 0x03, // U+F380 + 0xE7, 0x03, // U+F400 + 0xE7, 0x03, // U+F480 + 0xE7, 0x03, // U+F500 + 0xE7, 0x03, // U+F580 + 0xE7, 0x03, // U+F600 + 0xE7, 0x03, // U+F680 + 0xE7, 0x03, // U+F700 + 0xE7, 0x03, // U+F780 + 0xE7, 0x03, // U+F800 + 0xE7, 0x03, // U+F880 + 0xE7, 0x03, // U+F900 + 0x67, 0x04, // U+F980 + 0xE7, 0x04, // U+FA00 + 0x59, 0x05, // U+FA80 + 0xB3, 0x05, // U+FB00 + 0xD5, 0x05, // U+FB80 + 0xD5, 0x05, // U+FC00 + 0xD5, 0x05, // U+FC80 + 0xD5, 0x05, // U+FD00 + 0xD5, 0x05, // U+FD80 + 0xD5, 0x05, // U+FE00 + 0xD5, 0x05, // U+FE80 + 0xD5, 0x05, // U+FF00 + 0xD5, 0x05, // U+FF80 + 0xD5, 0x05, // U+10000 + }; + + /// + /// Gets the canonical decompositions, ordered by the character that decomposes so that the table can be searched by it. + /// + /// Each entry packs three scalar values into 21 bits each, most significant first: the character that decomposes, + /// the first character it decomposes to, and the second. A singleton decomposition leaves the second character zero. + /// + /// + /// Hangul syllables are absent: they decompose by arithmetic. A compatibility decomposition is absent as well, + /// because normalization does not use one. + /// + /// + /// Each entry occupies eight bytes, least significant first, and is read with + /// . + /// + /// + public static ReadOnlySpan Decompositions => new byte[] + { + 0x00, 0x03, 0x20, 0x08, 0x00, 0x00, 0x03, 0x00, // 0x3000008200300 + 0x01, 0x03, 0x20, 0x08, 0x00, 0x04, 0x03, 0x00, // 0x3040008200301 + 0x02, 0x03, 0x20, 0x08, 0x00, 0x08, 0x03, 0x00, // 0x3080008200302 + 0x03, 0x03, 0x20, 0x08, 0x00, 0x0C, 0x03, 0x00, // 0x30C0008200303 + 0x08, 0x03, 0x20, 0x08, 0x00, 0x10, 0x03, 0x00, // 0x3100008200308 + 0x0A, 0x03, 0x20, 0x08, 0x00, 0x14, 0x03, 0x00, // 0x314000820030A + 0x27, 0x03, 0x60, 0x08, 0x00, 0x1C, 0x03, 0x00, // 0x31C0008600327 + 0x00, 0x03, 0xA0, 0x08, 0x00, 0x20, 0x03, 0x00, // 0x3200008A00300 + 0x01, 0x03, 0xA0, 0x08, 0x00, 0x24, 0x03, 0x00, // 0x3240008A00301 + 0x02, 0x03, 0xA0, 0x08, 0x00, 0x28, 0x03, 0x00, // 0x3280008A00302 + 0x08, 0x03, 0xA0, 0x08, 0x00, 0x2C, 0x03, 0x00, // 0x32C0008A00308 + 0x00, 0x03, 0x20, 0x09, 0x00, 0x30, 0x03, 0x00, // 0x3300009200300 + 0x01, 0x03, 0x20, 0x09, 0x00, 0x34, 0x03, 0x00, // 0x3340009200301 + 0x02, 0x03, 0x20, 0x09, 0x00, 0x38, 0x03, 0x00, // 0x3380009200302 + 0x08, 0x03, 0x20, 0x09, 0x00, 0x3C, 0x03, 0x00, // 0x33C0009200308 + 0x03, 0x03, 0xC0, 0x09, 0x00, 0x44, 0x03, 0x00, // 0x3440009C00303 + 0x00, 0x03, 0xE0, 0x09, 0x00, 0x48, 0x03, 0x00, // 0x3480009E00300 + 0x01, 0x03, 0xE0, 0x09, 0x00, 0x4C, 0x03, 0x00, // 0x34C0009E00301 + 0x02, 0x03, 0xE0, 0x09, 0x00, 0x50, 0x03, 0x00, // 0x3500009E00302 + 0x03, 0x03, 0xE0, 0x09, 0x00, 0x54, 0x03, 0x00, // 0x3540009E00303 + 0x08, 0x03, 0xE0, 0x09, 0x00, 0x58, 0x03, 0x00, // 0x3580009E00308 + 0x00, 0x03, 0xA0, 0x0A, 0x00, 0x64, 0x03, 0x00, // 0x364000AA00300 + 0x01, 0x03, 0xA0, 0x0A, 0x00, 0x68, 0x03, 0x00, // 0x368000AA00301 + 0x02, 0x03, 0xA0, 0x0A, 0x00, 0x6C, 0x03, 0x00, // 0x36C000AA00302 + 0x08, 0x03, 0xA0, 0x0A, 0x00, 0x70, 0x03, 0x00, // 0x370000AA00308 + 0x01, 0x03, 0x20, 0x0B, 0x00, 0x74, 0x03, 0x00, // 0x374000B200301 + 0x00, 0x03, 0x20, 0x0C, 0x00, 0x80, 0x03, 0x00, // 0x380000C200300 + 0x01, 0x03, 0x20, 0x0C, 0x00, 0x84, 0x03, 0x00, // 0x384000C200301 + 0x02, 0x03, 0x20, 0x0C, 0x00, 0x88, 0x03, 0x00, // 0x388000C200302 + 0x03, 0x03, 0x20, 0x0C, 0x00, 0x8C, 0x03, 0x00, // 0x38C000C200303 + 0x08, 0x03, 0x20, 0x0C, 0x00, 0x90, 0x03, 0x00, // 0x390000C200308 + 0x0A, 0x03, 0x20, 0x0C, 0x00, 0x94, 0x03, 0x00, // 0x394000C20030A + 0x27, 0x03, 0x60, 0x0C, 0x00, 0x9C, 0x03, 0x00, // 0x39C000C600327 + 0x00, 0x03, 0xA0, 0x0C, 0x00, 0xA0, 0x03, 0x00, // 0x3A0000CA00300 + 0x01, 0x03, 0xA0, 0x0C, 0x00, 0xA4, 0x03, 0x00, // 0x3A4000CA00301 + 0x02, 0x03, 0xA0, 0x0C, 0x00, 0xA8, 0x03, 0x00, // 0x3A8000CA00302 + 0x08, 0x03, 0xA0, 0x0C, 0x00, 0xAC, 0x03, 0x00, // 0x3AC000CA00308 + 0x00, 0x03, 0x20, 0x0D, 0x00, 0xB0, 0x03, 0x00, // 0x3B0000D200300 + 0x01, 0x03, 0x20, 0x0D, 0x00, 0xB4, 0x03, 0x00, // 0x3B4000D200301 + 0x02, 0x03, 0x20, 0x0D, 0x00, 0xB8, 0x03, 0x00, // 0x3B8000D200302 + 0x08, 0x03, 0x20, 0x0D, 0x00, 0xBC, 0x03, 0x00, // 0x3BC000D200308 + 0x03, 0x03, 0xC0, 0x0D, 0x00, 0xC4, 0x03, 0x00, // 0x3C4000DC00303 + 0x00, 0x03, 0xE0, 0x0D, 0x00, 0xC8, 0x03, 0x00, // 0x3C8000DE00300 + 0x01, 0x03, 0xE0, 0x0D, 0x00, 0xCC, 0x03, 0x00, // 0x3CC000DE00301 + 0x02, 0x03, 0xE0, 0x0D, 0x00, 0xD0, 0x03, 0x00, // 0x3D0000DE00302 + 0x03, 0x03, 0xE0, 0x0D, 0x00, 0xD4, 0x03, 0x00, // 0x3D4000DE00303 + 0x08, 0x03, 0xE0, 0x0D, 0x00, 0xD8, 0x03, 0x00, // 0x3D8000DE00308 + 0x00, 0x03, 0xA0, 0x0E, 0x00, 0xE4, 0x03, 0x00, // 0x3E4000EA00300 + 0x01, 0x03, 0xA0, 0x0E, 0x00, 0xE8, 0x03, 0x00, // 0x3E8000EA00301 + 0x02, 0x03, 0xA0, 0x0E, 0x00, 0xEC, 0x03, 0x00, // 0x3EC000EA00302 + 0x08, 0x03, 0xA0, 0x0E, 0x00, 0xF0, 0x03, 0x00, // 0x3F0000EA00308 + 0x01, 0x03, 0x20, 0x0F, 0x00, 0xF4, 0x03, 0x00, // 0x3F4000F200301 + 0x08, 0x03, 0x20, 0x0F, 0x00, 0xFC, 0x03, 0x00, // 0x3FC000F200308 + 0x04, 0x03, 0x20, 0x08, 0x00, 0x00, 0x04, 0x00, // 0x4000008200304 + 0x04, 0x03, 0x20, 0x0C, 0x00, 0x04, 0x04, 0x00, // 0x404000C200304 + 0x06, 0x03, 0x20, 0x08, 0x00, 0x08, 0x04, 0x00, // 0x4080008200306 + 0x06, 0x03, 0x20, 0x0C, 0x00, 0x0C, 0x04, 0x00, // 0x40C000C200306 + 0x28, 0x03, 0x20, 0x08, 0x00, 0x10, 0x04, 0x00, // 0x4100008200328 + 0x28, 0x03, 0x20, 0x0C, 0x00, 0x14, 0x04, 0x00, // 0x414000C200328 + 0x01, 0x03, 0x60, 0x08, 0x00, 0x18, 0x04, 0x00, // 0x4180008600301 + 0x01, 0x03, 0x60, 0x0C, 0x00, 0x1C, 0x04, 0x00, // 0x41C000C600301 + 0x02, 0x03, 0x60, 0x08, 0x00, 0x20, 0x04, 0x00, // 0x4200008600302 + 0x02, 0x03, 0x60, 0x0C, 0x00, 0x24, 0x04, 0x00, // 0x424000C600302 + 0x07, 0x03, 0x60, 0x08, 0x00, 0x28, 0x04, 0x00, // 0x4280008600307 + 0x07, 0x03, 0x60, 0x0C, 0x00, 0x2C, 0x04, 0x00, // 0x42C000C600307 + 0x0C, 0x03, 0x60, 0x08, 0x00, 0x30, 0x04, 0x00, // 0x430000860030C + 0x0C, 0x03, 0x60, 0x0C, 0x00, 0x34, 0x04, 0x00, // 0x434000C60030C + 0x0C, 0x03, 0x80, 0x08, 0x00, 0x38, 0x04, 0x00, // 0x438000880030C + 0x0C, 0x03, 0x80, 0x0C, 0x00, 0x3C, 0x04, 0x00, // 0x43C000C80030C + 0x04, 0x03, 0xA0, 0x08, 0x00, 0x48, 0x04, 0x00, // 0x4480008A00304 + 0x04, 0x03, 0xA0, 0x0C, 0x00, 0x4C, 0x04, 0x00, // 0x44C000CA00304 + 0x06, 0x03, 0xA0, 0x08, 0x00, 0x50, 0x04, 0x00, // 0x4500008A00306 + 0x06, 0x03, 0xA0, 0x0C, 0x00, 0x54, 0x04, 0x00, // 0x454000CA00306 + 0x07, 0x03, 0xA0, 0x08, 0x00, 0x58, 0x04, 0x00, // 0x4580008A00307 + 0x07, 0x03, 0xA0, 0x0C, 0x00, 0x5C, 0x04, 0x00, // 0x45C000CA00307 + 0x28, 0x03, 0xA0, 0x08, 0x00, 0x60, 0x04, 0x00, // 0x4600008A00328 + 0x28, 0x03, 0xA0, 0x0C, 0x00, 0x64, 0x04, 0x00, // 0x464000CA00328 + 0x0C, 0x03, 0xA0, 0x08, 0x00, 0x68, 0x04, 0x00, // 0x4680008A0030C + 0x0C, 0x03, 0xA0, 0x0C, 0x00, 0x6C, 0x04, 0x00, // 0x46C000CA0030C + 0x02, 0x03, 0xE0, 0x08, 0x00, 0x70, 0x04, 0x00, // 0x4700008E00302 + 0x02, 0x03, 0xE0, 0x0C, 0x00, 0x74, 0x04, 0x00, // 0x474000CE00302 + 0x06, 0x03, 0xE0, 0x08, 0x00, 0x78, 0x04, 0x00, // 0x4780008E00306 + 0x06, 0x03, 0xE0, 0x0C, 0x00, 0x7C, 0x04, 0x00, // 0x47C000CE00306 + 0x07, 0x03, 0xE0, 0x08, 0x00, 0x80, 0x04, 0x00, // 0x4800008E00307 + 0x07, 0x03, 0xE0, 0x0C, 0x00, 0x84, 0x04, 0x00, // 0x484000CE00307 + 0x27, 0x03, 0xE0, 0x08, 0x00, 0x88, 0x04, 0x00, // 0x4880008E00327 + 0x27, 0x03, 0xE0, 0x0C, 0x00, 0x8C, 0x04, 0x00, // 0x48C000CE00327 + 0x02, 0x03, 0x00, 0x09, 0x00, 0x90, 0x04, 0x00, // 0x4900009000302 + 0x02, 0x03, 0x00, 0x0D, 0x00, 0x94, 0x04, 0x00, // 0x494000D000302 + 0x03, 0x03, 0x20, 0x09, 0x00, 0xA0, 0x04, 0x00, // 0x4A00009200303 + 0x03, 0x03, 0x20, 0x0D, 0x00, 0xA4, 0x04, 0x00, // 0x4A4000D200303 + 0x04, 0x03, 0x20, 0x09, 0x00, 0xA8, 0x04, 0x00, // 0x4A80009200304 + 0x04, 0x03, 0x20, 0x0D, 0x00, 0xAC, 0x04, 0x00, // 0x4AC000D200304 + 0x06, 0x03, 0x20, 0x09, 0x00, 0xB0, 0x04, 0x00, // 0x4B00009200306 + 0x06, 0x03, 0x20, 0x0D, 0x00, 0xB4, 0x04, 0x00, // 0x4B4000D200306 + 0x28, 0x03, 0x20, 0x09, 0x00, 0xB8, 0x04, 0x00, // 0x4B80009200328 + 0x28, 0x03, 0x20, 0x0D, 0x00, 0xBC, 0x04, 0x00, // 0x4BC000D200328 + 0x07, 0x03, 0x20, 0x09, 0x00, 0xC0, 0x04, 0x00, // 0x4C00009200307 + 0x02, 0x03, 0x40, 0x09, 0x00, 0xD0, 0x04, 0x00, // 0x4D00009400302 + 0x02, 0x03, 0x40, 0x0D, 0x00, 0xD4, 0x04, 0x00, // 0x4D4000D400302 + 0x27, 0x03, 0x60, 0x09, 0x00, 0xD8, 0x04, 0x00, // 0x4D80009600327 + 0x27, 0x03, 0x60, 0x0D, 0x00, 0xDC, 0x04, 0x00, // 0x4DC000D600327 + 0x01, 0x03, 0x80, 0x09, 0x00, 0xE4, 0x04, 0x00, // 0x4E40009800301 + 0x01, 0x03, 0x80, 0x0D, 0x00, 0xE8, 0x04, 0x00, // 0x4E8000D800301 + 0x27, 0x03, 0x80, 0x09, 0x00, 0xEC, 0x04, 0x00, // 0x4EC0009800327 + 0x27, 0x03, 0x80, 0x0D, 0x00, 0xF0, 0x04, 0x00, // 0x4F0000D800327 + 0x0C, 0x03, 0x80, 0x09, 0x00, 0xF4, 0x04, 0x00, // 0x4F4000980030C + 0x0C, 0x03, 0x80, 0x0D, 0x00, 0xF8, 0x04, 0x00, // 0x4F8000D80030C + 0x01, 0x03, 0xC0, 0x09, 0x00, 0x0C, 0x05, 0x00, // 0x50C0009C00301 + 0x01, 0x03, 0xC0, 0x0D, 0x00, 0x10, 0x05, 0x00, // 0x510000DC00301 + 0x27, 0x03, 0xC0, 0x09, 0x00, 0x14, 0x05, 0x00, // 0x5140009C00327 + 0x27, 0x03, 0xC0, 0x0D, 0x00, 0x18, 0x05, 0x00, // 0x518000DC00327 + 0x0C, 0x03, 0xC0, 0x09, 0x00, 0x1C, 0x05, 0x00, // 0x51C0009C0030C + 0x0C, 0x03, 0xC0, 0x0D, 0x00, 0x20, 0x05, 0x00, // 0x520000DC0030C + 0x04, 0x03, 0xE0, 0x09, 0x00, 0x30, 0x05, 0x00, // 0x5300009E00304 + 0x04, 0x03, 0xE0, 0x0D, 0x00, 0x34, 0x05, 0x00, // 0x534000DE00304 + 0x06, 0x03, 0xE0, 0x09, 0x00, 0x38, 0x05, 0x00, // 0x5380009E00306 + 0x06, 0x03, 0xE0, 0x0D, 0x00, 0x3C, 0x05, 0x00, // 0x53C000DE00306 + 0x0B, 0x03, 0xE0, 0x09, 0x00, 0x40, 0x05, 0x00, // 0x5400009E0030B + 0x0B, 0x03, 0xE0, 0x0D, 0x00, 0x44, 0x05, 0x00, // 0x544000DE0030B + 0x01, 0x03, 0x40, 0x0A, 0x00, 0x50, 0x05, 0x00, // 0x550000A400301 + 0x01, 0x03, 0x40, 0x0E, 0x00, 0x54, 0x05, 0x00, // 0x554000E400301 + 0x27, 0x03, 0x40, 0x0A, 0x00, 0x58, 0x05, 0x00, // 0x558000A400327 + 0x27, 0x03, 0x40, 0x0E, 0x00, 0x5C, 0x05, 0x00, // 0x55C000E400327 + 0x0C, 0x03, 0x40, 0x0A, 0x00, 0x60, 0x05, 0x00, // 0x560000A40030C + 0x0C, 0x03, 0x40, 0x0E, 0x00, 0x64, 0x05, 0x00, // 0x564000E40030C + 0x01, 0x03, 0x60, 0x0A, 0x00, 0x68, 0x05, 0x00, // 0x568000A600301 + 0x01, 0x03, 0x60, 0x0E, 0x00, 0x6C, 0x05, 0x00, // 0x56C000E600301 + 0x02, 0x03, 0x60, 0x0A, 0x00, 0x70, 0x05, 0x00, // 0x570000A600302 + 0x02, 0x03, 0x60, 0x0E, 0x00, 0x74, 0x05, 0x00, // 0x574000E600302 + 0x27, 0x03, 0x60, 0x0A, 0x00, 0x78, 0x05, 0x00, // 0x578000A600327 + 0x27, 0x03, 0x60, 0x0E, 0x00, 0x7C, 0x05, 0x00, // 0x57C000E600327 + 0x0C, 0x03, 0x60, 0x0A, 0x00, 0x80, 0x05, 0x00, // 0x580000A60030C + 0x0C, 0x03, 0x60, 0x0E, 0x00, 0x84, 0x05, 0x00, // 0x584000E60030C + 0x27, 0x03, 0x80, 0x0A, 0x00, 0x88, 0x05, 0x00, // 0x588000A800327 + 0x27, 0x03, 0x80, 0x0E, 0x00, 0x8C, 0x05, 0x00, // 0x58C000E800327 + 0x0C, 0x03, 0x80, 0x0A, 0x00, 0x90, 0x05, 0x00, // 0x590000A80030C + 0x0C, 0x03, 0x80, 0x0E, 0x00, 0x94, 0x05, 0x00, // 0x594000E80030C + 0x03, 0x03, 0xA0, 0x0A, 0x00, 0xA0, 0x05, 0x00, // 0x5A0000AA00303 + 0x03, 0x03, 0xA0, 0x0E, 0x00, 0xA4, 0x05, 0x00, // 0x5A4000EA00303 + 0x04, 0x03, 0xA0, 0x0A, 0x00, 0xA8, 0x05, 0x00, // 0x5A8000AA00304 + 0x04, 0x03, 0xA0, 0x0E, 0x00, 0xAC, 0x05, 0x00, // 0x5AC000EA00304 + 0x06, 0x03, 0xA0, 0x0A, 0x00, 0xB0, 0x05, 0x00, // 0x5B0000AA00306 + 0x06, 0x03, 0xA0, 0x0E, 0x00, 0xB4, 0x05, 0x00, // 0x5B4000EA00306 + 0x0A, 0x03, 0xA0, 0x0A, 0x00, 0xB8, 0x05, 0x00, // 0x5B8000AA0030A + 0x0A, 0x03, 0xA0, 0x0E, 0x00, 0xBC, 0x05, 0x00, // 0x5BC000EA0030A + 0x0B, 0x03, 0xA0, 0x0A, 0x00, 0xC0, 0x05, 0x00, // 0x5C0000AA0030B + 0x0B, 0x03, 0xA0, 0x0E, 0x00, 0xC4, 0x05, 0x00, // 0x5C4000EA0030B + 0x28, 0x03, 0xA0, 0x0A, 0x00, 0xC8, 0x05, 0x00, // 0x5C8000AA00328 + 0x28, 0x03, 0xA0, 0x0E, 0x00, 0xCC, 0x05, 0x00, // 0x5CC000EA00328 + 0x02, 0x03, 0xE0, 0x0A, 0x00, 0xD0, 0x05, 0x00, // 0x5D0000AE00302 + 0x02, 0x03, 0xE0, 0x0E, 0x00, 0xD4, 0x05, 0x00, // 0x5D4000EE00302 + 0x02, 0x03, 0x20, 0x0B, 0x00, 0xD8, 0x05, 0x00, // 0x5D8000B200302 + 0x02, 0x03, 0x20, 0x0F, 0x00, 0xDC, 0x05, 0x00, // 0x5DC000F200302 + 0x08, 0x03, 0x20, 0x0B, 0x00, 0xE0, 0x05, 0x00, // 0x5E0000B200308 + 0x01, 0x03, 0x40, 0x0B, 0x00, 0xE4, 0x05, 0x00, // 0x5E4000B400301 + 0x01, 0x03, 0x40, 0x0F, 0x00, 0xE8, 0x05, 0x00, // 0x5E8000F400301 + 0x07, 0x03, 0x40, 0x0B, 0x00, 0xEC, 0x05, 0x00, // 0x5EC000B400307 + 0x07, 0x03, 0x40, 0x0F, 0x00, 0xF0, 0x05, 0x00, // 0x5F0000F400307 + 0x0C, 0x03, 0x40, 0x0B, 0x00, 0xF4, 0x05, 0x00, // 0x5F4000B40030C + 0x0C, 0x03, 0x40, 0x0F, 0x00, 0xF8, 0x05, 0x00, // 0x5F8000F40030C + 0x1B, 0x03, 0xE0, 0x09, 0x00, 0x80, 0x06, 0x00, // 0x6800009E0031B + 0x1B, 0x03, 0xE0, 0x0D, 0x00, 0x84, 0x06, 0x00, // 0x684000DE0031B + 0x1B, 0x03, 0xA0, 0x0A, 0x00, 0xBC, 0x06, 0x00, // 0x6BC000AA0031B + 0x1B, 0x03, 0xA0, 0x0E, 0x00, 0xC0, 0x06, 0x00, // 0x6C0000EA0031B + 0x0C, 0x03, 0x20, 0x08, 0x00, 0x34, 0x07, 0x00, // 0x734000820030C + 0x0C, 0x03, 0x20, 0x0C, 0x00, 0x38, 0x07, 0x00, // 0x738000C20030C + 0x0C, 0x03, 0x20, 0x09, 0x00, 0x3C, 0x07, 0x00, // 0x73C000920030C + 0x0C, 0x03, 0x20, 0x0D, 0x00, 0x40, 0x07, 0x00, // 0x740000D20030C + 0x0C, 0x03, 0xE0, 0x09, 0x00, 0x44, 0x07, 0x00, // 0x7440009E0030C + 0x0C, 0x03, 0xE0, 0x0D, 0x00, 0x48, 0x07, 0x00, // 0x748000DE0030C + 0x0C, 0x03, 0xA0, 0x0A, 0x00, 0x4C, 0x07, 0x00, // 0x74C000AA0030C + 0x0C, 0x03, 0xA0, 0x0E, 0x00, 0x50, 0x07, 0x00, // 0x750000EA0030C + 0x04, 0x03, 0x80, 0x1B, 0x00, 0x54, 0x07, 0x00, // 0x754001B800304 + 0x04, 0x03, 0x80, 0x1F, 0x00, 0x58, 0x07, 0x00, // 0x758001F800304 + 0x01, 0x03, 0x80, 0x1B, 0x00, 0x5C, 0x07, 0x00, // 0x75C001B800301 + 0x01, 0x03, 0x80, 0x1F, 0x00, 0x60, 0x07, 0x00, // 0x760001F800301 + 0x0C, 0x03, 0x80, 0x1B, 0x00, 0x64, 0x07, 0x00, // 0x764001B80030C + 0x0C, 0x03, 0x80, 0x1F, 0x00, 0x68, 0x07, 0x00, // 0x768001F80030C + 0x00, 0x03, 0x80, 0x1B, 0x00, 0x6C, 0x07, 0x00, // 0x76C001B800300 + 0x00, 0x03, 0x80, 0x1F, 0x00, 0x70, 0x07, 0x00, // 0x770001F800300 + 0x04, 0x03, 0x80, 0x18, 0x00, 0x78, 0x07, 0x00, // 0x7780018800304 + 0x04, 0x03, 0x80, 0x1C, 0x00, 0x7C, 0x07, 0x00, // 0x77C001C800304 + 0x04, 0x03, 0xC0, 0x44, 0x00, 0x80, 0x07, 0x00, // 0x7800044C00304 + 0x04, 0x03, 0xE0, 0x44, 0x00, 0x84, 0x07, 0x00, // 0x7840044E00304 + 0x04, 0x03, 0xC0, 0x18, 0x00, 0x88, 0x07, 0x00, // 0x7880018C00304 + 0x04, 0x03, 0xC0, 0x1C, 0x00, 0x8C, 0x07, 0x00, // 0x78C001CC00304 + 0x0C, 0x03, 0xE0, 0x08, 0x00, 0x98, 0x07, 0x00, // 0x7980008E0030C + 0x0C, 0x03, 0xE0, 0x0C, 0x00, 0x9C, 0x07, 0x00, // 0x79C000CE0030C + 0x0C, 0x03, 0x60, 0x09, 0x00, 0xA0, 0x07, 0x00, // 0x7A0000960030C + 0x0C, 0x03, 0x60, 0x0D, 0x00, 0xA4, 0x07, 0x00, // 0x7A4000D60030C + 0x28, 0x03, 0xE0, 0x09, 0x00, 0xA8, 0x07, 0x00, // 0x7A80009E00328 + 0x28, 0x03, 0xE0, 0x0D, 0x00, 0xAC, 0x07, 0x00, // 0x7AC000DE00328 + 0x04, 0x03, 0x40, 0x3D, 0x00, 0xB0, 0x07, 0x00, // 0x7B0003D400304 + 0x04, 0x03, 0x60, 0x3D, 0x00, 0xB4, 0x07, 0x00, // 0x7B4003D600304 + 0x0C, 0x03, 0xE0, 0x36, 0x00, 0xB8, 0x07, 0x00, // 0x7B80036E0030C + 0x0C, 0x03, 0x40, 0x52, 0x00, 0xBC, 0x07, 0x00, // 0x7BC005240030C + 0x0C, 0x03, 0x40, 0x0D, 0x00, 0xC0, 0x07, 0x00, // 0x7C0000D40030C + 0x01, 0x03, 0xE0, 0x08, 0x00, 0xD0, 0x07, 0x00, // 0x7D00008E00301 + 0x01, 0x03, 0xE0, 0x0C, 0x00, 0xD4, 0x07, 0x00, // 0x7D4000CE00301 + 0x00, 0x03, 0xC0, 0x09, 0x00, 0xE0, 0x07, 0x00, // 0x7E00009C00300 + 0x00, 0x03, 0xC0, 0x0D, 0x00, 0xE4, 0x07, 0x00, // 0x7E4000DC00300 + 0x01, 0x03, 0xA0, 0x18, 0x00, 0xE8, 0x07, 0x00, // 0x7E80018A00301 + 0x01, 0x03, 0xA0, 0x1C, 0x00, 0xEC, 0x07, 0x00, // 0x7EC001CA00301 + 0x01, 0x03, 0xC0, 0x18, 0x00, 0xF0, 0x07, 0x00, // 0x7F00018C00301 + 0x01, 0x03, 0xC0, 0x1C, 0x00, 0xF4, 0x07, 0x00, // 0x7F4001CC00301 + 0x01, 0x03, 0x00, 0x1B, 0x00, 0xF8, 0x07, 0x00, // 0x7F8001B000301 + 0x01, 0x03, 0x00, 0x1F, 0x00, 0xFC, 0x07, 0x00, // 0x7FC001F000301 + 0x0F, 0x03, 0x20, 0x08, 0x00, 0x00, 0x08, 0x00, // 0x800000820030F + 0x0F, 0x03, 0x20, 0x0C, 0x00, 0x04, 0x08, 0x00, // 0x804000C20030F + 0x11, 0x03, 0x20, 0x08, 0x00, 0x08, 0x08, 0x00, // 0x8080008200311 + 0x11, 0x03, 0x20, 0x0C, 0x00, 0x0C, 0x08, 0x00, // 0x80C000C200311 + 0x0F, 0x03, 0xA0, 0x08, 0x00, 0x10, 0x08, 0x00, // 0x8100008A0030F + 0x0F, 0x03, 0xA0, 0x0C, 0x00, 0x14, 0x08, 0x00, // 0x814000CA0030F + 0x11, 0x03, 0xA0, 0x08, 0x00, 0x18, 0x08, 0x00, // 0x8180008A00311 + 0x11, 0x03, 0xA0, 0x0C, 0x00, 0x1C, 0x08, 0x00, // 0x81C000CA00311 + 0x0F, 0x03, 0x20, 0x09, 0x00, 0x20, 0x08, 0x00, // 0x820000920030F + 0x0F, 0x03, 0x20, 0x0D, 0x00, 0x24, 0x08, 0x00, // 0x824000D20030F + 0x11, 0x03, 0x20, 0x09, 0x00, 0x28, 0x08, 0x00, // 0x8280009200311 + 0x11, 0x03, 0x20, 0x0D, 0x00, 0x2C, 0x08, 0x00, // 0x82C000D200311 + 0x0F, 0x03, 0xE0, 0x09, 0x00, 0x30, 0x08, 0x00, // 0x8300009E0030F + 0x0F, 0x03, 0xE0, 0x0D, 0x00, 0x34, 0x08, 0x00, // 0x834000DE0030F + 0x11, 0x03, 0xE0, 0x09, 0x00, 0x38, 0x08, 0x00, // 0x8380009E00311 + 0x11, 0x03, 0xE0, 0x0D, 0x00, 0x3C, 0x08, 0x00, // 0x83C000DE00311 + 0x0F, 0x03, 0x40, 0x0A, 0x00, 0x40, 0x08, 0x00, // 0x840000A40030F + 0x0F, 0x03, 0x40, 0x0E, 0x00, 0x44, 0x08, 0x00, // 0x844000E40030F + 0x11, 0x03, 0x40, 0x0A, 0x00, 0x48, 0x08, 0x00, // 0x848000A400311 + 0x11, 0x03, 0x40, 0x0E, 0x00, 0x4C, 0x08, 0x00, // 0x84C000E400311 + 0x0F, 0x03, 0xA0, 0x0A, 0x00, 0x50, 0x08, 0x00, // 0x850000AA0030F + 0x0F, 0x03, 0xA0, 0x0E, 0x00, 0x54, 0x08, 0x00, // 0x854000EA0030F + 0x11, 0x03, 0xA0, 0x0A, 0x00, 0x58, 0x08, 0x00, // 0x858000AA00311 + 0x11, 0x03, 0xA0, 0x0E, 0x00, 0x5C, 0x08, 0x00, // 0x85C000EA00311 + 0x26, 0x03, 0x60, 0x0A, 0x00, 0x60, 0x08, 0x00, // 0x860000A600326 + 0x26, 0x03, 0x60, 0x0E, 0x00, 0x64, 0x08, 0x00, // 0x864000E600326 + 0x26, 0x03, 0x80, 0x0A, 0x00, 0x68, 0x08, 0x00, // 0x868000A800326 + 0x26, 0x03, 0x80, 0x0E, 0x00, 0x6C, 0x08, 0x00, // 0x86C000E800326 + 0x0C, 0x03, 0x00, 0x09, 0x00, 0x78, 0x08, 0x00, // 0x878000900030C + 0x0C, 0x03, 0x00, 0x0D, 0x00, 0x7C, 0x08, 0x00, // 0x87C000D00030C + 0x07, 0x03, 0x20, 0x08, 0x00, 0x98, 0x08, 0x00, // 0x8980008200307 + 0x07, 0x03, 0x20, 0x0C, 0x00, 0x9C, 0x08, 0x00, // 0x89C000C200307 + 0x27, 0x03, 0xA0, 0x08, 0x00, 0xA0, 0x08, 0x00, // 0x8A00008A00327 + 0x27, 0x03, 0xA0, 0x0C, 0x00, 0xA4, 0x08, 0x00, // 0x8A4000CA00327 + 0x04, 0x03, 0xC0, 0x1A, 0x00, 0xA8, 0x08, 0x00, // 0x8A8001AC00304 + 0x04, 0x03, 0xC0, 0x1E, 0x00, 0xAC, 0x08, 0x00, // 0x8AC001EC00304 + 0x04, 0x03, 0xA0, 0x1A, 0x00, 0xB0, 0x08, 0x00, // 0x8B0001AA00304 + 0x04, 0x03, 0xA0, 0x1E, 0x00, 0xB4, 0x08, 0x00, // 0x8B4001EA00304 + 0x07, 0x03, 0xE0, 0x09, 0x00, 0xB8, 0x08, 0x00, // 0x8B80009E00307 + 0x07, 0x03, 0xE0, 0x0D, 0x00, 0xBC, 0x08, 0x00, // 0x8BC000DE00307 + 0x04, 0x03, 0xC0, 0x45, 0x00, 0xC0, 0x08, 0x00, // 0x8C00045C00304 + 0x04, 0x03, 0xE0, 0x45, 0x00, 0xC4, 0x08, 0x00, // 0x8C40045E00304 + 0x04, 0x03, 0x20, 0x0B, 0x00, 0xC8, 0x08, 0x00, // 0x8C8000B200304 + 0x04, 0x03, 0x20, 0x0F, 0x00, 0xCC, 0x08, 0x00, // 0x8CC000F200304 + 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x0D, 0x00, // 0xD000060000000 + 0x00, 0x00, 0x20, 0x60, 0x00, 0x04, 0x0D, 0x00, // 0xD040060200000 + 0x00, 0x00, 0x60, 0x62, 0x00, 0x0C, 0x0D, 0x00, // 0xD0C0062600000 + 0x01, 0x03, 0x00, 0x61, 0x00, 0x10, 0x0D, 0x00, // 0xD100061000301 + 0x00, 0x00, 0x20, 0x57, 0x00, 0xD0, 0x0D, 0x00, // 0xDD00057200000 + 0x00, 0x00, 0x60, 0x07, 0x00, 0xF8, 0x0D, 0x00, // 0xDF80007600000 + 0x01, 0x03, 0x00, 0x15, 0x00, 0x14, 0x0E, 0x00, // 0xE140015000301 + 0x01, 0x03, 0x20, 0x72, 0x00, 0x18, 0x0E, 0x00, // 0xE180072200301 + 0x00, 0x00, 0xE0, 0x16, 0x00, 0x1C, 0x0E, 0x00, // 0xE1C0016E00000 + 0x01, 0x03, 0xA0, 0x72, 0x00, 0x20, 0x0E, 0x00, // 0xE200072A00301 + 0x01, 0x03, 0xE0, 0x72, 0x00, 0x24, 0x0E, 0x00, // 0xE240072E00301 + 0x01, 0x03, 0x20, 0x73, 0x00, 0x28, 0x0E, 0x00, // 0xE280073200301 + 0x01, 0x03, 0xE0, 0x73, 0x00, 0x30, 0x0E, 0x00, // 0xE300073E00301 + 0x01, 0x03, 0xA0, 0x74, 0x00, 0x38, 0x0E, 0x00, // 0xE380074A00301 + 0x01, 0x03, 0x20, 0x75, 0x00, 0x3C, 0x0E, 0x00, // 0xE3C0075200301 + 0x01, 0x03, 0x40, 0x79, 0x00, 0x40, 0x0E, 0x00, // 0xE400079400301 + 0x08, 0x03, 0x20, 0x73, 0x00, 0xA8, 0x0E, 0x00, // 0xEA80073200308 + 0x08, 0x03, 0xA0, 0x74, 0x00, 0xAC, 0x0E, 0x00, // 0xEAC0074A00308 + 0x01, 0x03, 0x20, 0x76, 0x00, 0xB0, 0x0E, 0x00, // 0xEB00076200301 + 0x01, 0x03, 0xA0, 0x76, 0x00, 0xB4, 0x0E, 0x00, // 0xEB40076A00301 + 0x01, 0x03, 0xE0, 0x76, 0x00, 0xB8, 0x0E, 0x00, // 0xEB80076E00301 + 0x01, 0x03, 0x20, 0x77, 0x00, 0xBC, 0x0E, 0x00, // 0xEBC0077200301 + 0x01, 0x03, 0x60, 0x79, 0x00, 0xC0, 0x0E, 0x00, // 0xEC00079600301 + 0x08, 0x03, 0x20, 0x77, 0x00, 0x28, 0x0F, 0x00, // 0xF280077200308 + 0x08, 0x03, 0xA0, 0x78, 0x00, 0x2C, 0x0F, 0x00, // 0xF2C0078A00308 + 0x01, 0x03, 0xE0, 0x77, 0x00, 0x30, 0x0F, 0x00, // 0xF300077E00301 + 0x01, 0x03, 0xA0, 0x78, 0x00, 0x34, 0x0F, 0x00, // 0xF340078A00301 + 0x01, 0x03, 0x20, 0x79, 0x00, 0x38, 0x0F, 0x00, // 0xF380079200301 + 0x01, 0x03, 0x40, 0x7A, 0x00, 0x4C, 0x0F, 0x00, // 0xF4C007A400301 + 0x08, 0x03, 0x40, 0x7A, 0x00, 0x50, 0x0F, 0x00, // 0xF50007A400308 + 0x00, 0x03, 0xA0, 0x82, 0x00, 0x00, 0x10, 0x00, // 0x10000082A00300 + 0x08, 0x03, 0xA0, 0x82, 0x00, 0x04, 0x10, 0x00, // 0x10040082A00308 + 0x01, 0x03, 0x60, 0x82, 0x00, 0x0C, 0x10, 0x00, // 0x100C0082600301 + 0x08, 0x03, 0xC0, 0x80, 0x00, 0x1C, 0x10, 0x00, // 0x101C0080C00308 + 0x01, 0x03, 0x40, 0x83, 0x00, 0x30, 0x10, 0x00, // 0x10300083400301 + 0x00, 0x03, 0x00, 0x83, 0x00, 0x34, 0x10, 0x00, // 0x10340083000300 + 0x06, 0x03, 0x60, 0x84, 0x00, 0x38, 0x10, 0x00, // 0x10380084600306 + 0x06, 0x03, 0x00, 0x83, 0x00, 0x64, 0x10, 0x00, // 0x10640083000306 + 0x06, 0x03, 0x00, 0x87, 0x00, 0xE4, 0x10, 0x00, // 0x10E40087000306 + 0x00, 0x03, 0xA0, 0x86, 0x00, 0x40, 0x11, 0x00, // 0x11400086A00300 + 0x08, 0x03, 0xA0, 0x86, 0x00, 0x44, 0x11, 0x00, // 0x11440086A00308 + 0x01, 0x03, 0x60, 0x86, 0x00, 0x4C, 0x11, 0x00, // 0x114C0086600301 + 0x08, 0x03, 0xC0, 0x8A, 0x00, 0x5C, 0x11, 0x00, // 0x115C008AC00308 + 0x01, 0x03, 0x40, 0x87, 0x00, 0x70, 0x11, 0x00, // 0x11700087400301 + 0x00, 0x03, 0x00, 0x87, 0x00, 0x74, 0x11, 0x00, // 0x11740087000300 + 0x06, 0x03, 0x60, 0x88, 0x00, 0x78, 0x11, 0x00, // 0x11780088600306 + 0x0F, 0x03, 0x80, 0x8E, 0x00, 0xD8, 0x11, 0x00, // 0x11D8008E80030F + 0x0F, 0x03, 0xA0, 0x8E, 0x00, 0xDC, 0x11, 0x00, // 0x11DC008EA0030F + 0x06, 0x03, 0xC0, 0x82, 0x00, 0x04, 0x13, 0x00, // 0x13040082C00306 + 0x06, 0x03, 0xC0, 0x86, 0x00, 0x08, 0x13, 0x00, // 0x13080086C00306 + 0x06, 0x03, 0x00, 0x82, 0x00, 0x40, 0x13, 0x00, // 0x13400082000306 + 0x06, 0x03, 0x00, 0x86, 0x00, 0x44, 0x13, 0x00, // 0x13440086000306 + 0x08, 0x03, 0x00, 0x82, 0x00, 0x48, 0x13, 0x00, // 0x13480082000308 + 0x08, 0x03, 0x00, 0x86, 0x00, 0x4C, 0x13, 0x00, // 0x134C0086000308 + 0x06, 0x03, 0xA0, 0x82, 0x00, 0x58, 0x13, 0x00, // 0x13580082A00306 + 0x06, 0x03, 0xA0, 0x86, 0x00, 0x5C, 0x13, 0x00, // 0x135C0086A00306 + 0x08, 0x03, 0x00, 0x9B, 0x00, 0x68, 0x13, 0x00, // 0x1368009B000308 + 0x08, 0x03, 0x20, 0x9B, 0x00, 0x6C, 0x13, 0x00, // 0x136C009B200308 + 0x08, 0x03, 0xC0, 0x82, 0x00, 0x70, 0x13, 0x00, // 0x13700082C00308 + 0x08, 0x03, 0xC0, 0x86, 0x00, 0x74, 0x13, 0x00, // 0x13740086C00308 + 0x08, 0x03, 0xE0, 0x82, 0x00, 0x78, 0x13, 0x00, // 0x13780082E00308 + 0x08, 0x03, 0xE0, 0x86, 0x00, 0x7C, 0x13, 0x00, // 0x137C0086E00308 + 0x04, 0x03, 0x00, 0x83, 0x00, 0x88, 0x13, 0x00, // 0x13880083000304 + 0x04, 0x03, 0x00, 0x87, 0x00, 0x8C, 0x13, 0x00, // 0x138C0087000304 + 0x08, 0x03, 0x00, 0x83, 0x00, 0x90, 0x13, 0x00, // 0x13900083000308 + 0x08, 0x03, 0x00, 0x87, 0x00, 0x94, 0x13, 0x00, // 0x13940087000308 + 0x08, 0x03, 0xC0, 0x83, 0x00, 0x98, 0x13, 0x00, // 0x13980083C00308 + 0x08, 0x03, 0xC0, 0x87, 0x00, 0x9C, 0x13, 0x00, // 0x139C0087C00308 + 0x08, 0x03, 0x00, 0x9D, 0x00, 0xA8, 0x13, 0x00, // 0x13A8009D000308 + 0x08, 0x03, 0x20, 0x9D, 0x00, 0xAC, 0x13, 0x00, // 0x13AC009D200308 + 0x08, 0x03, 0xA0, 0x85, 0x00, 0xB0, 0x13, 0x00, // 0x13B00085A00308 + 0x08, 0x03, 0xA0, 0x89, 0x00, 0xB4, 0x13, 0x00, // 0x13B40089A00308 + 0x04, 0x03, 0x60, 0x84, 0x00, 0xB8, 0x13, 0x00, // 0x13B80084600304 + 0x04, 0x03, 0x60, 0x88, 0x00, 0xBC, 0x13, 0x00, // 0x13BC0088600304 + 0x08, 0x03, 0x60, 0x84, 0x00, 0xC0, 0x13, 0x00, // 0x13C00084600308 + 0x08, 0x03, 0x60, 0x88, 0x00, 0xC4, 0x13, 0x00, // 0x13C40088600308 + 0x0B, 0x03, 0x60, 0x84, 0x00, 0xC8, 0x13, 0x00, // 0x13C8008460030B + 0x0B, 0x03, 0x60, 0x88, 0x00, 0xCC, 0x13, 0x00, // 0x13CC008860030B + 0x08, 0x03, 0xE0, 0x84, 0x00, 0xD0, 0x13, 0x00, // 0x13D00084E00308 + 0x08, 0x03, 0xE0, 0x88, 0x00, 0xD4, 0x13, 0x00, // 0x13D40088E00308 + 0x08, 0x03, 0x60, 0x85, 0x00, 0xE0, 0x13, 0x00, // 0x13E00085600308 + 0x08, 0x03, 0x60, 0x89, 0x00, 0xE4, 0x13, 0x00, // 0x13E40089600308 + 0x53, 0x06, 0xE0, 0xC4, 0x00, 0x88, 0x18, 0x00, // 0x188800C4E00653 + 0x54, 0x06, 0xE0, 0xC4, 0x00, 0x8C, 0x18, 0x00, // 0x188C00C4E00654 + 0x54, 0x06, 0x00, 0xC9, 0x00, 0x90, 0x18, 0x00, // 0x189000C9000654 + 0x55, 0x06, 0xE0, 0xC4, 0x00, 0x94, 0x18, 0x00, // 0x189400C4E00655 + 0x54, 0x06, 0x40, 0xC9, 0x00, 0x98, 0x18, 0x00, // 0x189800C9400654 + 0x54, 0x06, 0xA0, 0xDA, 0x00, 0x00, 0x1B, 0x00, // 0x1B0000DAA00654 + 0x54, 0x06, 0x20, 0xD8, 0x00, 0x08, 0x1B, 0x00, // 0x1B0800D8200654 + 0x54, 0x06, 0x40, 0xDA, 0x00, 0x4C, 0x1B, 0x00, // 0x1B4C00DA400654 + 0x3C, 0x09, 0x00, 0x25, 0x01, 0xA4, 0x24, 0x00, // 0x24A4012500093C + 0x3C, 0x09, 0x00, 0x26, 0x01, 0xC4, 0x24, 0x00, // 0x24C4012600093C + 0x3C, 0x09, 0x60, 0x26, 0x01, 0xD0, 0x24, 0x00, // 0x24D0012660093C + 0x3C, 0x09, 0xA0, 0x22, 0x01, 0x60, 0x25, 0x00, // 0x25600122A0093C + 0x3C, 0x09, 0xC0, 0x22, 0x01, 0x64, 0x25, 0x00, // 0x25640122C0093C + 0x3C, 0x09, 0xE0, 0x22, 0x01, 0x68, 0x25, 0x00, // 0x25680122E0093C + 0x3C, 0x09, 0x80, 0x23, 0x01, 0x6C, 0x25, 0x00, // 0x256C012380093C + 0x3C, 0x09, 0x20, 0x24, 0x01, 0x70, 0x25, 0x00, // 0x2570012420093C + 0x3C, 0x09, 0x40, 0x24, 0x01, 0x74, 0x25, 0x00, // 0x2574012440093C + 0x3C, 0x09, 0x60, 0x25, 0x01, 0x78, 0x25, 0x00, // 0x2578012560093C + 0x3C, 0x09, 0xE0, 0x25, 0x01, 0x7C, 0x25, 0x00, // 0x257C0125E0093C + 0xBE, 0x09, 0xE0, 0x38, 0x01, 0x2C, 0x27, 0x00, // 0x272C0138E009BE + 0xD7, 0x09, 0xE0, 0x38, 0x01, 0x30, 0x27, 0x00, // 0x27300138E009D7 + 0xBC, 0x09, 0x20, 0x34, 0x01, 0x70, 0x27, 0x00, // 0x277001342009BC + 0xBC, 0x09, 0x40, 0x34, 0x01, 0x74, 0x27, 0x00, // 0x277401344009BC + 0xBC, 0x09, 0xE0, 0x35, 0x01, 0x7C, 0x27, 0x00, // 0x277C0135E009BC + 0x3C, 0x0A, 0x40, 0x46, 0x01, 0xCC, 0x28, 0x00, // 0x28CC0146400A3C + 0x3C, 0x0A, 0x00, 0x47, 0x01, 0xD8, 0x28, 0x00, // 0x28D80147000A3C + 0x3C, 0x0A, 0xC0, 0x42, 0x01, 0x64, 0x29, 0x00, // 0x29640142C00A3C + 0x3C, 0x0A, 0xE0, 0x42, 0x01, 0x68, 0x29, 0x00, // 0x29680142E00A3C + 0x3C, 0x0A, 0x80, 0x43, 0x01, 0x6C, 0x29, 0x00, // 0x296C0143800A3C + 0x3C, 0x0A, 0x60, 0x45, 0x01, 0x78, 0x29, 0x00, // 0x29780145600A3C + 0x56, 0x0B, 0xE0, 0x68, 0x01, 0x20, 0x2D, 0x00, // 0x2D200168E00B56 + 0x3E, 0x0B, 0xE0, 0x68, 0x01, 0x2C, 0x2D, 0x00, // 0x2D2C0168E00B3E + 0x57, 0x0B, 0xE0, 0x68, 0x01, 0x30, 0x2D, 0x00, // 0x2D300168E00B57 + 0x3C, 0x0B, 0x20, 0x64, 0x01, 0x70, 0x2D, 0x00, // 0x2D700164200B3C + 0x3C, 0x0B, 0x40, 0x64, 0x01, 0x74, 0x2D, 0x00, // 0x2D740164400B3C + 0xD7, 0x0B, 0x40, 0x72, 0x01, 0x50, 0x2E, 0x00, // 0x2E500172400BD7 + 0xBE, 0x0B, 0xC0, 0x78, 0x01, 0x28, 0x2F, 0x00, // 0x2F280178C00BBE + 0xBE, 0x0B, 0xE0, 0x78, 0x01, 0x2C, 0x2F, 0x00, // 0x2F2C0178E00BBE + 0xD7, 0x0B, 0xC0, 0x78, 0x01, 0x30, 0x2F, 0x00, // 0x2F300178C00BD7 + 0x56, 0x0C, 0xC0, 0x88, 0x01, 0x20, 0x31, 0x00, // 0x31200188C00C56 + 0xD5, 0x0C, 0xE0, 0x97, 0x01, 0x00, 0x33, 0x00, // 0x33000197E00CD5 + 0xD5, 0x0C, 0xC0, 0x98, 0x01, 0x1C, 0x33, 0x00, // 0x331C0198C00CD5 + 0xD6, 0x0C, 0xC0, 0x98, 0x01, 0x20, 0x33, 0x00, // 0x33200198C00CD6 + 0xC2, 0x0C, 0xC0, 0x98, 0x01, 0x28, 0x33, 0x00, // 0x33280198C00CC2 + 0xD5, 0x0C, 0x40, 0x99, 0x01, 0x2C, 0x33, 0x00, // 0x332C0199400CD5 + 0x3E, 0x0D, 0xC0, 0xA8, 0x01, 0x28, 0x35, 0x00, // 0x352801A8C00D3E + 0x3E, 0x0D, 0xE0, 0xA8, 0x01, 0x2C, 0x35, 0x00, // 0x352C01A8E00D3E + 0x57, 0x0D, 0xC0, 0xA8, 0x01, 0x30, 0x35, 0x00, // 0x353001A8C00D57 + 0xCA, 0x0D, 0x20, 0xBB, 0x01, 0x68, 0x37, 0x00, // 0x376801BB200DCA + 0xCF, 0x0D, 0x20, 0xBB, 0x01, 0x70, 0x37, 0x00, // 0x377001BB200DCF + 0xCA, 0x0D, 0x80, 0xBB, 0x01, 0x74, 0x37, 0x00, // 0x377401BB800DCA + 0xDF, 0x0D, 0x20, 0xBB, 0x01, 0x78, 0x37, 0x00, // 0x377801BB200DDF + 0xB7, 0x0F, 0x40, 0xE8, 0x01, 0x0C, 0x3D, 0x00, // 0x3D0C01E8400FB7 + 0xB7, 0x0F, 0x80, 0xE9, 0x01, 0x34, 0x3D, 0x00, // 0x3D3401E9800FB7 + 0xB7, 0x0F, 0x20, 0xEA, 0x01, 0x48, 0x3D, 0x00, // 0x3D4801EA200FB7 + 0xB7, 0x0F, 0xC0, 0xEA, 0x01, 0x5C, 0x3D, 0x00, // 0x3D5C01EAC00FB7 + 0xB7, 0x0F, 0x60, 0xEB, 0x01, 0x70, 0x3D, 0x00, // 0x3D7001EB600FB7 + 0xB5, 0x0F, 0x00, 0xE8, 0x01, 0xA4, 0x3D, 0x00, // 0x3DA401E8000FB5 + 0x72, 0x0F, 0x20, 0xEE, 0x01, 0xCC, 0x3D, 0x00, // 0x3DCC01EE200F72 + 0x74, 0x0F, 0x20, 0xEE, 0x01, 0xD4, 0x3D, 0x00, // 0x3DD401EE200F74 + 0x80, 0x0F, 0x40, 0xF6, 0x01, 0xD8, 0x3D, 0x00, // 0x3DD801F6400F80 + 0x80, 0x0F, 0x60, 0xF6, 0x01, 0xE0, 0x3D, 0x00, // 0x3DE001F6600F80 + 0x80, 0x0F, 0x20, 0xEE, 0x01, 0x04, 0x3E, 0x00, // 0x3E0401EE200F80 + 0xB7, 0x0F, 0x40, 0xF2, 0x01, 0x4C, 0x3E, 0x00, // 0x3E4C01F2400FB7 + 0xB7, 0x0F, 0x80, 0xF3, 0x01, 0x74, 0x3E, 0x00, // 0x3E7401F3800FB7 + 0xB7, 0x0F, 0x20, 0xF4, 0x01, 0x88, 0x3E, 0x00, // 0x3E8801F4200FB7 + 0xB7, 0x0F, 0xC0, 0xF4, 0x01, 0x9C, 0x3E, 0x00, // 0x3E9C01F4C00FB7 + 0xB7, 0x0F, 0x60, 0xF5, 0x01, 0xB0, 0x3E, 0x00, // 0x3EB001F5600FB7 + 0xB5, 0x0F, 0x00, 0xF2, 0x01, 0xE4, 0x3E, 0x00, // 0x3EE401F2000FB5 + 0x2E, 0x10, 0xA0, 0x04, 0x02, 0x98, 0x40, 0x00, // 0x40980204A0102E + 0x35, 0x1B, 0xA0, 0x60, 0x03, 0x18, 0x6C, 0x00, // 0x6C180360A01B35 + 0x35, 0x1B, 0xE0, 0x60, 0x03, 0x20, 0x6C, 0x00, // 0x6C200360E01B35 + 0x35, 0x1B, 0x20, 0x61, 0x03, 0x28, 0x6C, 0x00, // 0x6C280361201B35 + 0x35, 0x1B, 0x60, 0x61, 0x03, 0x30, 0x6C, 0x00, // 0x6C300361601B35 + 0x35, 0x1B, 0xA0, 0x61, 0x03, 0x38, 0x6C, 0x00, // 0x6C380361A01B35 + 0x35, 0x1B, 0x20, 0x62, 0x03, 0x48, 0x6C, 0x00, // 0x6C480362201B35 + 0x35, 0x1B, 0x40, 0x67, 0x03, 0xEC, 0x6C, 0x00, // 0x6CEC0367401B35 + 0x35, 0x1B, 0x80, 0x67, 0x03, 0xF4, 0x6C, 0x00, // 0x6CF40367801B35 + 0x35, 0x1B, 0xC0, 0x67, 0x03, 0x00, 0x6D, 0x00, // 0x6D000367C01B35 + 0x35, 0x1B, 0xE0, 0x67, 0x03, 0x04, 0x6D, 0x00, // 0x6D040367E01B35 + 0x35, 0x1B, 0x40, 0x68, 0x03, 0x0C, 0x6D, 0x00, // 0x6D0C0368401B35 + 0x25, 0x03, 0x20, 0x08, 0x00, 0x00, 0x78, 0x00, // 0x78000008200325 + 0x25, 0x03, 0x20, 0x0C, 0x00, 0x04, 0x78, 0x00, // 0x7804000C200325 + 0x07, 0x03, 0x40, 0x08, 0x00, 0x08, 0x78, 0x00, // 0x78080008400307 + 0x07, 0x03, 0x40, 0x0C, 0x00, 0x0C, 0x78, 0x00, // 0x780C000C400307 + 0x23, 0x03, 0x40, 0x08, 0x00, 0x10, 0x78, 0x00, // 0x78100008400323 + 0x23, 0x03, 0x40, 0x0C, 0x00, 0x14, 0x78, 0x00, // 0x7814000C400323 + 0x31, 0x03, 0x40, 0x08, 0x00, 0x18, 0x78, 0x00, // 0x78180008400331 + 0x31, 0x03, 0x40, 0x0C, 0x00, 0x1C, 0x78, 0x00, // 0x781C000C400331 + 0x01, 0x03, 0xE0, 0x18, 0x00, 0x20, 0x78, 0x00, // 0x78200018E00301 + 0x01, 0x03, 0xE0, 0x1C, 0x00, 0x24, 0x78, 0x00, // 0x7824001CE00301 + 0x07, 0x03, 0x80, 0x08, 0x00, 0x28, 0x78, 0x00, // 0x78280008800307 + 0x07, 0x03, 0x80, 0x0C, 0x00, 0x2C, 0x78, 0x00, // 0x782C000C800307 + 0x23, 0x03, 0x80, 0x08, 0x00, 0x30, 0x78, 0x00, // 0x78300008800323 + 0x23, 0x03, 0x80, 0x0C, 0x00, 0x34, 0x78, 0x00, // 0x7834000C800323 + 0x31, 0x03, 0x80, 0x08, 0x00, 0x38, 0x78, 0x00, // 0x78380008800331 + 0x31, 0x03, 0x80, 0x0C, 0x00, 0x3C, 0x78, 0x00, // 0x783C000C800331 + 0x27, 0x03, 0x80, 0x08, 0x00, 0x40, 0x78, 0x00, // 0x78400008800327 + 0x27, 0x03, 0x80, 0x0C, 0x00, 0x44, 0x78, 0x00, // 0x7844000C800327 + 0x2D, 0x03, 0x80, 0x08, 0x00, 0x48, 0x78, 0x00, // 0x7848000880032D + 0x2D, 0x03, 0x80, 0x0C, 0x00, 0x4C, 0x78, 0x00, // 0x784C000C80032D + 0x00, 0x03, 0x40, 0x22, 0x00, 0x50, 0x78, 0x00, // 0x78500022400300 + 0x00, 0x03, 0x60, 0x22, 0x00, 0x54, 0x78, 0x00, // 0x78540022600300 + 0x01, 0x03, 0x40, 0x22, 0x00, 0x58, 0x78, 0x00, // 0x78580022400301 + 0x01, 0x03, 0x60, 0x22, 0x00, 0x5C, 0x78, 0x00, // 0x785C0022600301 + 0x2D, 0x03, 0xA0, 0x08, 0x00, 0x60, 0x78, 0x00, // 0x78600008A0032D + 0x2D, 0x03, 0xA0, 0x0C, 0x00, 0x64, 0x78, 0x00, // 0x7864000CA0032D + 0x30, 0x03, 0xA0, 0x08, 0x00, 0x68, 0x78, 0x00, // 0x78680008A00330 + 0x30, 0x03, 0xA0, 0x0C, 0x00, 0x6C, 0x78, 0x00, // 0x786C000CA00330 + 0x06, 0x03, 0x00, 0x45, 0x00, 0x70, 0x78, 0x00, // 0x78700045000306 + 0x06, 0x03, 0x20, 0x45, 0x00, 0x74, 0x78, 0x00, // 0x78740045200306 + 0x07, 0x03, 0xC0, 0x08, 0x00, 0x78, 0x78, 0x00, // 0x78780008C00307 + 0x07, 0x03, 0xC0, 0x0C, 0x00, 0x7C, 0x78, 0x00, // 0x787C000CC00307 + 0x04, 0x03, 0xE0, 0x08, 0x00, 0x80, 0x78, 0x00, // 0x78800008E00304 + 0x04, 0x03, 0xE0, 0x0C, 0x00, 0x84, 0x78, 0x00, // 0x7884000CE00304 + 0x07, 0x03, 0x00, 0x09, 0x00, 0x88, 0x78, 0x00, // 0x78880009000307 + 0x07, 0x03, 0x00, 0x0D, 0x00, 0x8C, 0x78, 0x00, // 0x788C000D000307 + 0x23, 0x03, 0x00, 0x09, 0x00, 0x90, 0x78, 0x00, // 0x78900009000323 + 0x23, 0x03, 0x00, 0x0D, 0x00, 0x94, 0x78, 0x00, // 0x7894000D000323 + 0x08, 0x03, 0x00, 0x09, 0x00, 0x98, 0x78, 0x00, // 0x78980009000308 + 0x08, 0x03, 0x00, 0x0D, 0x00, 0x9C, 0x78, 0x00, // 0x789C000D000308 + 0x27, 0x03, 0x00, 0x09, 0x00, 0xA0, 0x78, 0x00, // 0x78A00009000327 + 0x27, 0x03, 0x00, 0x0D, 0x00, 0xA4, 0x78, 0x00, // 0x78A4000D000327 + 0x2E, 0x03, 0x00, 0x09, 0x00, 0xA8, 0x78, 0x00, // 0x78A8000900032E + 0x2E, 0x03, 0x00, 0x0D, 0x00, 0xAC, 0x78, 0x00, // 0x78AC000D00032E + 0x30, 0x03, 0x20, 0x09, 0x00, 0xB0, 0x78, 0x00, // 0x78B00009200330 + 0x30, 0x03, 0x20, 0x0D, 0x00, 0xB4, 0x78, 0x00, // 0x78B4000D200330 + 0x01, 0x03, 0xE0, 0x19, 0x00, 0xB8, 0x78, 0x00, // 0x78B80019E00301 + 0x01, 0x03, 0xE0, 0x1D, 0x00, 0xBC, 0x78, 0x00, // 0x78BC001DE00301 + 0x01, 0x03, 0x60, 0x09, 0x00, 0xC0, 0x78, 0x00, // 0x78C00009600301 + 0x01, 0x03, 0x60, 0x0D, 0x00, 0xC4, 0x78, 0x00, // 0x78C4000D600301 + 0x23, 0x03, 0x60, 0x09, 0x00, 0xC8, 0x78, 0x00, // 0x78C80009600323 + 0x23, 0x03, 0x60, 0x0D, 0x00, 0xCC, 0x78, 0x00, // 0x78CC000D600323 + 0x31, 0x03, 0x60, 0x09, 0x00, 0xD0, 0x78, 0x00, // 0x78D00009600331 + 0x31, 0x03, 0x60, 0x0D, 0x00, 0xD4, 0x78, 0x00, // 0x78D4000D600331 + 0x23, 0x03, 0x80, 0x09, 0x00, 0xD8, 0x78, 0x00, // 0x78D80009800323 + 0x23, 0x03, 0x80, 0x0D, 0x00, 0xDC, 0x78, 0x00, // 0x78DC000D800323 + 0x04, 0x03, 0xC0, 0xC6, 0x03, 0xE0, 0x78, 0x00, // 0x78E003C6C00304 + 0x04, 0x03, 0xE0, 0xC6, 0x03, 0xE4, 0x78, 0x00, // 0x78E403C6E00304 + 0x31, 0x03, 0x80, 0x09, 0x00, 0xE8, 0x78, 0x00, // 0x78E80009800331 + 0x31, 0x03, 0x80, 0x0D, 0x00, 0xEC, 0x78, 0x00, // 0x78EC000D800331 + 0x2D, 0x03, 0x80, 0x09, 0x00, 0xF0, 0x78, 0x00, // 0x78F0000980032D + 0x2D, 0x03, 0x80, 0x0D, 0x00, 0xF4, 0x78, 0x00, // 0x78F4000D80032D + 0x01, 0x03, 0xA0, 0x09, 0x00, 0xF8, 0x78, 0x00, // 0x78F80009A00301 + 0x01, 0x03, 0xA0, 0x0D, 0x00, 0xFC, 0x78, 0x00, // 0x78FC000DA00301 + 0x07, 0x03, 0xA0, 0x09, 0x00, 0x00, 0x79, 0x00, // 0x79000009A00307 + 0x07, 0x03, 0xA0, 0x0D, 0x00, 0x04, 0x79, 0x00, // 0x7904000DA00307 + 0x23, 0x03, 0xA0, 0x09, 0x00, 0x08, 0x79, 0x00, // 0x79080009A00323 + 0x23, 0x03, 0xA0, 0x0D, 0x00, 0x0C, 0x79, 0x00, // 0x790C000DA00323 + 0x07, 0x03, 0xC0, 0x09, 0x00, 0x10, 0x79, 0x00, // 0x79100009C00307 + 0x07, 0x03, 0xC0, 0x0D, 0x00, 0x14, 0x79, 0x00, // 0x7914000DC00307 + 0x23, 0x03, 0xC0, 0x09, 0x00, 0x18, 0x79, 0x00, // 0x79180009C00323 + 0x23, 0x03, 0xC0, 0x0D, 0x00, 0x1C, 0x79, 0x00, // 0x791C000DC00323 + 0x31, 0x03, 0xC0, 0x09, 0x00, 0x20, 0x79, 0x00, // 0x79200009C00331 + 0x31, 0x03, 0xC0, 0x0D, 0x00, 0x24, 0x79, 0x00, // 0x7924000DC00331 + 0x2D, 0x03, 0xC0, 0x09, 0x00, 0x28, 0x79, 0x00, // 0x79280009C0032D + 0x2D, 0x03, 0xC0, 0x0D, 0x00, 0x2C, 0x79, 0x00, // 0x792C000DC0032D + 0x01, 0x03, 0xA0, 0x1A, 0x00, 0x30, 0x79, 0x00, // 0x7930001AA00301 + 0x01, 0x03, 0xA0, 0x1E, 0x00, 0x34, 0x79, 0x00, // 0x7934001EA00301 + 0x08, 0x03, 0xA0, 0x1A, 0x00, 0x38, 0x79, 0x00, // 0x7938001AA00308 + 0x08, 0x03, 0xA0, 0x1E, 0x00, 0x3C, 0x79, 0x00, // 0x793C001EA00308 + 0x00, 0x03, 0x80, 0x29, 0x00, 0x40, 0x79, 0x00, // 0x79400029800300 + 0x00, 0x03, 0xA0, 0x29, 0x00, 0x44, 0x79, 0x00, // 0x79440029A00300 + 0x01, 0x03, 0x80, 0x29, 0x00, 0x48, 0x79, 0x00, // 0x79480029800301 + 0x01, 0x03, 0xA0, 0x29, 0x00, 0x4C, 0x79, 0x00, // 0x794C0029A00301 + 0x01, 0x03, 0x00, 0x0A, 0x00, 0x50, 0x79, 0x00, // 0x7950000A000301 + 0x01, 0x03, 0x00, 0x0E, 0x00, 0x54, 0x79, 0x00, // 0x7954000E000301 + 0x07, 0x03, 0x00, 0x0A, 0x00, 0x58, 0x79, 0x00, // 0x7958000A000307 + 0x07, 0x03, 0x00, 0x0E, 0x00, 0x5C, 0x79, 0x00, // 0x795C000E000307 + 0x07, 0x03, 0x40, 0x0A, 0x00, 0x60, 0x79, 0x00, // 0x7960000A400307 + 0x07, 0x03, 0x40, 0x0E, 0x00, 0x64, 0x79, 0x00, // 0x7964000E400307 + 0x23, 0x03, 0x40, 0x0A, 0x00, 0x68, 0x79, 0x00, // 0x7968000A400323 + 0x23, 0x03, 0x40, 0x0E, 0x00, 0x6C, 0x79, 0x00, // 0x796C000E400323 + 0x04, 0x03, 0x40, 0xCB, 0x03, 0x70, 0x79, 0x00, // 0x797003CB400304 + 0x04, 0x03, 0x60, 0xCB, 0x03, 0x74, 0x79, 0x00, // 0x797403CB600304 + 0x31, 0x03, 0x40, 0x0A, 0x00, 0x78, 0x79, 0x00, // 0x7978000A400331 + 0x31, 0x03, 0x40, 0x0E, 0x00, 0x7C, 0x79, 0x00, // 0x797C000E400331 + 0x07, 0x03, 0x60, 0x0A, 0x00, 0x80, 0x79, 0x00, // 0x7980000A600307 + 0x07, 0x03, 0x60, 0x0E, 0x00, 0x84, 0x79, 0x00, // 0x7984000E600307 + 0x23, 0x03, 0x60, 0x0A, 0x00, 0x88, 0x79, 0x00, // 0x7988000A600323 + 0x23, 0x03, 0x60, 0x0E, 0x00, 0x8C, 0x79, 0x00, // 0x798C000E600323 + 0x07, 0x03, 0x40, 0x2B, 0x00, 0x90, 0x79, 0x00, // 0x7990002B400307 + 0x07, 0x03, 0x60, 0x2B, 0x00, 0x94, 0x79, 0x00, // 0x7994002B600307 + 0x07, 0x03, 0x00, 0x2C, 0x00, 0x98, 0x79, 0x00, // 0x7998002C000307 + 0x07, 0x03, 0x20, 0x2C, 0x00, 0x9C, 0x79, 0x00, // 0x799C002C200307 + 0x07, 0x03, 0x40, 0xCC, 0x03, 0xA0, 0x79, 0x00, // 0x79A003CC400307 + 0x07, 0x03, 0x60, 0xCC, 0x03, 0xA4, 0x79, 0x00, // 0x79A403CC600307 + 0x07, 0x03, 0x80, 0x0A, 0x00, 0xA8, 0x79, 0x00, // 0x79A8000A800307 + 0x07, 0x03, 0x80, 0x0E, 0x00, 0xAC, 0x79, 0x00, // 0x79AC000E800307 + 0x23, 0x03, 0x80, 0x0A, 0x00, 0xB0, 0x79, 0x00, // 0x79B0000A800323 + 0x23, 0x03, 0x80, 0x0E, 0x00, 0xB4, 0x79, 0x00, // 0x79B4000E800323 + 0x31, 0x03, 0x80, 0x0A, 0x00, 0xB8, 0x79, 0x00, // 0x79B8000A800331 + 0x31, 0x03, 0x80, 0x0E, 0x00, 0xBC, 0x79, 0x00, // 0x79BC000E800331 + 0x2D, 0x03, 0x80, 0x0A, 0x00, 0xC0, 0x79, 0x00, // 0x79C0000A80032D + 0x2D, 0x03, 0x80, 0x0E, 0x00, 0xC4, 0x79, 0x00, // 0x79C4000E80032D + 0x24, 0x03, 0xA0, 0x0A, 0x00, 0xC8, 0x79, 0x00, // 0x79C8000AA00324 + 0x24, 0x03, 0xA0, 0x0E, 0x00, 0xCC, 0x79, 0x00, // 0x79CC000EA00324 + 0x30, 0x03, 0xA0, 0x0A, 0x00, 0xD0, 0x79, 0x00, // 0x79D0000AA00330 + 0x30, 0x03, 0xA0, 0x0E, 0x00, 0xD4, 0x79, 0x00, // 0x79D4000EA00330 + 0x2D, 0x03, 0xA0, 0x0A, 0x00, 0xD8, 0x79, 0x00, // 0x79D8000AA0032D + 0x2D, 0x03, 0xA0, 0x0E, 0x00, 0xDC, 0x79, 0x00, // 0x79DC000EA0032D + 0x01, 0x03, 0x00, 0x2D, 0x00, 0xE0, 0x79, 0x00, // 0x79E0002D000301 + 0x01, 0x03, 0x20, 0x2D, 0x00, 0xE4, 0x79, 0x00, // 0x79E4002D200301 + 0x08, 0x03, 0x40, 0x2D, 0x00, 0xE8, 0x79, 0x00, // 0x79E8002D400308 + 0x08, 0x03, 0x60, 0x2D, 0x00, 0xEC, 0x79, 0x00, // 0x79EC002D600308 + 0x03, 0x03, 0xC0, 0x0A, 0x00, 0xF0, 0x79, 0x00, // 0x79F0000AC00303 + 0x03, 0x03, 0xC0, 0x0E, 0x00, 0xF4, 0x79, 0x00, // 0x79F4000EC00303 + 0x23, 0x03, 0xC0, 0x0A, 0x00, 0xF8, 0x79, 0x00, // 0x79F8000AC00323 + 0x23, 0x03, 0xC0, 0x0E, 0x00, 0xFC, 0x79, 0x00, // 0x79FC000EC00323 + 0x00, 0x03, 0xE0, 0x0A, 0x00, 0x00, 0x7A, 0x00, // 0x7A00000AE00300 + 0x00, 0x03, 0xE0, 0x0E, 0x00, 0x04, 0x7A, 0x00, // 0x7A04000EE00300 + 0x01, 0x03, 0xE0, 0x0A, 0x00, 0x08, 0x7A, 0x00, // 0x7A08000AE00301 + 0x01, 0x03, 0xE0, 0x0E, 0x00, 0x0C, 0x7A, 0x00, // 0x7A0C000EE00301 + 0x08, 0x03, 0xE0, 0x0A, 0x00, 0x10, 0x7A, 0x00, // 0x7A10000AE00308 + 0x08, 0x03, 0xE0, 0x0E, 0x00, 0x14, 0x7A, 0x00, // 0x7A14000EE00308 + 0x07, 0x03, 0xE0, 0x0A, 0x00, 0x18, 0x7A, 0x00, // 0x7A18000AE00307 + 0x07, 0x03, 0xE0, 0x0E, 0x00, 0x1C, 0x7A, 0x00, // 0x7A1C000EE00307 + 0x23, 0x03, 0xE0, 0x0A, 0x00, 0x20, 0x7A, 0x00, // 0x7A20000AE00323 + 0x23, 0x03, 0xE0, 0x0E, 0x00, 0x24, 0x7A, 0x00, // 0x7A24000EE00323 + 0x07, 0x03, 0x00, 0x0B, 0x00, 0x28, 0x7A, 0x00, // 0x7A28000B000307 + 0x07, 0x03, 0x00, 0x0F, 0x00, 0x2C, 0x7A, 0x00, // 0x7A2C000F000307 + 0x08, 0x03, 0x00, 0x0B, 0x00, 0x30, 0x7A, 0x00, // 0x7A30000B000308 + 0x08, 0x03, 0x00, 0x0F, 0x00, 0x34, 0x7A, 0x00, // 0x7A34000F000308 + 0x07, 0x03, 0x20, 0x0B, 0x00, 0x38, 0x7A, 0x00, // 0x7A38000B200307 + 0x07, 0x03, 0x20, 0x0F, 0x00, 0x3C, 0x7A, 0x00, // 0x7A3C000F200307 + 0x02, 0x03, 0x40, 0x0B, 0x00, 0x40, 0x7A, 0x00, // 0x7A40000B400302 + 0x02, 0x03, 0x40, 0x0F, 0x00, 0x44, 0x7A, 0x00, // 0x7A44000F400302 + 0x23, 0x03, 0x40, 0x0B, 0x00, 0x48, 0x7A, 0x00, // 0x7A48000B400323 + 0x23, 0x03, 0x40, 0x0F, 0x00, 0x4C, 0x7A, 0x00, // 0x7A4C000F400323 + 0x31, 0x03, 0x40, 0x0B, 0x00, 0x50, 0x7A, 0x00, // 0x7A50000B400331 + 0x31, 0x03, 0x40, 0x0F, 0x00, 0x54, 0x7A, 0x00, // 0x7A54000F400331 + 0x31, 0x03, 0x00, 0x0D, 0x00, 0x58, 0x7A, 0x00, // 0x7A58000D000331 + 0x08, 0x03, 0x80, 0x0E, 0x00, 0x5C, 0x7A, 0x00, // 0x7A5C000E800308 + 0x0A, 0x03, 0xE0, 0x0E, 0x00, 0x60, 0x7A, 0x00, // 0x7A60000EE0030A + 0x0A, 0x03, 0x20, 0x0F, 0x00, 0x64, 0x7A, 0x00, // 0x7A64000F20030A + 0x07, 0x03, 0xE0, 0x2F, 0x00, 0x6C, 0x7A, 0x00, // 0x7A6C002FE00307 + 0x23, 0x03, 0x20, 0x08, 0x00, 0x80, 0x7A, 0x00, // 0x7A800008200323 + 0x23, 0x03, 0x20, 0x0C, 0x00, 0x84, 0x7A, 0x00, // 0x7A84000C200323 + 0x09, 0x03, 0x20, 0x08, 0x00, 0x88, 0x7A, 0x00, // 0x7A880008200309 + 0x09, 0x03, 0x20, 0x0C, 0x00, 0x8C, 0x7A, 0x00, // 0x7A8C000C200309 + 0x01, 0x03, 0x40, 0x18, 0x00, 0x90, 0x7A, 0x00, // 0x7A900018400301 + 0x01, 0x03, 0x40, 0x1C, 0x00, 0x94, 0x7A, 0x00, // 0x7A94001C400301 + 0x00, 0x03, 0x40, 0x18, 0x00, 0x98, 0x7A, 0x00, // 0x7A980018400300 + 0x00, 0x03, 0x40, 0x1C, 0x00, 0x9C, 0x7A, 0x00, // 0x7A9C001C400300 + 0x09, 0x03, 0x40, 0x18, 0x00, 0xA0, 0x7A, 0x00, // 0x7AA00018400309 + 0x09, 0x03, 0x40, 0x1C, 0x00, 0xA4, 0x7A, 0x00, // 0x7AA4001C400309 + 0x03, 0x03, 0x40, 0x18, 0x00, 0xA8, 0x7A, 0x00, // 0x7AA80018400303 + 0x03, 0x03, 0x40, 0x1C, 0x00, 0xAC, 0x7A, 0x00, // 0x7AAC001C400303 + 0x02, 0x03, 0x00, 0xD4, 0x03, 0xB0, 0x7A, 0x00, // 0x7AB003D4000302 + 0x02, 0x03, 0x20, 0xD4, 0x03, 0xB4, 0x7A, 0x00, // 0x7AB403D4200302 + 0x01, 0x03, 0x40, 0x20, 0x00, 0xB8, 0x7A, 0x00, // 0x7AB80020400301 + 0x01, 0x03, 0x60, 0x20, 0x00, 0xBC, 0x7A, 0x00, // 0x7ABC0020600301 + 0x00, 0x03, 0x40, 0x20, 0x00, 0xC0, 0x7A, 0x00, // 0x7AC00020400300 + 0x00, 0x03, 0x60, 0x20, 0x00, 0xC4, 0x7A, 0x00, // 0x7AC40020600300 + 0x09, 0x03, 0x40, 0x20, 0x00, 0xC8, 0x7A, 0x00, // 0x7AC80020400309 + 0x09, 0x03, 0x60, 0x20, 0x00, 0xCC, 0x7A, 0x00, // 0x7ACC0020600309 + 0x03, 0x03, 0x40, 0x20, 0x00, 0xD0, 0x7A, 0x00, // 0x7AD00020400303 + 0x03, 0x03, 0x60, 0x20, 0x00, 0xD4, 0x7A, 0x00, // 0x7AD40020600303 + 0x06, 0x03, 0x00, 0xD4, 0x03, 0xD8, 0x7A, 0x00, // 0x7AD803D4000306 + 0x06, 0x03, 0x20, 0xD4, 0x03, 0xDC, 0x7A, 0x00, // 0x7ADC03D4200306 + 0x23, 0x03, 0xA0, 0x08, 0x00, 0xE0, 0x7A, 0x00, // 0x7AE00008A00323 + 0x23, 0x03, 0xA0, 0x0C, 0x00, 0xE4, 0x7A, 0x00, // 0x7AE4000CA00323 + 0x09, 0x03, 0xA0, 0x08, 0x00, 0xE8, 0x7A, 0x00, // 0x7AE80008A00309 + 0x09, 0x03, 0xA0, 0x0C, 0x00, 0xEC, 0x7A, 0x00, // 0x7AEC000CA00309 + 0x03, 0x03, 0xA0, 0x08, 0x00, 0xF0, 0x7A, 0x00, // 0x7AF00008A00303 + 0x03, 0x03, 0xA0, 0x0C, 0x00, 0xF4, 0x7A, 0x00, // 0x7AF4000CA00303 + 0x01, 0x03, 0x40, 0x19, 0x00, 0xF8, 0x7A, 0x00, // 0x7AF80019400301 + 0x01, 0x03, 0x40, 0x1D, 0x00, 0xFC, 0x7A, 0x00, // 0x7AFC001D400301 + 0x00, 0x03, 0x40, 0x19, 0x00, 0x00, 0x7B, 0x00, // 0x7B000019400300 + 0x00, 0x03, 0x40, 0x1D, 0x00, 0x04, 0x7B, 0x00, // 0x7B04001D400300 + 0x09, 0x03, 0x40, 0x19, 0x00, 0x08, 0x7B, 0x00, // 0x7B080019400309 + 0x09, 0x03, 0x40, 0x1D, 0x00, 0x0C, 0x7B, 0x00, // 0x7B0C001D400309 + 0x03, 0x03, 0x40, 0x19, 0x00, 0x10, 0x7B, 0x00, // 0x7B100019400303 + 0x03, 0x03, 0x40, 0x1D, 0x00, 0x14, 0x7B, 0x00, // 0x7B14001D400303 + 0x02, 0x03, 0x00, 0xD7, 0x03, 0x18, 0x7B, 0x00, // 0x7B1803D7000302 + 0x02, 0x03, 0x20, 0xD7, 0x03, 0x1C, 0x7B, 0x00, // 0x7B1C03D7200302 + 0x09, 0x03, 0x20, 0x09, 0x00, 0x20, 0x7B, 0x00, // 0x7B200009200309 + 0x09, 0x03, 0x20, 0x0D, 0x00, 0x24, 0x7B, 0x00, // 0x7B24000D200309 + 0x23, 0x03, 0x20, 0x09, 0x00, 0x28, 0x7B, 0x00, // 0x7B280009200323 + 0x23, 0x03, 0x20, 0x0D, 0x00, 0x2C, 0x7B, 0x00, // 0x7B2C000D200323 + 0x23, 0x03, 0xE0, 0x09, 0x00, 0x30, 0x7B, 0x00, // 0x7B300009E00323 + 0x23, 0x03, 0xE0, 0x0D, 0x00, 0x34, 0x7B, 0x00, // 0x7B34000DE00323 + 0x09, 0x03, 0xE0, 0x09, 0x00, 0x38, 0x7B, 0x00, // 0x7B380009E00309 + 0x09, 0x03, 0xE0, 0x0D, 0x00, 0x3C, 0x7B, 0x00, // 0x7B3C000DE00309 + 0x01, 0x03, 0x80, 0x1A, 0x00, 0x40, 0x7B, 0x00, // 0x7B40001A800301 + 0x01, 0x03, 0x80, 0x1E, 0x00, 0x44, 0x7B, 0x00, // 0x7B44001E800301 + 0x00, 0x03, 0x80, 0x1A, 0x00, 0x48, 0x7B, 0x00, // 0x7B48001A800300 + 0x00, 0x03, 0x80, 0x1E, 0x00, 0x4C, 0x7B, 0x00, // 0x7B4C001E800300 + 0x09, 0x03, 0x80, 0x1A, 0x00, 0x50, 0x7B, 0x00, // 0x7B50001A800309 + 0x09, 0x03, 0x80, 0x1E, 0x00, 0x54, 0x7B, 0x00, // 0x7B54001E800309 + 0x03, 0x03, 0x80, 0x1A, 0x00, 0x58, 0x7B, 0x00, // 0x7B58001A800303 + 0x03, 0x03, 0x80, 0x1E, 0x00, 0x5C, 0x7B, 0x00, // 0x7B5C001E800303 + 0x02, 0x03, 0x80, 0xD9, 0x03, 0x60, 0x7B, 0x00, // 0x7B6003D9800302 + 0x02, 0x03, 0xA0, 0xD9, 0x03, 0x64, 0x7B, 0x00, // 0x7B6403D9A00302 + 0x01, 0x03, 0x00, 0x34, 0x00, 0x68, 0x7B, 0x00, // 0x7B680034000301 + 0x01, 0x03, 0x20, 0x34, 0x00, 0x6C, 0x7B, 0x00, // 0x7B6C0034200301 + 0x00, 0x03, 0x00, 0x34, 0x00, 0x70, 0x7B, 0x00, // 0x7B700034000300 + 0x00, 0x03, 0x20, 0x34, 0x00, 0x74, 0x7B, 0x00, // 0x7B740034200300 + 0x09, 0x03, 0x00, 0x34, 0x00, 0x78, 0x7B, 0x00, // 0x7B780034000309 + 0x09, 0x03, 0x20, 0x34, 0x00, 0x7C, 0x7B, 0x00, // 0x7B7C0034200309 + 0x03, 0x03, 0x00, 0x34, 0x00, 0x80, 0x7B, 0x00, // 0x7B800034000303 + 0x03, 0x03, 0x20, 0x34, 0x00, 0x84, 0x7B, 0x00, // 0x7B840034200303 + 0x23, 0x03, 0x00, 0x34, 0x00, 0x88, 0x7B, 0x00, // 0x7B880034000323 + 0x23, 0x03, 0x20, 0x34, 0x00, 0x8C, 0x7B, 0x00, // 0x7B8C0034200323 + 0x23, 0x03, 0xA0, 0x0A, 0x00, 0x90, 0x7B, 0x00, // 0x7B90000AA00323 + 0x23, 0x03, 0xA0, 0x0E, 0x00, 0x94, 0x7B, 0x00, // 0x7B94000EA00323 + 0x09, 0x03, 0xA0, 0x0A, 0x00, 0x98, 0x7B, 0x00, // 0x7B98000AA00309 + 0x09, 0x03, 0xA0, 0x0E, 0x00, 0x9C, 0x7B, 0x00, // 0x7B9C000EA00309 + 0x01, 0x03, 0xE0, 0x35, 0x00, 0xA0, 0x7B, 0x00, // 0x7BA00035E00301 + 0x01, 0x03, 0x00, 0x36, 0x00, 0xA4, 0x7B, 0x00, // 0x7BA40036000301 + 0x00, 0x03, 0xE0, 0x35, 0x00, 0xA8, 0x7B, 0x00, // 0x7BA80035E00300 + 0x00, 0x03, 0x00, 0x36, 0x00, 0xAC, 0x7B, 0x00, // 0x7BAC0036000300 + 0x09, 0x03, 0xE0, 0x35, 0x00, 0xB0, 0x7B, 0x00, // 0x7BB00035E00309 + 0x09, 0x03, 0x00, 0x36, 0x00, 0xB4, 0x7B, 0x00, // 0x7BB40036000309 + 0x03, 0x03, 0xE0, 0x35, 0x00, 0xB8, 0x7B, 0x00, // 0x7BB80035E00303 + 0x03, 0x03, 0x00, 0x36, 0x00, 0xBC, 0x7B, 0x00, // 0x7BBC0036000303 + 0x23, 0x03, 0xE0, 0x35, 0x00, 0xC0, 0x7B, 0x00, // 0x7BC00035E00323 + 0x23, 0x03, 0x00, 0x36, 0x00, 0xC4, 0x7B, 0x00, // 0x7BC40036000323 + 0x00, 0x03, 0x20, 0x0B, 0x00, 0xC8, 0x7B, 0x00, // 0x7BC8000B200300 + 0x00, 0x03, 0x20, 0x0F, 0x00, 0xCC, 0x7B, 0x00, // 0x7BCC000F200300 + 0x23, 0x03, 0x20, 0x0B, 0x00, 0xD0, 0x7B, 0x00, // 0x7BD0000B200323 + 0x23, 0x03, 0x20, 0x0F, 0x00, 0xD4, 0x7B, 0x00, // 0x7BD4000F200323 + 0x09, 0x03, 0x20, 0x0B, 0x00, 0xD8, 0x7B, 0x00, // 0x7BD8000B200309 + 0x09, 0x03, 0x20, 0x0F, 0x00, 0xDC, 0x7B, 0x00, // 0x7BDC000F200309 + 0x03, 0x03, 0x20, 0x0B, 0x00, 0xE0, 0x7B, 0x00, // 0x7BE0000B200303 + 0x03, 0x03, 0x20, 0x0F, 0x00, 0xE4, 0x7B, 0x00, // 0x7BE4000F200303 + 0x13, 0x03, 0x20, 0x76, 0x00, 0x00, 0x7C, 0x00, // 0x7C000076200313 + 0x14, 0x03, 0x20, 0x76, 0x00, 0x04, 0x7C, 0x00, // 0x7C040076200314 + 0x00, 0x03, 0x00, 0xE0, 0x03, 0x08, 0x7C, 0x00, // 0x7C0803E0000300 + 0x00, 0x03, 0x20, 0xE0, 0x03, 0x0C, 0x7C, 0x00, // 0x7C0C03E0200300 + 0x01, 0x03, 0x00, 0xE0, 0x03, 0x10, 0x7C, 0x00, // 0x7C1003E0000301 + 0x01, 0x03, 0x20, 0xE0, 0x03, 0x14, 0x7C, 0x00, // 0x7C1403E0200301 + 0x42, 0x03, 0x00, 0xE0, 0x03, 0x18, 0x7C, 0x00, // 0x7C1803E0000342 + 0x42, 0x03, 0x20, 0xE0, 0x03, 0x1C, 0x7C, 0x00, // 0x7C1C03E0200342 + 0x13, 0x03, 0x20, 0x72, 0x00, 0x20, 0x7C, 0x00, // 0x7C200072200313 + 0x14, 0x03, 0x20, 0x72, 0x00, 0x24, 0x7C, 0x00, // 0x7C240072200314 + 0x00, 0x03, 0x00, 0xE1, 0x03, 0x28, 0x7C, 0x00, // 0x7C2803E1000300 + 0x00, 0x03, 0x20, 0xE1, 0x03, 0x2C, 0x7C, 0x00, // 0x7C2C03E1200300 + 0x01, 0x03, 0x00, 0xE1, 0x03, 0x30, 0x7C, 0x00, // 0x7C3003E1000301 + 0x01, 0x03, 0x20, 0xE1, 0x03, 0x34, 0x7C, 0x00, // 0x7C3403E1200301 + 0x42, 0x03, 0x00, 0xE1, 0x03, 0x38, 0x7C, 0x00, // 0x7C3803E1000342 + 0x42, 0x03, 0x20, 0xE1, 0x03, 0x3C, 0x7C, 0x00, // 0x7C3C03E1200342 + 0x13, 0x03, 0xA0, 0x76, 0x00, 0x40, 0x7C, 0x00, // 0x7C400076A00313 + 0x14, 0x03, 0xA0, 0x76, 0x00, 0x44, 0x7C, 0x00, // 0x7C440076A00314 + 0x00, 0x03, 0x00, 0xE2, 0x03, 0x48, 0x7C, 0x00, // 0x7C4803E2000300 + 0x00, 0x03, 0x20, 0xE2, 0x03, 0x4C, 0x7C, 0x00, // 0x7C4C03E2200300 + 0x01, 0x03, 0x00, 0xE2, 0x03, 0x50, 0x7C, 0x00, // 0x7C5003E2000301 + 0x01, 0x03, 0x20, 0xE2, 0x03, 0x54, 0x7C, 0x00, // 0x7C5403E2200301 + 0x13, 0x03, 0xA0, 0x72, 0x00, 0x60, 0x7C, 0x00, // 0x7C600072A00313 + 0x14, 0x03, 0xA0, 0x72, 0x00, 0x64, 0x7C, 0x00, // 0x7C640072A00314 + 0x00, 0x03, 0x00, 0xE3, 0x03, 0x68, 0x7C, 0x00, // 0x7C6803E3000300 + 0x00, 0x03, 0x20, 0xE3, 0x03, 0x6C, 0x7C, 0x00, // 0x7C6C03E3200300 + 0x01, 0x03, 0x00, 0xE3, 0x03, 0x70, 0x7C, 0x00, // 0x7C7003E3000301 + 0x01, 0x03, 0x20, 0xE3, 0x03, 0x74, 0x7C, 0x00, // 0x7C7403E3200301 + 0x13, 0x03, 0xE0, 0x76, 0x00, 0x80, 0x7C, 0x00, // 0x7C800076E00313 + 0x14, 0x03, 0xE0, 0x76, 0x00, 0x84, 0x7C, 0x00, // 0x7C840076E00314 + 0x00, 0x03, 0x00, 0xE4, 0x03, 0x88, 0x7C, 0x00, // 0x7C8803E4000300 + 0x00, 0x03, 0x20, 0xE4, 0x03, 0x8C, 0x7C, 0x00, // 0x7C8C03E4200300 + 0x01, 0x03, 0x00, 0xE4, 0x03, 0x90, 0x7C, 0x00, // 0x7C9003E4000301 + 0x01, 0x03, 0x20, 0xE4, 0x03, 0x94, 0x7C, 0x00, // 0x7C9403E4200301 + 0x42, 0x03, 0x00, 0xE4, 0x03, 0x98, 0x7C, 0x00, // 0x7C9803E4000342 + 0x42, 0x03, 0x20, 0xE4, 0x03, 0x9C, 0x7C, 0x00, // 0x7C9C03E4200342 + 0x13, 0x03, 0xE0, 0x72, 0x00, 0xA0, 0x7C, 0x00, // 0x7CA00072E00313 + 0x14, 0x03, 0xE0, 0x72, 0x00, 0xA4, 0x7C, 0x00, // 0x7CA40072E00314 + 0x00, 0x03, 0x00, 0xE5, 0x03, 0xA8, 0x7C, 0x00, // 0x7CA803E5000300 + 0x00, 0x03, 0x20, 0xE5, 0x03, 0xAC, 0x7C, 0x00, // 0x7CAC03E5200300 + 0x01, 0x03, 0x00, 0xE5, 0x03, 0xB0, 0x7C, 0x00, // 0x7CB003E5000301 + 0x01, 0x03, 0x20, 0xE5, 0x03, 0xB4, 0x7C, 0x00, // 0x7CB403E5200301 + 0x42, 0x03, 0x00, 0xE5, 0x03, 0xB8, 0x7C, 0x00, // 0x7CB803E5000342 + 0x42, 0x03, 0x20, 0xE5, 0x03, 0xBC, 0x7C, 0x00, // 0x7CBC03E5200342 + 0x13, 0x03, 0x20, 0x77, 0x00, 0xC0, 0x7C, 0x00, // 0x7CC00077200313 + 0x14, 0x03, 0x20, 0x77, 0x00, 0xC4, 0x7C, 0x00, // 0x7CC40077200314 + 0x00, 0x03, 0x00, 0xE6, 0x03, 0xC8, 0x7C, 0x00, // 0x7CC803E6000300 + 0x00, 0x03, 0x20, 0xE6, 0x03, 0xCC, 0x7C, 0x00, // 0x7CCC03E6200300 + 0x01, 0x03, 0x00, 0xE6, 0x03, 0xD0, 0x7C, 0x00, // 0x7CD003E6000301 + 0x01, 0x03, 0x20, 0xE6, 0x03, 0xD4, 0x7C, 0x00, // 0x7CD403E6200301 + 0x42, 0x03, 0x00, 0xE6, 0x03, 0xD8, 0x7C, 0x00, // 0x7CD803E6000342 + 0x42, 0x03, 0x20, 0xE6, 0x03, 0xDC, 0x7C, 0x00, // 0x7CDC03E6200342 + 0x13, 0x03, 0x20, 0x73, 0x00, 0xE0, 0x7C, 0x00, // 0x7CE00073200313 + 0x14, 0x03, 0x20, 0x73, 0x00, 0xE4, 0x7C, 0x00, // 0x7CE40073200314 + 0x00, 0x03, 0x00, 0xE7, 0x03, 0xE8, 0x7C, 0x00, // 0x7CE803E7000300 + 0x00, 0x03, 0x20, 0xE7, 0x03, 0xEC, 0x7C, 0x00, // 0x7CEC03E7200300 + 0x01, 0x03, 0x00, 0xE7, 0x03, 0xF0, 0x7C, 0x00, // 0x7CF003E7000301 + 0x01, 0x03, 0x20, 0xE7, 0x03, 0xF4, 0x7C, 0x00, // 0x7CF403E7200301 + 0x42, 0x03, 0x00, 0xE7, 0x03, 0xF8, 0x7C, 0x00, // 0x7CF803E7000342 + 0x42, 0x03, 0x20, 0xE7, 0x03, 0xFC, 0x7C, 0x00, // 0x7CFC03E7200342 + 0x13, 0x03, 0xE0, 0x77, 0x00, 0x00, 0x7D, 0x00, // 0x7D000077E00313 + 0x14, 0x03, 0xE0, 0x77, 0x00, 0x04, 0x7D, 0x00, // 0x7D040077E00314 + 0x00, 0x03, 0x00, 0xE8, 0x03, 0x08, 0x7D, 0x00, // 0x7D0803E8000300 + 0x00, 0x03, 0x20, 0xE8, 0x03, 0x0C, 0x7D, 0x00, // 0x7D0C03E8200300 + 0x01, 0x03, 0x00, 0xE8, 0x03, 0x10, 0x7D, 0x00, // 0x7D1003E8000301 + 0x01, 0x03, 0x20, 0xE8, 0x03, 0x14, 0x7D, 0x00, // 0x7D1403E8200301 + 0x13, 0x03, 0xE0, 0x73, 0x00, 0x20, 0x7D, 0x00, // 0x7D200073E00313 + 0x14, 0x03, 0xE0, 0x73, 0x00, 0x24, 0x7D, 0x00, // 0x7D240073E00314 + 0x00, 0x03, 0x00, 0xE9, 0x03, 0x28, 0x7D, 0x00, // 0x7D2803E9000300 + 0x00, 0x03, 0x20, 0xE9, 0x03, 0x2C, 0x7D, 0x00, // 0x7D2C03E9200300 + 0x01, 0x03, 0x00, 0xE9, 0x03, 0x30, 0x7D, 0x00, // 0x7D3003E9000301 + 0x01, 0x03, 0x20, 0xE9, 0x03, 0x34, 0x7D, 0x00, // 0x7D3403E9200301 + 0x13, 0x03, 0xA0, 0x78, 0x00, 0x40, 0x7D, 0x00, // 0x7D400078A00313 + 0x14, 0x03, 0xA0, 0x78, 0x00, 0x44, 0x7D, 0x00, // 0x7D440078A00314 + 0x00, 0x03, 0x00, 0xEA, 0x03, 0x48, 0x7D, 0x00, // 0x7D4803EA000300 + 0x00, 0x03, 0x20, 0xEA, 0x03, 0x4C, 0x7D, 0x00, // 0x7D4C03EA200300 + 0x01, 0x03, 0x00, 0xEA, 0x03, 0x50, 0x7D, 0x00, // 0x7D5003EA000301 + 0x01, 0x03, 0x20, 0xEA, 0x03, 0x54, 0x7D, 0x00, // 0x7D5403EA200301 + 0x42, 0x03, 0x00, 0xEA, 0x03, 0x58, 0x7D, 0x00, // 0x7D5803EA000342 + 0x42, 0x03, 0x20, 0xEA, 0x03, 0x5C, 0x7D, 0x00, // 0x7D5C03EA200342 + 0x14, 0x03, 0xA0, 0x74, 0x00, 0x64, 0x7D, 0x00, // 0x7D640074A00314 + 0x00, 0x03, 0x20, 0xEB, 0x03, 0x6C, 0x7D, 0x00, // 0x7D6C03EB200300 + 0x01, 0x03, 0x20, 0xEB, 0x03, 0x74, 0x7D, 0x00, // 0x7D7403EB200301 + 0x42, 0x03, 0x20, 0xEB, 0x03, 0x7C, 0x7D, 0x00, // 0x7D7C03EB200342 + 0x13, 0x03, 0x20, 0x79, 0x00, 0x80, 0x7D, 0x00, // 0x7D800079200313 + 0x14, 0x03, 0x20, 0x79, 0x00, 0x84, 0x7D, 0x00, // 0x7D840079200314 + 0x00, 0x03, 0x00, 0xEC, 0x03, 0x88, 0x7D, 0x00, // 0x7D8803EC000300 + 0x00, 0x03, 0x20, 0xEC, 0x03, 0x8C, 0x7D, 0x00, // 0x7D8C03EC200300 + 0x01, 0x03, 0x00, 0xEC, 0x03, 0x90, 0x7D, 0x00, // 0x7D9003EC000301 + 0x01, 0x03, 0x20, 0xEC, 0x03, 0x94, 0x7D, 0x00, // 0x7D9403EC200301 + 0x42, 0x03, 0x00, 0xEC, 0x03, 0x98, 0x7D, 0x00, // 0x7D9803EC000342 + 0x42, 0x03, 0x20, 0xEC, 0x03, 0x9C, 0x7D, 0x00, // 0x7D9C03EC200342 + 0x13, 0x03, 0x20, 0x75, 0x00, 0xA0, 0x7D, 0x00, // 0x7DA00075200313 + 0x14, 0x03, 0x20, 0x75, 0x00, 0xA4, 0x7D, 0x00, // 0x7DA40075200314 + 0x00, 0x03, 0x00, 0xED, 0x03, 0xA8, 0x7D, 0x00, // 0x7DA803ED000300 + 0x00, 0x03, 0x20, 0xED, 0x03, 0xAC, 0x7D, 0x00, // 0x7DAC03ED200300 + 0x01, 0x03, 0x00, 0xED, 0x03, 0xB0, 0x7D, 0x00, // 0x7DB003ED000301 + 0x01, 0x03, 0x20, 0xED, 0x03, 0xB4, 0x7D, 0x00, // 0x7DB403ED200301 + 0x42, 0x03, 0x00, 0xED, 0x03, 0xB8, 0x7D, 0x00, // 0x7DB803ED000342 + 0x42, 0x03, 0x20, 0xED, 0x03, 0xBC, 0x7D, 0x00, // 0x7DBC03ED200342 + 0x00, 0x03, 0x20, 0x76, 0x00, 0xC0, 0x7D, 0x00, // 0x7DC00076200300 + 0x00, 0x00, 0x80, 0x75, 0x00, 0xC4, 0x7D, 0x00, // 0x7DC40075800000 + 0x00, 0x03, 0xA0, 0x76, 0x00, 0xC8, 0x7D, 0x00, // 0x7DC80076A00300 + 0x00, 0x00, 0xA0, 0x75, 0x00, 0xCC, 0x7D, 0x00, // 0x7DCC0075A00000 + 0x00, 0x03, 0xE0, 0x76, 0x00, 0xD0, 0x7D, 0x00, // 0x7DD00076E00300 + 0x00, 0x00, 0xC0, 0x75, 0x00, 0xD4, 0x7D, 0x00, // 0x7DD40075C00000 + 0x00, 0x03, 0x20, 0x77, 0x00, 0xD8, 0x7D, 0x00, // 0x7DD80077200300 + 0x00, 0x00, 0xE0, 0x75, 0x00, 0xDC, 0x7D, 0x00, // 0x7DDC0075E00000 + 0x00, 0x03, 0xE0, 0x77, 0x00, 0xE0, 0x7D, 0x00, // 0x7DE00077E00300 + 0x00, 0x00, 0x80, 0x79, 0x00, 0xE4, 0x7D, 0x00, // 0x7DE40079800000 + 0x00, 0x03, 0xA0, 0x78, 0x00, 0xE8, 0x7D, 0x00, // 0x7DE80078A00300 + 0x00, 0x00, 0xA0, 0x79, 0x00, 0xEC, 0x7D, 0x00, // 0x7DEC0079A00000 + 0x00, 0x03, 0x20, 0x79, 0x00, 0xF0, 0x7D, 0x00, // 0x7DF00079200300 + 0x00, 0x00, 0xC0, 0x79, 0x00, 0xF4, 0x7D, 0x00, // 0x7DF40079C00000 + 0x45, 0x03, 0x00, 0xE0, 0x03, 0x00, 0x7E, 0x00, // 0x7E0003E0000345 + 0x45, 0x03, 0x20, 0xE0, 0x03, 0x04, 0x7E, 0x00, // 0x7E0403E0200345 + 0x45, 0x03, 0x40, 0xE0, 0x03, 0x08, 0x7E, 0x00, // 0x7E0803E0400345 + 0x45, 0x03, 0x60, 0xE0, 0x03, 0x0C, 0x7E, 0x00, // 0x7E0C03E0600345 + 0x45, 0x03, 0x80, 0xE0, 0x03, 0x10, 0x7E, 0x00, // 0x7E1003E0800345 + 0x45, 0x03, 0xA0, 0xE0, 0x03, 0x14, 0x7E, 0x00, // 0x7E1403E0A00345 + 0x45, 0x03, 0xC0, 0xE0, 0x03, 0x18, 0x7E, 0x00, // 0x7E1803E0C00345 + 0x45, 0x03, 0xE0, 0xE0, 0x03, 0x1C, 0x7E, 0x00, // 0x7E1C03E0E00345 + 0x45, 0x03, 0x00, 0xE1, 0x03, 0x20, 0x7E, 0x00, // 0x7E2003E1000345 + 0x45, 0x03, 0x20, 0xE1, 0x03, 0x24, 0x7E, 0x00, // 0x7E2403E1200345 + 0x45, 0x03, 0x40, 0xE1, 0x03, 0x28, 0x7E, 0x00, // 0x7E2803E1400345 + 0x45, 0x03, 0x60, 0xE1, 0x03, 0x2C, 0x7E, 0x00, // 0x7E2C03E1600345 + 0x45, 0x03, 0x80, 0xE1, 0x03, 0x30, 0x7E, 0x00, // 0x7E3003E1800345 + 0x45, 0x03, 0xA0, 0xE1, 0x03, 0x34, 0x7E, 0x00, // 0x7E3403E1A00345 + 0x45, 0x03, 0xC0, 0xE1, 0x03, 0x38, 0x7E, 0x00, // 0x7E3803E1C00345 + 0x45, 0x03, 0xE0, 0xE1, 0x03, 0x3C, 0x7E, 0x00, // 0x7E3C03E1E00345 + 0x45, 0x03, 0x00, 0xE4, 0x03, 0x40, 0x7E, 0x00, // 0x7E4003E4000345 + 0x45, 0x03, 0x20, 0xE4, 0x03, 0x44, 0x7E, 0x00, // 0x7E4403E4200345 + 0x45, 0x03, 0x40, 0xE4, 0x03, 0x48, 0x7E, 0x00, // 0x7E4803E4400345 + 0x45, 0x03, 0x60, 0xE4, 0x03, 0x4C, 0x7E, 0x00, // 0x7E4C03E4600345 + 0x45, 0x03, 0x80, 0xE4, 0x03, 0x50, 0x7E, 0x00, // 0x7E5003E4800345 + 0x45, 0x03, 0xA0, 0xE4, 0x03, 0x54, 0x7E, 0x00, // 0x7E5403E4A00345 + 0x45, 0x03, 0xC0, 0xE4, 0x03, 0x58, 0x7E, 0x00, // 0x7E5803E4C00345 + 0x45, 0x03, 0xE0, 0xE4, 0x03, 0x5C, 0x7E, 0x00, // 0x7E5C03E4E00345 + 0x45, 0x03, 0x00, 0xE5, 0x03, 0x60, 0x7E, 0x00, // 0x7E6003E5000345 + 0x45, 0x03, 0x20, 0xE5, 0x03, 0x64, 0x7E, 0x00, // 0x7E6403E5200345 + 0x45, 0x03, 0x40, 0xE5, 0x03, 0x68, 0x7E, 0x00, // 0x7E6803E5400345 + 0x45, 0x03, 0x60, 0xE5, 0x03, 0x6C, 0x7E, 0x00, // 0x7E6C03E5600345 + 0x45, 0x03, 0x80, 0xE5, 0x03, 0x70, 0x7E, 0x00, // 0x7E7003E5800345 + 0x45, 0x03, 0xA0, 0xE5, 0x03, 0x74, 0x7E, 0x00, // 0x7E7403E5A00345 + 0x45, 0x03, 0xC0, 0xE5, 0x03, 0x78, 0x7E, 0x00, // 0x7E7803E5C00345 + 0x45, 0x03, 0xE0, 0xE5, 0x03, 0x7C, 0x7E, 0x00, // 0x7E7C03E5E00345 + 0x45, 0x03, 0x00, 0xEC, 0x03, 0x80, 0x7E, 0x00, // 0x7E8003EC000345 + 0x45, 0x03, 0x20, 0xEC, 0x03, 0x84, 0x7E, 0x00, // 0x7E8403EC200345 + 0x45, 0x03, 0x40, 0xEC, 0x03, 0x88, 0x7E, 0x00, // 0x7E8803EC400345 + 0x45, 0x03, 0x60, 0xEC, 0x03, 0x8C, 0x7E, 0x00, // 0x7E8C03EC600345 + 0x45, 0x03, 0x80, 0xEC, 0x03, 0x90, 0x7E, 0x00, // 0x7E9003EC800345 + 0x45, 0x03, 0xA0, 0xEC, 0x03, 0x94, 0x7E, 0x00, // 0x7E9403ECA00345 + 0x45, 0x03, 0xC0, 0xEC, 0x03, 0x98, 0x7E, 0x00, // 0x7E9803ECC00345 + 0x45, 0x03, 0xE0, 0xEC, 0x03, 0x9C, 0x7E, 0x00, // 0x7E9C03ECE00345 + 0x45, 0x03, 0x00, 0xED, 0x03, 0xA0, 0x7E, 0x00, // 0x7EA003ED000345 + 0x45, 0x03, 0x20, 0xED, 0x03, 0xA4, 0x7E, 0x00, // 0x7EA403ED200345 + 0x45, 0x03, 0x40, 0xED, 0x03, 0xA8, 0x7E, 0x00, // 0x7EA803ED400345 + 0x45, 0x03, 0x60, 0xED, 0x03, 0xAC, 0x7E, 0x00, // 0x7EAC03ED600345 + 0x45, 0x03, 0x80, 0xED, 0x03, 0xB0, 0x7E, 0x00, // 0x7EB003ED800345 + 0x45, 0x03, 0xA0, 0xED, 0x03, 0xB4, 0x7E, 0x00, // 0x7EB403EDA00345 + 0x45, 0x03, 0xC0, 0xED, 0x03, 0xB8, 0x7E, 0x00, // 0x7EB803EDC00345 + 0x45, 0x03, 0xE0, 0xED, 0x03, 0xBC, 0x7E, 0x00, // 0x7EBC03EDE00345 + 0x06, 0x03, 0x20, 0x76, 0x00, 0xC0, 0x7E, 0x00, // 0x7EC00076200306 + 0x04, 0x03, 0x20, 0x76, 0x00, 0xC4, 0x7E, 0x00, // 0x7EC40076200304 + 0x45, 0x03, 0x00, 0xEE, 0x03, 0xC8, 0x7E, 0x00, // 0x7EC803EE000345 + 0x45, 0x03, 0x20, 0x76, 0x00, 0xCC, 0x7E, 0x00, // 0x7ECC0076200345 + 0x45, 0x03, 0x80, 0x75, 0x00, 0xD0, 0x7E, 0x00, // 0x7ED00075800345 + 0x42, 0x03, 0x20, 0x76, 0x00, 0xD8, 0x7E, 0x00, // 0x7ED80076200342 + 0x45, 0x03, 0xC0, 0xF6, 0x03, 0xDC, 0x7E, 0x00, // 0x7EDC03F6C00345 + 0x06, 0x03, 0x20, 0x72, 0x00, 0xE0, 0x7E, 0x00, // 0x7EE00072200306 + 0x04, 0x03, 0x20, 0x72, 0x00, 0xE4, 0x7E, 0x00, // 0x7EE40072200304 + 0x00, 0x03, 0x20, 0x72, 0x00, 0xE8, 0x7E, 0x00, // 0x7EE80072200300 + 0x00, 0x00, 0xC0, 0x70, 0x00, 0xEC, 0x7E, 0x00, // 0x7EEC0070C00000 + 0x45, 0x03, 0x20, 0x72, 0x00, 0xF0, 0x7E, 0x00, // 0x7EF00072200345 + 0x00, 0x00, 0x20, 0x77, 0x00, 0xF8, 0x7E, 0x00, // 0x7EF80077200000 + 0x42, 0x03, 0x00, 0x15, 0x00, 0x04, 0x7F, 0x00, // 0x7F040015000342 + 0x45, 0x03, 0x80, 0xEE, 0x03, 0x08, 0x7F, 0x00, // 0x7F0803EE800345 + 0x45, 0x03, 0xE0, 0x76, 0x00, 0x0C, 0x7F, 0x00, // 0x7F0C0076E00345 + 0x45, 0x03, 0xC0, 0x75, 0x00, 0x10, 0x7F, 0x00, // 0x7F100075C00345 + 0x42, 0x03, 0xE0, 0x76, 0x00, 0x18, 0x7F, 0x00, // 0x7F180076E00342 + 0x45, 0x03, 0xC0, 0xF8, 0x03, 0x1C, 0x7F, 0x00, // 0x7F1C03F8C00345 + 0x00, 0x03, 0xA0, 0x72, 0x00, 0x20, 0x7F, 0x00, // 0x7F200072A00300 + 0x00, 0x00, 0x00, 0x71, 0x00, 0x24, 0x7F, 0x00, // 0x7F240071000000 + 0x00, 0x03, 0xE0, 0x72, 0x00, 0x28, 0x7F, 0x00, // 0x7F280072E00300 + 0x00, 0x00, 0x20, 0x71, 0x00, 0x2C, 0x7F, 0x00, // 0x7F2C0071200000 + 0x45, 0x03, 0xE0, 0x72, 0x00, 0x30, 0x7F, 0x00, // 0x7F300072E00345 + 0x00, 0x03, 0xE0, 0xF7, 0x03, 0x34, 0x7F, 0x00, // 0x7F3403F7E00300 + 0x01, 0x03, 0xE0, 0xF7, 0x03, 0x38, 0x7F, 0x00, // 0x7F3803F7E00301 + 0x42, 0x03, 0xE0, 0xF7, 0x03, 0x3C, 0x7F, 0x00, // 0x7F3C03F7E00342 + 0x06, 0x03, 0x20, 0x77, 0x00, 0x40, 0x7F, 0x00, // 0x7F400077200306 + 0x04, 0x03, 0x20, 0x77, 0x00, 0x44, 0x7F, 0x00, // 0x7F440077200304 + 0x00, 0x03, 0x40, 0x79, 0x00, 0x48, 0x7F, 0x00, // 0x7F480079400300 + 0x00, 0x00, 0x00, 0x72, 0x00, 0x4C, 0x7F, 0x00, // 0x7F4C0072000000 + 0x42, 0x03, 0x20, 0x77, 0x00, 0x58, 0x7F, 0x00, // 0x7F580077200342 + 0x42, 0x03, 0x40, 0x79, 0x00, 0x5C, 0x7F, 0x00, // 0x7F5C0079400342 + 0x06, 0x03, 0x20, 0x73, 0x00, 0x60, 0x7F, 0x00, // 0x7F600073200306 + 0x04, 0x03, 0x20, 0x73, 0x00, 0x64, 0x7F, 0x00, // 0x7F640073200304 + 0x00, 0x03, 0x20, 0x73, 0x00, 0x68, 0x7F, 0x00, // 0x7F680073200300 + 0x00, 0x00, 0x40, 0x71, 0x00, 0x6C, 0x7F, 0x00, // 0x7F6C0071400000 + 0x00, 0x03, 0xC0, 0xFF, 0x03, 0x74, 0x7F, 0x00, // 0x7F7403FFC00300 + 0x01, 0x03, 0xC0, 0xFF, 0x03, 0x78, 0x7F, 0x00, // 0x7F7803FFC00301 + 0x42, 0x03, 0xC0, 0xFF, 0x03, 0x7C, 0x7F, 0x00, // 0x7F7C03FFC00342 + 0x06, 0x03, 0xA0, 0x78, 0x00, 0x80, 0x7F, 0x00, // 0x7F800078A00306 + 0x04, 0x03, 0xA0, 0x78, 0x00, 0x84, 0x7F, 0x00, // 0x7F840078A00304 + 0x00, 0x03, 0x60, 0x79, 0x00, 0x88, 0x7F, 0x00, // 0x7F880079600300 + 0x00, 0x00, 0x00, 0x76, 0x00, 0x8C, 0x7F, 0x00, // 0x7F8C0076000000 + 0x13, 0x03, 0x20, 0x78, 0x00, 0x90, 0x7F, 0x00, // 0x7F900078200313 + 0x14, 0x03, 0x20, 0x78, 0x00, 0x94, 0x7F, 0x00, // 0x7F940078200314 + 0x42, 0x03, 0xA0, 0x78, 0x00, 0x98, 0x7F, 0x00, // 0x7F980078A00342 + 0x42, 0x03, 0x60, 0x79, 0x00, 0x9C, 0x7F, 0x00, // 0x7F9C0079600342 + 0x06, 0x03, 0xA0, 0x74, 0x00, 0xA0, 0x7F, 0x00, // 0x7FA00074A00306 + 0x04, 0x03, 0xA0, 0x74, 0x00, 0xA4, 0x7F, 0x00, // 0x7FA40074A00304 + 0x00, 0x03, 0xA0, 0x74, 0x00, 0xA8, 0x7F, 0x00, // 0x7FA80074A00300 + 0x00, 0x00, 0xC0, 0x71, 0x00, 0xAC, 0x7F, 0x00, // 0x7FAC0071C00000 + 0x14, 0x03, 0x20, 0x74, 0x00, 0xB0, 0x7F, 0x00, // 0x7FB00074200314 + 0x00, 0x03, 0x00, 0x15, 0x00, 0xB4, 0x7F, 0x00, // 0x7FB40015000300 + 0x00, 0x00, 0xA0, 0x70, 0x00, 0xB8, 0x7F, 0x00, // 0x7FB80070A00000 + 0x00, 0x00, 0x00, 0x0C, 0x00, 0xBC, 0x7F, 0x00, // 0x7FBC000C000000 + 0x45, 0x03, 0x80, 0xEF, 0x03, 0xC8, 0x7F, 0x00, // 0x7FC803EF800345 + 0x45, 0x03, 0x20, 0x79, 0x00, 0xCC, 0x7F, 0x00, // 0x7FCC0079200345 + 0x45, 0x03, 0xC0, 0x79, 0x00, 0xD0, 0x7F, 0x00, // 0x7FD00079C00345 + 0x42, 0x03, 0x20, 0x79, 0x00, 0xD8, 0x7F, 0x00, // 0x7FD80079200342 + 0x45, 0x03, 0xC0, 0xFE, 0x03, 0xDC, 0x7F, 0x00, // 0x7FDC03FEC00345 + 0x00, 0x03, 0xE0, 0x73, 0x00, 0xE0, 0x7F, 0x00, // 0x7FE00073E00300 + 0x00, 0x00, 0x80, 0x71, 0x00, 0xE4, 0x7F, 0x00, // 0x7FE40071800000 + 0x00, 0x03, 0x20, 0x75, 0x00, 0xE8, 0x7F, 0x00, // 0x7FE80075200300 + 0x00, 0x00, 0xE0, 0x71, 0x00, 0xEC, 0x7F, 0x00, // 0x7FEC0071E00000 + 0x45, 0x03, 0x20, 0x75, 0x00, 0xF0, 0x7F, 0x00, // 0x7FF00075200345 + 0x00, 0x00, 0x80, 0x16, 0x00, 0xF4, 0x7F, 0x00, // 0x7FF40016800000 + 0x00, 0x00, 0x40, 0x00, 0x04, 0x00, 0x80, 0x00, // 0x80000400400000 + 0x00, 0x00, 0x60, 0x00, 0x04, 0x04, 0x80, 0x00, // 0x80040400600000 + 0x00, 0x00, 0x20, 0x75, 0x00, 0x98, 0x84, 0x00, // 0x84980075200000 + 0x00, 0x00, 0x60, 0x09, 0x00, 0xA8, 0x84, 0x00, // 0x84A80009600000 + 0x00, 0x00, 0xA0, 0x18, 0x00, 0xAC, 0x84, 0x00, // 0x84AC0018A00000 + 0x38, 0x03, 0x00, 0x32, 0x04, 0x68, 0x86, 0x00, // 0x86680432000338 + 0x38, 0x03, 0x40, 0x32, 0x04, 0x6C, 0x86, 0x00, // 0x866C0432400338 + 0x38, 0x03, 0x80, 0x32, 0x04, 0xB8, 0x86, 0x00, // 0x86B80432800338 + 0x38, 0x03, 0x00, 0x3A, 0x04, 0x34, 0x87, 0x00, // 0x8734043A000338 + 0x38, 0x03, 0x80, 0x3A, 0x04, 0x38, 0x87, 0x00, // 0x8738043A800338 + 0x38, 0x03, 0x40, 0x3A, 0x04, 0x3C, 0x87, 0x00, // 0x873C043A400338 + 0x38, 0x03, 0x60, 0x40, 0x04, 0x10, 0x88, 0x00, // 0x88100440600338 + 0x38, 0x03, 0x00, 0x41, 0x04, 0x24, 0x88, 0x00, // 0x88240441000338 + 0x38, 0x03, 0x60, 0x41, 0x04, 0x30, 0x88, 0x00, // 0x88300441600338 + 0x38, 0x03, 0x60, 0x44, 0x04, 0x90, 0x88, 0x00, // 0x88900444600338 + 0x38, 0x03, 0xA0, 0x44, 0x04, 0x98, 0x88, 0x00, // 0x88980444A00338 + 0x38, 0x03, 0x80, 0x47, 0x04, 0x04, 0x89, 0x00, // 0x89040447800338 + 0x38, 0x03, 0x60, 0x48, 0x04, 0x10, 0x89, 0x00, // 0x89100448600338 + 0x38, 0x03, 0xA0, 0x48, 0x04, 0x1C, 0x89, 0x00, // 0x891C0448A00338 + 0x38, 0x03, 0x00, 0x49, 0x04, 0x24, 0x89, 0x00, // 0x89240449000338 + 0x38, 0x03, 0xA0, 0x07, 0x00, 0x80, 0x89, 0x00, // 0x89800007A00338 + 0x38, 0x03, 0x20, 0x4C, 0x04, 0x88, 0x89, 0x00, // 0x8988044C200338 + 0x38, 0x03, 0xA0, 0x49, 0x04, 0xB4, 0x89, 0x00, // 0x89B40449A00338 + 0x38, 0x03, 0x80, 0x07, 0x00, 0xB8, 0x89, 0x00, // 0x89B80007800338 + 0x38, 0x03, 0xC0, 0x07, 0x00, 0xBC, 0x89, 0x00, // 0x89BC0007C00338 + 0x38, 0x03, 0x80, 0x4C, 0x04, 0xC0, 0x89, 0x00, // 0x89C0044C800338 + 0x38, 0x03, 0xA0, 0x4C, 0x04, 0xC4, 0x89, 0x00, // 0x89C4044CA00338 + 0x38, 0x03, 0x40, 0x4E, 0x04, 0xD0, 0x89, 0x00, // 0x89D0044E400338 + 0x38, 0x03, 0x60, 0x4E, 0x04, 0xD4, 0x89, 0x00, // 0x89D4044E600338 + 0x38, 0x03, 0xC0, 0x4E, 0x04, 0xE0, 0x89, 0x00, // 0x89E0044EC00338 + 0x38, 0x03, 0xE0, 0x4E, 0x04, 0xE4, 0x89, 0x00, // 0x89E4044EE00338 + 0x38, 0x03, 0x40, 0x4F, 0x04, 0x00, 0x8A, 0x00, // 0x8A00044F400338 + 0x38, 0x03, 0x60, 0x4F, 0x04, 0x04, 0x8A, 0x00, // 0x8A04044F600338 + 0x38, 0x03, 0x40, 0x50, 0x04, 0x10, 0x8A, 0x00, // 0x8A100450400338 + 0x38, 0x03, 0x60, 0x50, 0x04, 0x14, 0x8A, 0x00, // 0x8A140450600338 + 0x38, 0x03, 0xC0, 0x50, 0x04, 0x20, 0x8A, 0x00, // 0x8A200450C00338 + 0x38, 0x03, 0xE0, 0x50, 0x04, 0x24, 0x8A, 0x00, // 0x8A240450E00338 + 0x38, 0x03, 0x40, 0x54, 0x04, 0xB0, 0x8A, 0x00, // 0x8AB00454400338 + 0x38, 0x03, 0x00, 0x55, 0x04, 0xB4, 0x8A, 0x00, // 0x8AB40455000338 + 0x38, 0x03, 0x20, 0x55, 0x04, 0xB8, 0x8A, 0x00, // 0x8AB80455200338 + 0x38, 0x03, 0x60, 0x55, 0x04, 0xBC, 0x8A, 0x00, // 0x8ABC0455600338 + 0x38, 0x03, 0x80, 0x4F, 0x04, 0x80, 0x8B, 0x00, // 0x8B80044F800338 + 0x38, 0x03, 0xA0, 0x4F, 0x04, 0x84, 0x8B, 0x00, // 0x8B84044FA00338 + 0x38, 0x03, 0x20, 0x52, 0x04, 0x88, 0x8B, 0x00, // 0x8B880452200338 + 0x38, 0x03, 0x40, 0x52, 0x04, 0x8C, 0x8B, 0x00, // 0x8B8C0452400338 + 0x38, 0x03, 0x40, 0x56, 0x04, 0xA8, 0x8B, 0x00, // 0x8BA80456400338 + 0x38, 0x03, 0x60, 0x56, 0x04, 0xAC, 0x8B, 0x00, // 0x8BAC0456600338 + 0x38, 0x03, 0x80, 0x56, 0x04, 0xB0, 0x8B, 0x00, // 0x8BB00456800338 + 0x38, 0x03, 0xA0, 0x56, 0x04, 0xB4, 0x8B, 0x00, // 0x8BB40456A00338 + 0x00, 0x00, 0x00, 0x01, 0x06, 0xA4, 0x8C, 0x00, // 0x8CA40601000000 + 0x00, 0x00, 0x20, 0x01, 0x06, 0xA8, 0x8C, 0x00, // 0x8CA80601200000 + 0x38, 0x03, 0xA0, 0x5B, 0x05, 0x70, 0xAB, 0x00, // 0xAB70055BA00338 + 0x99, 0x30, 0x60, 0x09, 0x06, 0x30, 0xC1, 0x00, // 0xC1300609603099 + 0x99, 0x30, 0xA0, 0x09, 0x06, 0x38, 0xC1, 0x00, // 0xC1380609A03099 + 0x99, 0x30, 0xE0, 0x09, 0x06, 0x40, 0xC1, 0x00, // 0xC1400609E03099 + 0x99, 0x30, 0x20, 0x0A, 0x06, 0x48, 0xC1, 0x00, // 0xC148060A203099 + 0x99, 0x30, 0x60, 0x0A, 0x06, 0x50, 0xC1, 0x00, // 0xC150060A603099 + 0x99, 0x30, 0xA0, 0x0A, 0x06, 0x58, 0xC1, 0x00, // 0xC158060AA03099 + 0x99, 0x30, 0xE0, 0x0A, 0x06, 0x60, 0xC1, 0x00, // 0xC160060AE03099 + 0x99, 0x30, 0x20, 0x0B, 0x06, 0x68, 0xC1, 0x00, // 0xC168060B203099 + 0x99, 0x30, 0x60, 0x0B, 0x06, 0x70, 0xC1, 0x00, // 0xC170060B603099 + 0x99, 0x30, 0xA0, 0x0B, 0x06, 0x78, 0xC1, 0x00, // 0xC178060BA03099 + 0x99, 0x30, 0xE0, 0x0B, 0x06, 0x80, 0xC1, 0x00, // 0xC180060BE03099 + 0x99, 0x30, 0x20, 0x0C, 0x06, 0x88, 0xC1, 0x00, // 0xC188060C203099 + 0x99, 0x30, 0x80, 0x0C, 0x06, 0x94, 0xC1, 0x00, // 0xC194060C803099 + 0x99, 0x30, 0xC0, 0x0C, 0x06, 0x9C, 0xC1, 0x00, // 0xC19C060CC03099 + 0x99, 0x30, 0x00, 0x0D, 0x06, 0xA4, 0xC1, 0x00, // 0xC1A4060D003099 + 0x99, 0x30, 0xE0, 0x0D, 0x06, 0xC0, 0xC1, 0x00, // 0xC1C0060DE03099 + 0x9A, 0x30, 0xE0, 0x0D, 0x06, 0xC4, 0xC1, 0x00, // 0xC1C4060DE0309A + 0x99, 0x30, 0x40, 0x0E, 0x06, 0xCC, 0xC1, 0x00, // 0xC1CC060E403099 + 0x9A, 0x30, 0x40, 0x0E, 0x06, 0xD0, 0xC1, 0x00, // 0xC1D0060E40309A + 0x99, 0x30, 0xA0, 0x0E, 0x06, 0xD8, 0xC1, 0x00, // 0xC1D8060EA03099 + 0x9A, 0x30, 0xA0, 0x0E, 0x06, 0xDC, 0xC1, 0x00, // 0xC1DC060EA0309A + 0x99, 0x30, 0x00, 0x0F, 0x06, 0xE4, 0xC1, 0x00, // 0xC1E4060F003099 + 0x9A, 0x30, 0x00, 0x0F, 0x06, 0xE8, 0xC1, 0x00, // 0xC1E8060F00309A + 0x99, 0x30, 0x60, 0x0F, 0x06, 0xF0, 0xC1, 0x00, // 0xC1F0060F603099 + 0x9A, 0x30, 0x60, 0x0F, 0x06, 0xF4, 0xC1, 0x00, // 0xC1F4060F60309A + 0x99, 0x30, 0xC0, 0x08, 0x06, 0x50, 0xC2, 0x00, // 0xC2500608C03099 + 0x99, 0x30, 0xA0, 0x13, 0x06, 0x78, 0xC2, 0x00, // 0xC2780613A03099 + 0x99, 0x30, 0x60, 0x15, 0x06, 0xB0, 0xC2, 0x00, // 0xC2B00615603099 + 0x99, 0x30, 0xA0, 0x15, 0x06, 0xB8, 0xC2, 0x00, // 0xC2B80615A03099 + 0x99, 0x30, 0xE0, 0x15, 0x06, 0xC0, 0xC2, 0x00, // 0xC2C00615E03099 + 0x99, 0x30, 0x20, 0x16, 0x06, 0xC8, 0xC2, 0x00, // 0xC2C80616203099 + 0x99, 0x30, 0x60, 0x16, 0x06, 0xD0, 0xC2, 0x00, // 0xC2D00616603099 + 0x99, 0x30, 0xA0, 0x16, 0x06, 0xD8, 0xC2, 0x00, // 0xC2D80616A03099 + 0x99, 0x30, 0xE0, 0x16, 0x06, 0xE0, 0xC2, 0x00, // 0xC2E00616E03099 + 0x99, 0x30, 0x20, 0x17, 0x06, 0xE8, 0xC2, 0x00, // 0xC2E80617203099 + 0x99, 0x30, 0x60, 0x17, 0x06, 0xF0, 0xC2, 0x00, // 0xC2F00617603099 + 0x99, 0x30, 0xA0, 0x17, 0x06, 0xF8, 0xC2, 0x00, // 0xC2F80617A03099 + 0x99, 0x30, 0xE0, 0x17, 0x06, 0x00, 0xC3, 0x00, // 0xC3000617E03099 + 0x99, 0x30, 0x20, 0x18, 0x06, 0x08, 0xC3, 0x00, // 0xC3080618203099 + 0x99, 0x30, 0x80, 0x18, 0x06, 0x14, 0xC3, 0x00, // 0xC3140618803099 + 0x99, 0x30, 0xC0, 0x18, 0x06, 0x1C, 0xC3, 0x00, // 0xC31C0618C03099 + 0x99, 0x30, 0x00, 0x19, 0x06, 0x24, 0xC3, 0x00, // 0xC3240619003099 + 0x99, 0x30, 0xE0, 0x19, 0x06, 0x40, 0xC3, 0x00, // 0xC3400619E03099 + 0x9A, 0x30, 0xE0, 0x19, 0x06, 0x44, 0xC3, 0x00, // 0xC3440619E0309A + 0x99, 0x30, 0x40, 0x1A, 0x06, 0x4C, 0xC3, 0x00, // 0xC34C061A403099 + 0x9A, 0x30, 0x40, 0x1A, 0x06, 0x50, 0xC3, 0x00, // 0xC350061A40309A + 0x99, 0x30, 0xA0, 0x1A, 0x06, 0x58, 0xC3, 0x00, // 0xC358061AA03099 + 0x9A, 0x30, 0xA0, 0x1A, 0x06, 0x5C, 0xC3, 0x00, // 0xC35C061AA0309A + 0x99, 0x30, 0x00, 0x1B, 0x06, 0x64, 0xC3, 0x00, // 0xC364061B003099 + 0x9A, 0x30, 0x00, 0x1B, 0x06, 0x68, 0xC3, 0x00, // 0xC368061B00309A + 0x99, 0x30, 0x60, 0x1B, 0x06, 0x70, 0xC3, 0x00, // 0xC370061B603099 + 0x9A, 0x30, 0x60, 0x1B, 0x06, 0x74, 0xC3, 0x00, // 0xC374061B60309A + 0x99, 0x30, 0xC0, 0x14, 0x06, 0xD0, 0xC3, 0x00, // 0xC3D00614C03099 + 0x99, 0x30, 0xE0, 0x1D, 0x06, 0xDC, 0xC3, 0x00, // 0xC3DC061DE03099 + 0x99, 0x30, 0x00, 0x1E, 0x06, 0xE0, 0xC3, 0x00, // 0xC3E0061E003099 + 0x99, 0x30, 0x20, 0x1E, 0x06, 0xE4, 0xC3, 0x00, // 0xC3E4061E203099 + 0x99, 0x30, 0x40, 0x1E, 0x06, 0xE8, 0xC3, 0x00, // 0xC3E8061E403099 + 0x99, 0x30, 0xA0, 0x1F, 0x06, 0xF8, 0xC3, 0x00, // 0xC3F8061FA03099 + 0x00, 0x00, 0x00, 0x89, 0x11, 0x00, 0xE4, 0x03, // 0x3E4001189000000 + 0x00, 0x00, 0x80, 0xDE, 0x0C, 0x04, 0xE4, 0x03, // 0x3E4040CDE800000 + 0x00, 0x00, 0x40, 0xD9, 0x11, 0x08, 0xE4, 0x03, // 0x3E40811D9400000 + 0x00, 0x00, 0x00, 0x99, 0x11, 0x0C, 0xE4, 0x03, // 0x3E40C1199000000 + 0x00, 0x00, 0x20, 0xDA, 0x0D, 0x10, 0xE4, 0x03, // 0x3E4100DDA200000 + 0x00, 0x00, 0x40, 0xC6, 0x09, 0x14, 0xE4, 0x03, // 0x3E41409C6400000 + 0x00, 0x00, 0xA0, 0x7C, 0x0A, 0x18, 0xE4, 0x03, // 0x3E4180A7CA00000 + 0x00, 0x00, 0x80, 0xF3, 0x13, 0x1C, 0xE4, 0x03, // 0x3E41C13F3800000 + 0x00, 0x00, 0x80, 0xF3, 0x13, 0x20, 0xE4, 0x03, // 0x3E42013F3800000 + 0x00, 0x00, 0x20, 0x2A, 0x0B, 0x24, 0xE4, 0x03, // 0x3E4240B2A200000 + 0x00, 0x00, 0x20, 0x3A, 0x12, 0x28, 0xE4, 0x03, // 0x3E428123A200000 + 0x00, 0x00, 0xE0, 0xB0, 0x0A, 0x2C, 0xE4, 0x03, // 0x3E42C0AB0E00000 + 0x00, 0x00, 0x00, 0x29, 0x0B, 0x30, 0xE4, 0x03, // 0x3E4300B29000000 + 0x00, 0x00, 0xC0, 0x3E, 0x0C, 0x34, 0xE4, 0x03, // 0x3E4340C3EC00000 + 0x00, 0x00, 0x20, 0xCD, 0x0E, 0x38, 0xE4, 0x03, // 0x3E4380ECD200000 + 0x00, 0x00, 0xA0, 0xF0, 0x0F, 0x3C, 0xE4, 0x03, // 0x3E43C0FF0A00000 + 0x00, 0x00, 0xE0, 0xC7, 0x10, 0x40, 0xE4, 0x03, // 0x3E44010C7E00000 + 0x00, 0x00, 0x40, 0xF7, 0x10, 0x44, 0xE4, 0x03, // 0x3E44410F7400000 + 0x00, 0x00, 0x00, 0x1F, 0x11, 0x48, 0xE4, 0x03, // 0x3E448111F000000 + 0x00, 0x00, 0xE0, 0x11, 0x12, 0x4C, 0xE4, 0x03, // 0x3E44C1211E00000 + 0x00, 0x00, 0x40, 0x40, 0x0D, 0x50, 0xE4, 0x03, // 0x3E4500D40400000 + 0x00, 0x00, 0x60, 0xA3, 0x0D, 0x54, 0xE4, 0x03, // 0x3E4540DA3600000 + 0x00, 0x00, 0x20, 0x1B, 0x0E, 0x58, 0xE4, 0x03, // 0x3E4580E1B200000 + 0x00, 0x00, 0xC0, 0x7B, 0x0E, 0x5C, 0xE4, 0x03, // 0x3E45C0E7BC00000 + 0x00, 0x00, 0xA0, 0x87, 0x10, 0x60, 0xE4, 0x03, // 0x3E4601087A00000 + 0x00, 0x00, 0x40, 0x2D, 0x12, 0x64, 0xE4, 0x03, // 0x3E464122D400000 + 0x00, 0x00, 0x20, 0x3E, 0x13, 0x68, 0xE4, 0x03, // 0x3E468133E200000 + 0x00, 0x00, 0x40, 0xD0, 0x09, 0x6C, 0xE4, 0x03, // 0x3E46C09D0400000 + 0x00, 0x00, 0xA0, 0x6E, 0x0A, 0x70, 0xE4, 0x03, // 0x3E4700A6EA00000 + 0x00, 0x00, 0x80, 0x60, 0x0D, 0x74, 0xE4, 0x03, // 0x3E4740D60800000 + 0x00, 0x00, 0x60, 0x43, 0x0E, 0x78, 0xE4, 0x03, // 0x3E4780E43600000 + 0x00, 0x00, 0xA0, 0xC5, 0x10, 0x7C, 0xE4, 0x03, // 0x3E47C10C5A00000 + 0x00, 0x00, 0xC0, 0xC3, 0x13, 0x80, 0xE4, 0x03, // 0x3E48013C3C00000 + 0x00, 0x00, 0x00, 0xAA, 0x0B, 0x84, 0xE4, 0x03, // 0x3E4840BAA000000 + 0x00, 0x00, 0x60, 0xFD, 0x0D, 0x88, 0xE4, 0x03, // 0x3E4880DFD600000 + 0x00, 0x00, 0xA0, 0xB9, 0x10, 0x8C, 0xE4, 0x03, // 0x3E48C10B9A00000 + 0x00, 0x00, 0x80, 0x2C, 0x11, 0x90, 0xE4, 0x03, // 0x3E490112C800000 + 0x00, 0x00, 0x20, 0x59, 0x0C, 0x94, 0xE4, 0x03, // 0x3E4940C59200000 + 0x00, 0x00, 0x00, 0x3B, 0x10, 0x98, 0xE4, 0x03, // 0x3E498103B000000 + 0x00, 0x00, 0xE0, 0x03, 0x11, 0x9C, 0xE4, 0x03, // 0x3E49C1103E00000 + 0x00, 0x00, 0x40, 0xD9, 0x0B, 0xA0, 0xE4, 0x03, // 0x3E4A00BD9400000 + 0x00, 0x00, 0xE0, 0xE2, 0x0C, 0xA4, 0xE4, 0x03, // 0x3E4A40CE2E00000 + 0x00, 0x00, 0x40, 0xAD, 0x0D, 0xA8, 0xE4, 0x03, // 0x3E4A80DAD400000 + 0x00, 0x00, 0x80, 0x5F, 0x0E, 0xAC, 0xE4, 0x03, // 0x3E4AC0E5F800000 + 0x00, 0x00, 0xC0, 0x19, 0x12, 0xB0, 0xE4, 0x03, // 0x3E4B01219C00000 + 0x00, 0x00, 0xC0, 0xF0, 0x09, 0xB4, 0xE4, 0x03, // 0x3E4B409F0C00000 + 0x00, 0x00, 0xE0, 0x36, 0x0A, 0xB8, 0xE4, 0x03, // 0x3E4B80A36E00000 + 0x00, 0x00, 0xC0, 0x5B, 0x0A, 0xBC, 0xE4, 0x03, // 0x3E4BC0A5BC00000 + 0x00, 0x00, 0x80, 0x98, 0x0C, 0xC0, 0xE4, 0x03, // 0x3E4C00C98800000 + 0x00, 0x00, 0x60, 0x5A, 0x0D, 0xC4, 0xE4, 0x03, // 0x3E4C40D5A600000 + 0x00, 0x00, 0x00, 0x42, 0x0E, 0xC8, 0xE4, 0x03, // 0x3E4C80E42000000 + 0x00, 0x00, 0xE0, 0xDC, 0x0E, 0xCC, 0xE4, 0x03, // 0x3E4CC0EDCE00000 + 0x00, 0x00, 0x20, 0x00, 0x10, 0xD0, 0xE4, 0x03, // 0x3E4D01000200000 + 0x00, 0x00, 0xC0, 0xC0, 0x10, 0xD4, 0xE4, 0x03, // 0x3E4D410C0C00000 + 0x00, 0x00, 0x80, 0xCB, 0x10, 0xD8, 0xE4, 0x03, // 0x3E4D810CB800000 + 0x00, 0x00, 0xE0, 0xBD, 0x11, 0xDC, 0xE4, 0x03, // 0x3E4DC11BDE00000 + 0x00, 0x00, 0x40, 0xE6, 0x12, 0xE0, 0xE4, 0x03, // 0x3E4E012E6400000 + 0x00, 0x00, 0xE0, 0x6D, 0x13, 0xE4, 0xE4, 0x03, // 0x3E4E4136DE00000 + 0x00, 0x00, 0x40, 0xBF, 0x13, 0xE8, 0xE4, 0x03, // 0x3E4E813BF400000 + 0x00, 0x00, 0x80, 0x11, 0x0F, 0xEC, 0xE4, 0x03, // 0x3E4EC0F11800000 + 0x00, 0x00, 0xE0, 0x2F, 0x0F, 0xF0, 0xE4, 0x03, // 0x3E4F00F2FE00000 + 0x00, 0x00, 0x00, 0xB4, 0x0F, 0xF4, 0xE4, 0x03, // 0x3E4F40FB4000000 + 0x00, 0x00, 0x20, 0x79, 0x10, 0xF8, 0xE4, 0x03, // 0x3E4F81079200000 + 0x00, 0x00, 0x80, 0x60, 0x12, 0xFC, 0xE4, 0x03, // 0x3E4FC1260800000 + 0x00, 0x00, 0xE0, 0xCF, 0x13, 0x00, 0xE5, 0x03, // 0x3E50013CFE00000 + 0x00, 0x00, 0xC0, 0x5A, 0x11, 0x04, 0xE5, 0x03, // 0x3E504115AC00000 + 0x00, 0x00, 0xE0, 0x1B, 0x0B, 0x08, 0xE5, 0x03, // 0x3E5080B1BE00000 + 0x00, 0x00, 0x80, 0xE0, 0x0B, 0x0C, 0xE5, 0x03, // 0x3E50C0BE0800000 + 0x00, 0x00, 0x00, 0x8C, 0x0F, 0x10, 0xE5, 0x03, // 0x3E5100F8C000000 + 0x00, 0x00, 0xC0, 0x0F, 0x10, 0x14, 0xE5, 0x03, // 0x3E514100FC00000 + 0x00, 0x00, 0x40, 0x4C, 0x0E, 0x18, 0xE5, 0x03, // 0x3E5180E4C400000 + 0x00, 0x00, 0x40, 0x19, 0x0F, 0x1C, 0xE5, 0x03, // 0x3E51C0F19400000 + 0x00, 0x00, 0x40, 0x98, 0x11, 0x20, 0xE5, 0x03, // 0x3E5201198400000 + 0x00, 0x00, 0xE0, 0xDE, 0x12, 0x24, 0xE5, 0x03, // 0x3E52412DEE00000 + 0x00, 0x00, 0x00, 0x1B, 0x0B, 0x28, 0xE5, 0x03, // 0x3E5280B1B000000 + 0x00, 0x00, 0x40, 0x8C, 0x0B, 0x2C, 0xE5, 0x03, // 0x3E52C0B8C400000 + 0x00, 0x00, 0x60, 0x42, 0x0D, 0x30, 0xE5, 0x03, // 0x3E5300D42600000 + 0x00, 0x00, 0x40, 0xBB, 0x0D, 0x34, 0xE5, 0x03, // 0x3E5340DBB400000 + 0x00, 0x00, 0xE0, 0xE1, 0x0D, 0x38, 0xE5, 0x03, // 0x3E5380DE1E00000 + 0x00, 0x00, 0xE0, 0xA5, 0x0F, 0x3C, 0xE5, 0x03, // 0x3E53C0FA5E00000 + 0x00, 0x00, 0xE0, 0xC6, 0x0F, 0x40, 0xE5, 0x03, // 0x3E5400FC6E00000 + 0x00, 0x00, 0x60, 0xC9, 0x12, 0x44, 0xE5, 0x03, // 0x3E54412C9600000 + 0x00, 0x00, 0x40, 0x5A, 0x0A, 0x48, 0xE5, 0x03, // 0x3E5480A5A400000 + 0x00, 0x00, 0x60, 0x11, 0x10, 0x4C, 0xE5, 0x03, // 0x3E54C1011600000 + 0x00, 0x00, 0x80, 0x3B, 0x0A, 0x50, 0xE5, 0x03, // 0x3E5500A3B800000 + 0x00, 0x00, 0x80, 0x39, 0x0A, 0x54, 0xE5, 0x03, // 0x3E5540A39800000 + 0x00, 0x00, 0x80, 0x43, 0x0F, 0x58, 0xE5, 0x03, // 0x3E5580F43800000 + 0x00, 0x00, 0xC0, 0xB7, 0x0F, 0x5C, 0xE5, 0x03, // 0x3E55C0FB7C00000 + 0x00, 0x00, 0x20, 0x7E, 0x10, 0x60, 0xE5, 0x03, // 0x3E560107E200000 + 0x00, 0x00, 0xA0, 0xCE, 0x12, 0x64, 0xE5, 0x03, // 0x3E56412CEA00000 + 0x00, 0x00, 0x00, 0x70, 0x11, 0x68, 0xE5, 0x03, // 0x3E5681170000000 + 0x00, 0x00, 0xE0, 0x59, 0x0C, 0x6C, 0xE5, 0x03, // 0x3E56C0C59E00000 + 0x00, 0x00, 0x40, 0x40, 0x0D, 0x70, 0xE5, 0x03, // 0x3E5700D40400000 + 0x00, 0x00, 0xC0, 0x5F, 0x11, 0x74, 0xE5, 0x03, // 0x3E574115FC00000 + 0x00, 0x00, 0x20, 0xC7, 0x09, 0x78, 0xE5, 0x03, // 0x3E57809C7200000 + 0x00, 0x00, 0xE0, 0x7C, 0x0B, 0x7C, 0xE5, 0x03, // 0x3E57C0B7CE00000 + 0x00, 0x00, 0x40, 0x02, 0x0C, 0x80, 0xE5, 0x03, // 0x3E5800C02400000 + 0x00, 0x00, 0xE0, 0x70, 0x0E, 0x84, 0xE5, 0x03, // 0x3E5840E70E00000 + 0x00, 0x00, 0x00, 0xAE, 0x0E, 0x88, 0xE5, 0x03, // 0x3E5880EAE000000 + 0x00, 0x00, 0xE0, 0x62, 0x0A, 0x8C, 0xE5, 0x03, // 0x3E58C0A62E00000 + 0x00, 0x00, 0x60, 0x1F, 0x0F, 0x90, 0xE5, 0x03, // 0x3E5900F1F600000 + 0x00, 0x00, 0xE0, 0xF7, 0x09, 0x94, 0xE5, 0x03, // 0x3E59409F7E00000 + 0x00, 0x00, 0x20, 0xF5, 0x0B, 0x98, 0xE5, 0x03, // 0x3E5980BF5200000 + 0x00, 0x00, 0xA0, 0xC1, 0x09, 0x9C, 0xE5, 0x03, // 0x3E59C09C1A00000 + 0x00, 0x00, 0x80, 0x99, 0x0D, 0xA0, 0xE5, 0x03, // 0x3E5A00D99800000 + 0x00, 0x00, 0x00, 0xAF, 0x0C, 0xA4, 0xE5, 0x03, // 0x3E5A40CAF000000 + 0x00, 0x00, 0x40, 0xA4, 0x0F, 0xA8, 0xE5, 0x03, // 0x3E5A80FA4400000 + 0x00, 0x00, 0x60, 0x78, 0x0A, 0xAC, 0xE5, 0x03, // 0x3E5AC0A78600000 + 0x00, 0x00, 0xC0, 0x0B, 0x0B, 0xB0, 0xE5, 0x03, // 0x3E5B00B0BC00000 + 0x00, 0x00, 0x20, 0xE0, 0x0E, 0xB4, 0xE5, 0x03, // 0x3E5B40EE0200000 + 0x00, 0x00, 0x20, 0x89, 0x10, 0xB8, 0xE5, 0x03, // 0x3E5B81089200000 + 0x00, 0x00, 0x40, 0x55, 0x11, 0xBC, 0xE5, 0x03, // 0x3E5BC1155400000 + 0x00, 0x00, 0x40, 0x77, 0x0D, 0xC0, 0xE5, 0x03, // 0x3E5C00D77400000 + 0x00, 0x00, 0x00, 0xF6, 0x11, 0xC4, 0xE5, 0x03, // 0x3E5C411F6000000 + 0x00, 0x00, 0x00, 0x91, 0x0D, 0xC8, 0xE5, 0x03, // 0x3E5C80D91000000 + 0x00, 0x00, 0xC0, 0x5F, 0x0C, 0xCC, 0xE5, 0x03, // 0x3E5CC0C5FC00000 + 0x00, 0x00, 0xA0, 0x5C, 0x10, 0xD0, 0xE5, 0x03, // 0x3E5D0105CA00000 + 0x00, 0x00, 0x00, 0x74, 0x0C, 0xD4, 0xE5, 0x03, // 0x3E5D40C74000000 + 0x00, 0x00, 0xA0, 0xAC, 0x0E, 0xD8, 0xE5, 0x03, // 0x3E5D80EACA00000 + 0x00, 0x00, 0xC0, 0xD5, 0x09, 0xDC, 0xE5, 0x03, // 0x3E5DC09D5C00000 + 0x00, 0x00, 0x20, 0x2D, 0x0A, 0xE0, 0xE5, 0x03, // 0x3E5E00A2D200000 + 0x00, 0x00, 0x20, 0x39, 0x0A, 0xE4, 0xE5, 0x03, // 0x3E5E40A39200000 + 0x00, 0x00, 0x20, 0x10, 0x0D, 0xE8, 0xE5, 0x03, // 0x3E5E80D10200000 + 0x00, 0x00, 0xE0, 0x9C, 0x0F, 0xEC, 0xE5, 0x03, // 0x3E5EC0F9CE00000 + 0x00, 0x00, 0xE0, 0x4D, 0x10, 0xF0, 0xE5, 0x03, // 0x3E5F0104DE00000 + 0x00, 0x00, 0x40, 0x5A, 0x11, 0xF4, 0xE5, 0x03, // 0x3E5F4115A400000 + 0x00, 0x00, 0xE0, 0x39, 0x12, 0xF8, 0xE5, 0x03, // 0x3E5F81239E00000 + 0x00, 0x00, 0xA0, 0x5E, 0x0A, 0xFC, 0xE5, 0x03, // 0x3E5FC0A5EA00000 + 0x00, 0x00, 0x40, 0x88, 0x0A, 0x00, 0xE6, 0x03, // 0x3E6000A88400000 + 0x00, 0x00, 0x60, 0x2E, 0x0B, 0x04, 0xE6, 0x03, // 0x3E6040B2E600000 + 0x00, 0x00, 0x80, 0xDD, 0x0B, 0x08, 0xE6, 0x03, // 0x3E6080BDD800000 + 0x00, 0x00, 0xA0, 0xB8, 0x0C, 0x0C, 0xE6, 0x03, // 0x3E60C0CB8A00000 + 0x00, 0x00, 0xC0, 0xFF, 0x0D, 0x10, 0xE6, 0x03, // 0x3E6100DFFC00000 + 0x00, 0x00, 0x40, 0x25, 0x0F, 0x14, 0xE6, 0x03, // 0x3E6140F25400000 + 0x00, 0x00, 0xA0, 0xB5, 0x12, 0x18, 0xE6, 0x03, // 0x3E61812B5A00000 + 0x00, 0x00, 0x40, 0x4D, 0x13, 0x1C, 0xE6, 0x03, // 0x3E61C134D400000 + 0x00, 0x00, 0xE0, 0xD2, 0x13, 0x20, 0xE6, 0x03, // 0x3E62013D2E00000 + 0x00, 0x00, 0xC0, 0xD9, 0x13, 0x24, 0xE6, 0x03, // 0x3E62413D9C00000 + 0x00, 0x00, 0x60, 0x53, 0x0A, 0x28, 0xE6, 0x03, // 0x3E6280A53600000 + 0x00, 0x00, 0xC0, 0xD8, 0x0C, 0x2C, 0xE6, 0x03, // 0x3E62C0CD8C00000 + 0x00, 0x00, 0xE0, 0x6E, 0x0D, 0x30, 0xE6, 0x03, // 0x3E6300D6EE00000 + 0x00, 0x00, 0x40, 0xEC, 0x11, 0x34, 0xE6, 0x03, // 0x3E63411EC400000 + 0x00, 0x00, 0x80, 0xCE, 0x0B, 0x38, 0xE6, 0x03, // 0x3E6380BCE800000 + 0x00, 0x00, 0x00, 0x32, 0x0C, 0x3C, 0xE6, 0x03, // 0x3E63C0C32000000 + 0x00, 0x00, 0x00, 0x40, 0x0C, 0x40, 0xE6, 0x03, // 0x3E6400C40000000 + 0x00, 0x00, 0x40, 0x93, 0x0C, 0x44, 0xE6, 0x03, // 0x3E6440C93400000 + 0x00, 0x00, 0x60, 0xE4, 0x0D, 0x48, 0xE6, 0x03, // 0x3E6480DE4600000 + 0x00, 0x00, 0x20, 0x29, 0x0E, 0x4C, 0xE6, 0x03, // 0x3E64C0E29200000 + 0x00, 0x00, 0x20, 0x91, 0x0E, 0x50, 0xE6, 0x03, // 0x3E6500E91200000 + 0x00, 0x00, 0x40, 0x39, 0x0F, 0x54, 0xE6, 0x03, // 0x3E6540F39400000 + 0x00, 0x00, 0x80, 0xBE, 0x0F, 0x58, 0xE6, 0x03, // 0x3E6580FBE800000 + 0x00, 0x00, 0xE0, 0x0D, 0x10, 0x5C, 0xE6, 0x03, // 0x3E65C100DE00000 + 0x00, 0x00, 0xC0, 0xE4, 0x11, 0x60, 0xE6, 0x03, // 0x3E66011E4C00000 + 0x00, 0x00, 0xC0, 0x9D, 0x10, 0x64, 0xE6, 0x03, // 0x3E664109DC00000 + 0x00, 0x00, 0x60, 0x04, 0x12, 0x68, 0xE6, 0x03, // 0x3E6681204600000 + 0x00, 0x00, 0x40, 0x69, 0x12, 0x6C, 0xE6, 0x03, // 0x3E66C1269400000 + 0x00, 0x00, 0xE0, 0x42, 0x0A, 0x70, 0xE6, 0x03, // 0x3E6700A42E00000 + 0x00, 0x00, 0x60, 0x54, 0x0A, 0x74, 0xE6, 0x03, // 0x3E6740A54600000 + 0x00, 0x00, 0xA0, 0x97, 0x0A, 0x78, 0xE6, 0x03, // 0x3E6780A97A00000 + 0x00, 0x00, 0x00, 0x19, 0x0E, 0x7C, 0xE6, 0x03, // 0x3E67C0E19000000 + 0x00, 0x00, 0x40, 0x18, 0x11, 0x80, 0xE6, 0x03, // 0x3E6801118400000 + 0x00, 0x00, 0x40, 0x55, 0x11, 0x84, 0xE6, 0x03, // 0x3E6841155400000 + 0x00, 0x00, 0x20, 0xD9, 0x0B, 0x88, 0xE6, 0x03, // 0x3E6880BD9200000 + 0x00, 0x00, 0xA0, 0xFE, 0x0B, 0x8C, 0xE6, 0x03, // 0x3E68C0BFEA00000 + 0x00, 0x00, 0x60, 0x6F, 0x0C, 0x90, 0xE6, 0x03, // 0x3E6900C6F600000 + 0x00, 0x00, 0xC0, 0x75, 0x0D, 0x94, 0xE6, 0x03, // 0x3E6940D75C00000 + 0x00, 0x00, 0xC0, 0x87, 0x0F, 0x98, 0xE6, 0x03, // 0x3E6980F87C00000 + 0x00, 0x00, 0xA0, 0x6E, 0x0E, 0x9C, 0xE6, 0x03, // 0x3E69C0E6EA00000 + 0x00, 0x00, 0x80, 0xDC, 0x09, 0xA0, 0xE6, 0x03, // 0x3E6A009DC800000 + 0x00, 0x00, 0x20, 0xDF, 0x0A, 0xA4, 0xE6, 0x03, // 0x3E6A40ADF200000 + 0x00, 0x00, 0xE0, 0x7C, 0x0B, 0xA8, 0xE6, 0x03, // 0x3E6A80B7CE00000 + 0x00, 0x00, 0x40, 0xB7, 0x0B, 0xAC, 0xE6, 0x03, // 0x3E6AC0BB7400000 + 0x00, 0x00, 0x80, 0x03, 0x0C, 0xB0, 0xE6, 0x03, // 0x3E6B00C03800000 + 0x00, 0x00, 0x40, 0x76, 0x0E, 0xB4, 0xE6, 0x03, // 0x3E6B40E76400000 + 0x00, 0x00, 0x20, 0x8D, 0x0E, 0xB8, 0xE6, 0x03, // 0x3E6B80E8D200000 + 0x00, 0x00, 0x40, 0xF3, 0x0F, 0xBC, 0xE6, 0x03, // 0x3E6BC0FF3400000 + 0x00, 0x00, 0xC0, 0x08, 0x10, 0xC0, 0xE6, 0x03, // 0x3E6C01008C00000 + 0x00, 0x00, 0x80, 0x46, 0x12, 0xC4, 0xE6, 0x03, // 0x3E6C41246800000 + 0x00, 0x00, 0xC0, 0xDE, 0x12, 0xC8, 0xE6, 0x03, // 0x3E6C812DEC00000 + 0x00, 0x00, 0x00, 0xE9, 0x12, 0xCC, 0xE6, 0x03, // 0x3E6CC12E9000000 + 0x00, 0x00, 0x00, 0x03, 0x13, 0xD0, 0xE6, 0x03, // 0x3E6D01303000000 + 0x00, 0x00, 0x60, 0xF1, 0x09, 0xD4, 0xE6, 0x03, // 0x3E6D409F1600000 + 0x00, 0x00, 0xC0, 0x35, 0x0F, 0xD8, 0xE6, 0x03, // 0x3E6D80F35C00000 + 0x00, 0x00, 0x80, 0x36, 0x12, 0xDC, 0xE6, 0x03, // 0x3E6DC1236800000 + 0x00, 0x00, 0x00, 0xD7, 0x12, 0xE0, 0xE6, 0x03, // 0x3E6E012D7000000 + 0x00, 0x00, 0x20, 0x1C, 0x0C, 0xE4, 0xE6, 0x03, // 0x3E6E40C1C200000 + 0x00, 0x00, 0xC0, 0xD0, 0x09, 0xE8, 0xE6, 0x03, // 0x3E6E809D0C00000 + 0x00, 0x00, 0x40, 0x1B, 0x0A, 0xEC, 0xE6, 0x03, // 0x3E6EC0A1B400000 + 0x00, 0x00, 0xC0, 0x7D, 0x0B, 0xF0, 0xE6, 0x03, // 0x3E6F00B7DC00000 + 0x00, 0x00, 0xE0, 0x87, 0x0B, 0xF4, 0xE6, 0x03, // 0x3E6F40B87E00000 + 0x00, 0x00, 0x20, 0xB3, 0x0C, 0xF8, 0xE6, 0x03, // 0x3E6F80CB3200000 + 0x00, 0x00, 0x40, 0x40, 0x0D, 0xFC, 0xE6, 0x03, // 0x3E6FC0D40400000 + 0x00, 0x00, 0xC0, 0x39, 0x0E, 0x00, 0xE7, 0x03, // 0x3E7000E39C00000 + 0x00, 0x00, 0x40, 0xC8, 0x0E, 0x04, 0xE7, 0x03, // 0x3E7040EC8400000 + 0x00, 0x00, 0x80, 0x9F, 0x10, 0x08, 0xE7, 0x03, // 0x3E708109F800000 + 0x00, 0x00, 0x80, 0x0F, 0x12, 0x0C, 0xE7, 0x03, // 0x3E70C120F800000 + 0x00, 0x00, 0xA0, 0xF1, 0x13, 0x10, 0xE7, 0x03, // 0x3E71013F1A00000 + 0x00, 0x00, 0x00, 0xD1, 0x0C, 0x14, 0xE7, 0x03, // 0x3E7140CD1000000 + 0x00, 0x00, 0xC0, 0xC5, 0x12, 0x18, 0xE7, 0x03, // 0x3E71812C5C00000 + 0x00, 0x00, 0x20, 0x51, 0x0A, 0x1C, 0xE7, 0x03, // 0x3E71C0A51200000 + 0x00, 0x00, 0x60, 0xEF, 0x0C, 0x20, 0xE7, 0x03, // 0x3E7200CEF600000 + 0x00, 0x00, 0x60, 0xFE, 0x0C, 0x24, 0xE7, 0x03, // 0x3E7240CFE600000 + 0x00, 0x00, 0x20, 0xA8, 0x0D, 0x28, 0xE7, 0x03, // 0x3E7280DA8200000 + 0x00, 0x00, 0x80, 0xD3, 0x0D, 0x2C, 0xE7, 0x03, // 0x3E72C0DD3800000 + 0x00, 0x00, 0x20, 0x81, 0x0E, 0x30, 0xE7, 0x03, // 0x3E7300E81200000 + 0x00, 0x00, 0x20, 0xAB, 0x0E, 0x34, 0xE7, 0x03, // 0x3E7340EAB200000 + 0x00, 0x00, 0x60, 0x0D, 0x0F, 0x38, 0xE7, 0x03, // 0x3E7380F0D600000 + 0x00, 0x00, 0x00, 0xA2, 0x0F, 0x3C, 0xE7, 0x03, // 0x3E73C0FA2000000 + 0x00, 0x00, 0xC0, 0x0B, 0x13, 0x40, 0xE7, 0x03, // 0x3E740130BC00000 + 0x00, 0x00, 0xA0, 0x2D, 0x0A, 0x44, 0xE7, 0x03, // 0x3E7440A2DA00000 + 0x00, 0x00, 0xC0, 0x45, 0x0C, 0x48, 0xE7, 0x03, // 0x3E7480C45C00000 + 0x00, 0x00, 0x00, 0xCF, 0x12, 0x4C, 0xE7, 0x03, // 0x3E74C12CF000000 + 0x00, 0x00, 0x60, 0x05, 0x0A, 0x50, 0xE7, 0x03, // 0x3E7500A05600000 + 0x00, 0x00, 0x20, 0xA3, 0x0B, 0x54, 0xE7, 0x03, // 0x3E7540BA3200000 + 0x00, 0x00, 0x40, 0xBD, 0x0D, 0x58, 0xE7, 0x03, // 0x3E7580DBD400000 + 0x00, 0x00, 0x40, 0xE5, 0x11, 0x5C, 0xE7, 0x03, // 0x3E75C11E5400000 + 0x00, 0x00, 0x60, 0xF1, 0x0B, 0x60, 0xE7, 0x03, // 0x3E7600BF1600000 + 0x00, 0x00, 0x80, 0x28, 0x0C, 0x64, 0xE7, 0x03, // 0x3E7640C28800000 + 0x00, 0x00, 0xE0, 0x02, 0x0D, 0x68, 0xE7, 0x03, // 0x3E7680D02E00000 + 0x00, 0x00, 0xE0, 0x70, 0x0E, 0x6C, 0xE7, 0x03, // 0x3E76C0E70E00000 + 0x00, 0x00, 0xC0, 0xD0, 0x12, 0x70, 0xE7, 0x03, // 0x3E77012D0C00000 + 0x00, 0x00, 0x20, 0x45, 0x0A, 0x74, 0xE7, 0x03, // 0x3E7740A45200000 + 0x00, 0x00, 0xE0, 0x81, 0x0A, 0x78, 0xE7, 0x03, // 0x3E7780A81E00000 + 0x00, 0x00, 0xA0, 0x8C, 0x0B, 0x7C, 0xE7, 0x03, // 0x3E77C0B8CA00000 + 0x00, 0x00, 0x60, 0xC2, 0x0C, 0x80, 0xE7, 0x03, // 0x3E7800CC2600000 + 0x00, 0x00, 0xC0, 0xE9, 0x0C, 0x84, 0xE7, 0x03, // 0x3E7840CE9C00000 + 0x00, 0x00, 0x00, 0x15, 0x0D, 0x88, 0xE7, 0x03, // 0x3E7880D15000000 + 0x00, 0x00, 0xA0, 0x9C, 0x0D, 0x8C, 0xE7, 0x03, // 0x3E78C0D9CA00000 + 0x00, 0x00, 0xC0, 0x80, 0x0E, 0x90, 0xE7, 0x03, // 0x3E7900E80C00000 + 0x00, 0x00, 0x40, 0xBC, 0x0E, 0x94, 0xE7, 0x03, // 0x3E7940EBC400000 + 0x00, 0x00, 0x20, 0xEF, 0x0F, 0x98, 0xE7, 0x03, // 0x3E7980FEF200000 + 0x00, 0x00, 0xE0, 0x19, 0x11, 0x9C, 0xE7, 0x03, // 0x3E79C1119E00000 + 0x00, 0x00, 0x20, 0x1C, 0x11, 0xA0, 0xE7, 0x03, // 0x3E7A0111C200000 + 0x00, 0x00, 0x80, 0x39, 0x12, 0xA4, 0xE7, 0x03, // 0x3E7A41239800000 + 0x00, 0x00, 0x40, 0xDC, 0x12, 0xA8, 0xE7, 0x03, // 0x3E7A812DC400000 + 0x00, 0x00, 0xE0, 0x67, 0x0A, 0xAC, 0xE7, 0x03, // 0x3E7AC0A67E00000 + 0x00, 0x00, 0x40, 0xD7, 0x0D, 0xB0, 0xE7, 0x03, // 0x3E7B00DD7400000 + 0x00, 0x00, 0xA0, 0x83, 0x0A, 0xB4, 0xE7, 0x03, // 0x3E7B40A83A00000 + 0x00, 0x00, 0x00, 0x3A, 0x0E, 0xB8, 0xE7, 0x03, // 0x3E7B80E3A000000 + 0x00, 0x00, 0x00, 0x93, 0x0E, 0xBC, 0xE7, 0x03, // 0x3E7BC0E93000000 + 0x00, 0x00, 0x40, 0xBF, 0x10, 0xC0, 0xE7, 0x03, // 0x3E7C010BF400000 + 0x00, 0x00, 0x60, 0xD4, 0x12, 0xC4, 0xE7, 0x03, // 0x3E7C412D4600000 + 0x00, 0x00, 0xE0, 0x8A, 0x13, 0xC8, 0xE7, 0x03, // 0x3E7C8138AE00000 + 0x00, 0x00, 0xE0, 0xD3, 0x13, 0xCC, 0xE7, 0x03, // 0x3E7CC13D3E00000 + 0x00, 0x00, 0xE0, 0xF2, 0x0C, 0xD0, 0xE7, 0x03, // 0x3E7D00CF2E00000 + 0x00, 0x00, 0x60, 0xB9, 0x0D, 0xD4, 0xE7, 0x03, // 0x3E7D40DB9600000 + 0x00, 0x00, 0x00, 0x3D, 0x10, 0xD8, 0xE7, 0x03, // 0x3E7D8103D000000 + 0x00, 0x00, 0x60, 0x59, 0x0F, 0xDC, 0xE7, 0x03, // 0x3E7DC0F59600000 + 0x00, 0x00, 0x00, 0x64, 0x0F, 0xE0, 0xE7, 0x03, // 0x3E7E00F64000000 + 0x00, 0x00, 0x40, 0x92, 0x0F, 0xE4, 0xE7, 0x03, // 0x3E7E40F92400000 + 0x00, 0x00, 0x00, 0x58, 0x0E, 0xE8, 0xE7, 0x03, // 0x3E7E80E58000000 + 0x00, 0x00, 0x20, 0x13, 0x0E, 0xEC, 0xE7, 0x03, // 0x3E7EC0E13200000 + 0x00, 0x00, 0x00, 0x6B, 0x11, 0xF0, 0xE7, 0x03, // 0x3E7F0116B000000 + 0x00, 0x00, 0x00, 0xD8, 0x09, 0xF4, 0xE7, 0x03, // 0x3E7F409D8000000 + 0x00, 0x00, 0xC0, 0x66, 0x10, 0xF8, 0xE7, 0x03, // 0x3E7F81066C00000 + 0x00, 0x00, 0x40, 0x47, 0x0A, 0xFC, 0xE7, 0x03, // 0x3E7FC0A47400000 + 0x00, 0x00, 0xE0, 0x40, 0x0A, 0x00, 0xE8, 0x03, // 0x3E8000A40E00000 + 0x00, 0x00, 0xC0, 0xD4, 0x0B, 0x04, 0xE8, 0x03, // 0x3E8040BD4C00000 + 0x00, 0x00, 0x60, 0x5A, 0x0C, 0x08, 0xE8, 0x03, // 0x3E8080C5A600000 + 0x00, 0x00, 0xC0, 0x9A, 0x0F, 0x0C, 0xE8, 0x03, // 0x3E80C0F9AC00000 + 0x00, 0x00, 0xA0, 0x70, 0x0B, 0x10, 0xE8, 0x03, // 0x3E8100B70A00000 + 0x00, 0x00, 0xC0, 0xA3, 0x0D, 0x14, 0xE8, 0x03, // 0x3E8140DA3C00000 + 0x00, 0x00, 0x80, 0xD6, 0x0C, 0x18, 0xE8, 0x03, // 0x3E8180CD6800000 + 0x00, 0x00, 0x60, 0xE7, 0x11, 0x1C, 0xE8, 0x03, // 0x3E81C11E7600000 + 0x00, 0x00, 0x80, 0x09, 0x11, 0x20, 0xE8, 0x03, // 0x3E8201109800000 + 0x00, 0x00, 0xA0, 0xC9, 0x12, 0x24, 0xE8, 0x03, // 0x3E82412C9A00000 + 0x00, 0x00, 0x60, 0x31, 0x11, 0x28, 0xE8, 0x03, // 0x3E8281131600000 + 0x00, 0x00, 0x60, 0xDA, 0x0B, 0x2C, 0xE8, 0x03, // 0x3E82C0BDA600000 + 0x00, 0x00, 0x00, 0x28, 0x0A, 0x30, 0xE8, 0x03, // 0x3E8300A28000000 + 0x00, 0x00, 0x00, 0xB8, 0x0A, 0x34, 0xE8, 0x03, // 0x3E8340AB8000000 + 0x00, 0x00, 0x40, 0x0B, 0x0B, 0x40, 0xE8, 0x03, // 0x3E8400B0B400000 + 0x00, 0x00, 0x80, 0xCE, 0x0C, 0x48, 0xE8, 0x03, // 0x3E8480CCE800000 + 0x00, 0x00, 0xC0, 0x3B, 0x0A, 0x54, 0xE8, 0x03, // 0x3E8540A3BC00000 + 0x00, 0x00, 0x40, 0x65, 0x0E, 0x58, 0xE8, 0x03, // 0x3E8580E65400000 + 0x00, 0x00, 0x40, 0xD9, 0x0E, 0x5C, 0xE8, 0x03, // 0x3E85C0ED9400000 + 0x00, 0x00, 0x80, 0x27, 0x0F, 0x60, 0xE8, 0x03, // 0x3E8600F27800000 + 0x00, 0x00, 0xC0, 0x2B, 0x0F, 0x64, 0xE8, 0x03, // 0x3E8640F2BC00000 + 0x00, 0x00, 0xA0, 0x2C, 0x0F, 0x68, 0xE8, 0x03, // 0x3E8680F2CA00000 + 0x00, 0x00, 0xE0, 0x31, 0x0F, 0x6C, 0xE8, 0x03, // 0x3E86C0F31E00000 + 0x00, 0x00, 0xC0, 0xEA, 0x12, 0x70, 0xE8, 0x03, // 0x3E87012EAC00000 + 0x00, 0x00, 0xC0, 0x97, 0x0F, 0x74, 0xE8, 0x03, // 0x3E8740F97C00000 + 0x00, 0x00, 0xA0, 0xF7, 0x0F, 0x78, 0xE8, 0x03, // 0x3E8780FF7A00000 + 0x00, 0x00, 0x40, 0xC2, 0x10, 0x80, 0xE8, 0x03, // 0x3E88010C2400000 + 0x00, 0x00, 0x00, 0x5F, 0x11, 0x88, 0xE8, 0x03, // 0x3E888115F000000 + 0x00, 0x00, 0x00, 0x07, 0x12, 0x94, 0xE8, 0x03, // 0x3E8941207000000 + 0x00, 0x00, 0xA0, 0x1F, 0x12, 0x98, 0xE8, 0x03, // 0x3E898121FA00000 + 0x00, 0x00, 0xE0, 0x1D, 0x13, 0xA8, 0xE8, 0x03, // 0x3E8A8131DE00000 + 0x00, 0x00, 0x80, 0x1F, 0x13, 0xAC, 0xE8, 0x03, // 0x3E8AC131F800000 + 0x00, 0x00, 0x00, 0x25, 0x13, 0xB0, 0xE8, 0x03, // 0x3E8B01325000000 + 0x00, 0x00, 0x80, 0xB6, 0x13, 0xB4, 0xE8, 0x03, // 0x3E8B413B6800000 + 0x00, 0x00, 0xC0, 0x1B, 0x12, 0xB8, 0xE8, 0x03, // 0x3E8B8121BC00000 + 0x00, 0x00, 0xE0, 0xD6, 0x12, 0xBC, 0xE8, 0x03, // 0x3E8BC12D6E00000 + 0x00, 0x00, 0xC0, 0xF5, 0x09, 0xC0, 0xE8, 0x03, // 0x3E8C009F5C00000 + 0x00, 0x00, 0xE0, 0x1C, 0x0A, 0xC4, 0xE8, 0x03, // 0x3E8C40A1CE00000 + 0x00, 0x00, 0xA0, 0x29, 0x0A, 0xC8, 0xE8, 0x03, // 0x3E8C80A29A00000 + 0x00, 0x00, 0x20, 0x59, 0x0A, 0xCC, 0xE8, 0x03, // 0x3E8CC0A59200000 + 0x00, 0x00, 0x80, 0x5C, 0x0A, 0xD0, 0xE8, 0x03, // 0x3E8D00A5C800000 + 0x00, 0x00, 0x20, 0x6A, 0x0A, 0xD4, 0xE8, 0x03, // 0x3E8D40A6A200000 + 0x00, 0x00, 0xA0, 0xB3, 0x0A, 0xD8, 0xE8, 0x03, // 0x3E8D80AB3A00000 + 0x00, 0x00, 0xC0, 0xC0, 0x0A, 0xDC, 0xE8, 0x03, // 0x3E8DC0AC0C00000 + 0x00, 0x00, 0x00, 0xCD, 0x0A, 0xE0, 0xE8, 0x03, // 0x3E8E00ACD000000 + 0x00, 0x00, 0x00, 0x08, 0x0B, 0xE4, 0xE8, 0x03, // 0x3E8E40B08000000 + 0x00, 0x00, 0x00, 0x15, 0x0B, 0xE8, 0xE8, 0x03, // 0x3E8E80B15000000 + 0x00, 0x00, 0x80, 0x8C, 0x0B, 0xEC, 0xE8, 0x03, // 0x3E8EC0B8C800000 + 0x00, 0x00, 0xC0, 0x8D, 0x0B, 0xF0, 0xE8, 0x03, // 0x3E8F00B8DC00000 + 0x00, 0x00, 0x80, 0x12, 0x0C, 0xF4, 0xE8, 0x03, // 0x3E8F40C12800000 + 0x00, 0x00, 0x00, 0x2D, 0x0C, 0xF8, 0xE8, 0x03, // 0x3E8F80C2D000000 + 0x00, 0x00, 0xC0, 0x31, 0x0C, 0xFC, 0xE8, 0x03, // 0x3E8FC0C31C00000 + 0x00, 0x00, 0x40, 0x3E, 0x0C, 0x00, 0xE9, 0x03, // 0x3E9000C3E400000 + 0x00, 0x00, 0xE0, 0xA9, 0x0C, 0x04, 0xE9, 0x03, // 0x3E9040CA9E00000 + 0x00, 0x00, 0x40, 0xBC, 0x0C, 0x08, 0xE9, 0x03, // 0x3E9080CBC400000 + 0x00, 0x00, 0x20, 0xD2, 0x0C, 0x0C, 0xE9, 0x03, // 0x3E90C0CD2200000 + 0x00, 0x00, 0xA0, 0x10, 0x0D, 0x10, 0xE9, 0x03, // 0x3E9100D10A00000 + 0x00, 0x00, 0xE0, 0xAE, 0x0D, 0x14, 0xE9, 0x03, // 0x3E9140DAEE00000 + 0x00, 0x00, 0x40, 0xC3, 0x0D, 0x18, 0xE9, 0x03, // 0x3E9180DC3400000 + 0x00, 0x00, 0x40, 0xE4, 0x0D, 0x1C, 0xE9, 0x03, // 0x3E91C0DE4400000 + 0x00, 0x00, 0xC0, 0x2D, 0x0E, 0x20, 0xE9, 0x03, // 0x3E9200E2DC00000 + 0x00, 0x00, 0x60, 0x45, 0x0E, 0x24, 0xE9, 0x03, // 0x3E9240E45600000 + 0x00, 0x00, 0x40, 0x84, 0x0E, 0x28, 0xE9, 0x03, // 0x3E9280E84400000 + 0x00, 0x00, 0x20, 0x12, 0x0F, 0x2C, 0xE9, 0x03, // 0x3E92C0F12200000 + 0x00, 0x00, 0xC0, 0x27, 0x0F, 0x30, 0xE9, 0x03, // 0x3E9300F27C00000 + 0x00, 0x00, 0x20, 0x29, 0x0F, 0x34, 0xE9, 0x03, // 0x3E9340F29200000 + 0x00, 0x00, 0x00, 0x29, 0x0F, 0x38, 0xE9, 0x03, // 0x3E9380F29000000 + 0x00, 0x00, 0x00, 0x2A, 0x0F, 0x3C, 0xE9, 0x03, // 0x3E93C0F2A000000 + 0x00, 0x00, 0xC0, 0x2A, 0x0F, 0x40, 0xE9, 0x03, // 0x3E9400F2AC00000 + 0x00, 0x00, 0xA0, 0x2B, 0x0F, 0x44, 0xE9, 0x03, // 0x3E9440F2BA00000 + 0x00, 0x00, 0xA0, 0x31, 0x0F, 0x48, 0xE9, 0x03, // 0x3E9480F31A00000 + 0x00, 0x00, 0xC0, 0x31, 0x0F, 0x4C, 0xE9, 0x03, // 0x3E94C0F31C00000 + 0x00, 0x00, 0x00, 0x48, 0x0F, 0x50, 0xE9, 0x03, // 0x3E9500F48000000 + 0x00, 0x00, 0x20, 0x50, 0x0F, 0x54, 0xE9, 0x03, // 0x3E9540F50200000 + 0x00, 0x00, 0x00, 0x78, 0x0F, 0x58, 0xE9, 0x03, // 0x3E9580F78000000 + 0x00, 0x00, 0x80, 0xBE, 0x0F, 0x5C, 0xE9, 0x03, // 0x3E95C0FBE800000 + 0x00, 0x00, 0x20, 0xC1, 0x0F, 0x60, 0xE9, 0x03, // 0x3E9600FC1200000 + 0x00, 0x00, 0x20, 0xC8, 0x0F, 0x64, 0xE9, 0x03, // 0x3E9640FC8200000 + 0x00, 0x00, 0x40, 0xEE, 0x0F, 0x68, 0xE9, 0x03, // 0x3E9680FEE400000 + 0x00, 0x00, 0xA0, 0x00, 0x10, 0x6C, 0xE9, 0x03, // 0x3E96C1000A00000 + 0x00, 0x00, 0xA0, 0x3D, 0x10, 0x70, 0xE9, 0x03, // 0x3E970103DA00000 + 0x00, 0x00, 0x20, 0x4F, 0x10, 0x74, 0xE9, 0x03, // 0x3E974104F200000 + 0x00, 0x00, 0x20, 0x4F, 0x10, 0x78, 0xE9, 0x03, // 0x3E978104F200000 + 0x00, 0x00, 0xE0, 0x8A, 0x10, 0x7C, 0xE9, 0x03, // 0x3E97C108AE00000 + 0x00, 0x00, 0x00, 0x22, 0x11, 0x80, 0xE9, 0x03, // 0x3E9801122000000 + 0x00, 0x00, 0xC0, 0x32, 0x11, 0x84, 0xE9, 0x03, // 0x3E9841132C00000 + 0x00, 0x00, 0x20, 0x60, 0x11, 0x88, 0xE9, 0x03, // 0x3E9881160200000 + 0x00, 0x00, 0x20, 0x67, 0x11, 0x8C, 0xE9, 0x03, // 0x3E98C1167200000 + 0x00, 0x00, 0x60, 0x9A, 0x11, 0x90, 0xE9, 0x03, // 0x3E990119A600000 + 0x00, 0x00, 0x00, 0xA1, 0x11, 0x94, 0xE9, 0x03, // 0x3E99411A1000000 + 0x00, 0x00, 0xC0, 0xF6, 0x11, 0x98, 0xE9, 0x03, // 0x3E99811F6C00000 + 0x00, 0x00, 0x00, 0x07, 0x12, 0x9C, 0xE9, 0x03, // 0x3E99C1207000000 + 0x00, 0x00, 0x60, 0xDC, 0x12, 0xA0, 0xE9, 0x03, // 0x3E9A012DC600000 + 0x00, 0x00, 0xE0, 0xFF, 0x12, 0xA4, 0xE9, 0x03, // 0x3E9A412FFE00000 + 0x00, 0x00, 0x60, 0x07, 0x13, 0xA8, 0xE9, 0x03, // 0x3E9A81307600000 + 0x00, 0x00, 0xA0, 0x0E, 0x0C, 0xAC, 0xE9, 0x03, // 0x3E9AC0C0EA00000 + 0x00, 0x00, 0xC0, 0x5D, 0x48, 0xB0, 0xE9, 0x03, // 0x3E9B0485DC00000 + 0x00, 0x00, 0x00, 0x43, 0x10, 0xB4, 0xE9, 0x03, // 0x3E9B41043000000 + 0x00, 0x00, 0xC0, 0xC4, 0x09, 0xC0, 0xE9, 0x03, // 0x3E9C009C4C00000 + 0x00, 0x00, 0xA0, 0x36, 0x0A, 0xC4, 0xE9, 0x03, // 0x3E9C40A36A00000 + 0x00, 0x00, 0x00, 0x2D, 0x0A, 0xC8, 0xE9, 0x03, // 0x3E9C80A2D000000 + 0x00, 0x00, 0x00, 0xF0, 0x09, 0xCC, 0xE9, 0x03, // 0x3E9CC09F0000000 + 0x00, 0x00, 0xA0, 0x28, 0x0A, 0xD0, 0xE9, 0x03, // 0x3E9D00A28A00000 + 0x00, 0x00, 0x00, 0x30, 0x0A, 0xD4, 0xE9, 0x03, // 0x3E9D40A30000000 + 0x00, 0x00, 0xE0, 0x58, 0x0A, 0xD8, 0xE9, 0x03, // 0x3E9D80A58E00000 + 0x00, 0x00, 0x40, 0x5F, 0x0A, 0xDC, 0xE9, 0x03, // 0x3E9DC0A5F400000 + 0x00, 0x00, 0xA0, 0xB3, 0x0A, 0xE0, 0xE9, 0x03, // 0x3E9E00AB3A00000 + 0x00, 0x00, 0xA0, 0xAA, 0x0A, 0xE4, 0xE9, 0x03, // 0x3E9E40AAAA00000 + 0x00, 0x00, 0x20, 0xB3, 0x0A, 0xE8, 0xE9, 0x03, // 0x3E9E80AB3200000 + 0x00, 0x00, 0x40, 0xBC, 0x0A, 0xEC, 0xE9, 0x03, // 0x3E9EC0ABC400000 + 0x00, 0x00, 0x40, 0x0B, 0x0B, 0xF0, 0xE9, 0x03, // 0x3E9F00B0B400000 + 0x00, 0x00, 0x60, 0x16, 0x0B, 0xF4, 0xE9, 0x03, // 0x3E9F40B16600000 + 0x00, 0x00, 0x80, 0x28, 0x0B, 0xF8, 0xE9, 0x03, // 0x3E9F80B28800000 + 0x00, 0x00, 0x80, 0x2A, 0x0B, 0xFC, 0xE9, 0x03, // 0x3E9FC0B2A800000 + 0x00, 0x00, 0x40, 0x4C, 0x0B, 0x00, 0xEA, 0x03, // 0x3EA000B4C400000 + 0x00, 0x00, 0x00, 0x65, 0x0B, 0x04, 0xEA, 0x03, // 0x3EA040B65000000 + 0x00, 0x00, 0x40, 0xDA, 0x0B, 0x08, 0xEA, 0x03, // 0x3EA080BDA400000 + 0x00, 0x00, 0x20, 0xDB, 0x0B, 0x0C, 0xEA, 0x03, // 0x3EA0C0BDB200000 + 0x00, 0x00, 0x20, 0xED, 0x0B, 0x10, 0xEA, 0x03, // 0x3EA100BED200000 + 0x00, 0x00, 0xA0, 0xF5, 0x0B, 0x14, 0xEA, 0x03, // 0x3EA140BF5A00000 + 0x00, 0x00, 0x00, 0x1B, 0x0C, 0x18, 0xEA, 0x03, // 0x3EA180C1B000000 + 0x00, 0x00, 0xC0, 0x29, 0x0C, 0x1C, 0xEA, 0x03, // 0x3EA1C0C29C00000 + 0x00, 0x00, 0x00, 0x21, 0x0C, 0x20, 0xEA, 0x03, // 0x3EA200C21000000 + 0x00, 0x00, 0xC0, 0x31, 0x0C, 0x24, 0xEA, 0x03, // 0x3EA240C31C00000 + 0x00, 0x00, 0x00, 0x2C, 0x0C, 0x28, 0xEA, 0x03, // 0x3EA280C2C000000 + 0x00, 0x00, 0x40, 0x3E, 0x0C, 0x2C, 0xEA, 0x03, // 0x3EA2C0C3E400000 + 0x00, 0x00, 0x80, 0x46, 0x0C, 0x30, 0xEA, 0x03, // 0x3EA300C46800000 + 0x00, 0x00, 0x80, 0x78, 0x0C, 0x34, 0xEA, 0x03, // 0x3EA340C78800000 + 0x00, 0x00, 0x80, 0x83, 0x0C, 0x38, 0xEA, 0x03, // 0x3EA380C83800000 + 0x00, 0x00, 0x40, 0x8A, 0x0C, 0x3C, 0xEA, 0x03, // 0x3EA3C0C8A400000 + 0x00, 0x00, 0xC0, 0xAA, 0x0C, 0x40, 0xEA, 0x03, // 0x3EA400CAAC00000 + 0x00, 0x00, 0x80, 0xCE, 0x0C, 0x44, 0xEA, 0x03, // 0x3EA440CCE800000 + 0x00, 0x00, 0xE0, 0xE2, 0x0C, 0x48, 0xEA, 0x03, // 0x3EA480CE2E00000 + 0x00, 0x00, 0x60, 0xE3, 0x0C, 0x4C, 0xEA, 0x03, // 0x3EA4C0CE3600000 + 0x00, 0x00, 0xC0, 0xEA, 0x0C, 0x50, 0xEA, 0x03, // 0x3EA500CEAC00000 + 0x00, 0x00, 0x20, 0x6F, 0x0D, 0x54, 0xEA, 0x03, // 0x3EA540D6F200000 + 0x00, 0x00, 0x40, 0x77, 0x0D, 0x58, 0xEA, 0x03, // 0x3EA580D77400000 + 0x00, 0x00, 0x20, 0xA8, 0x0D, 0x5C, 0xEA, 0x03, // 0x3EA5C0DA8200000 + 0x00, 0x00, 0x60, 0xDB, 0x0D, 0x60, 0xEA, 0x03, // 0x3EA600DDB600000 + 0x00, 0x00, 0x60, 0xD9, 0x0D, 0x64, 0xEA, 0x03, // 0x3EA640DD9600000 + 0x00, 0x00, 0x40, 0xE4, 0x0D, 0x68, 0xEA, 0x03, // 0x3EA680DE4400000 + 0x00, 0x00, 0xC0, 0x03, 0x0E, 0x6C, 0xEA, 0x03, // 0x3EA6C0E03C00000 + 0x00, 0x00, 0xC0, 0x2D, 0x0E, 0x70, 0xEA, 0x03, // 0x3EA700E2DC00000 + 0x00, 0x00, 0xE0, 0xF4, 0x0E, 0x74, 0xEA, 0x03, // 0x3EA740EF4E00000 + 0x00, 0x00, 0xA0, 0x46, 0x0E, 0x78, 0xEA, 0x03, // 0x3EA780E46A00000 + 0x00, 0x00, 0xE0, 0x55, 0x0E, 0x7C, 0xEA, 0x03, // 0x3EA7C0E55E00000 + 0x00, 0x00, 0x40, 0x65, 0x0E, 0x80, 0xEA, 0x03, // 0x3EA800E65400000 + 0x00, 0x00, 0x20, 0x8E, 0x0E, 0x84, 0xEA, 0x03, // 0x3EA840E8E200000 + 0x00, 0x00, 0xC0, 0xA0, 0x0E, 0x88, 0xEA, 0x03, // 0x3EA880EA0C00000 + 0x00, 0x00, 0x60, 0xA7, 0x0E, 0x8C, 0xEA, 0x03, // 0x3EA8C0EA7600000 + 0x00, 0x00, 0xA0, 0xC3, 0x0E, 0x90, 0xEA, 0x03, // 0x3EA900EC3A00000 + 0x00, 0x00, 0xE0, 0xC3, 0x0E, 0x94, 0xEA, 0x03, // 0x3EA940EC3E00000 + 0x00, 0x00, 0x40, 0xD9, 0x0E, 0x98, 0xEA, 0x03, // 0x3EA980ED9400000 + 0x00, 0x00, 0x60, 0xDB, 0x0E, 0x9C, 0xEA, 0x03, // 0x3EA9C0EDB600000 + 0x00, 0x00, 0x80, 0xDE, 0x0E, 0xA0, 0xEA, 0x03, // 0x3EAA00EDE800000 + 0x00, 0x00, 0x40, 0xE9, 0x0E, 0xA4, 0xEA, 0x03, // 0x3EAA40EE9400000 + 0x00, 0x00, 0x00, 0xE8, 0x0E, 0xA8, 0xEA, 0x03, // 0x3EAA80EE8000000 + 0x00, 0x00, 0x80, 0x19, 0x0F, 0xAC, 0xEA, 0x03, // 0x3EAAC0F19800000 + 0x00, 0x00, 0x20, 0x56, 0x0F, 0xB0, 0xEA, 0x03, // 0x3EAB00F56200000 + 0x00, 0x00, 0x00, 0x78, 0x0F, 0xB4, 0xEA, 0x03, // 0x3EAB40F78000000 + 0x00, 0x00, 0x60, 0x8F, 0x0F, 0xB8, 0xEA, 0x03, // 0x3EAB80F8F600000 + 0x00, 0x00, 0x60, 0xAB, 0x0F, 0xBC, 0xEA, 0x03, // 0x3EABC0FAB600000 + 0x00, 0x00, 0x80, 0xBE, 0x0F, 0xC0, 0xEA, 0x03, // 0x3EAC00FBE800000 + 0x00, 0x00, 0xC0, 0xE7, 0x0F, 0xC4, 0xEA, 0x03, // 0x3EAC40FE7C00000 + 0x00, 0x00, 0xA0, 0x00, 0x10, 0xC8, 0xEA, 0x03, // 0x3EAC81000A00000 + 0x00, 0x00, 0x40, 0x6A, 0x10, 0xCC, 0xEA, 0x03, // 0x3EACC106A400000 + 0x00, 0x00, 0xE0, 0x7D, 0x10, 0xD0, 0xEA, 0x03, // 0x3EAD0107DE00000 + 0x00, 0x00, 0x20, 0xEF, 0x10, 0xD4, 0xEA, 0x03, // 0x3EAD410EF200000 + 0x00, 0x00, 0x20, 0x28, 0x11, 0xD8, 0xEA, 0x03, // 0x3EAD81128200000 + 0x00, 0x00, 0xC0, 0x30, 0x11, 0xDC, 0xEA, 0x03, // 0x3EADC1130C00000 + 0x00, 0x00, 0xC0, 0x32, 0x11, 0xE0, 0xEA, 0x03, // 0x3EAE01132C00000 + 0x00, 0x00, 0xE0, 0x57, 0x11, 0xE4, 0xEA, 0x03, // 0x3EAE41157E00000 + 0x00, 0x00, 0x00, 0x5F, 0x11, 0xE8, 0xEA, 0x03, // 0x3EAE8115F000000 + 0x00, 0x00, 0x60, 0x59, 0x11, 0xEC, 0xEA, 0x03, // 0x3EAEC1159600000 + 0x00, 0x00, 0x20, 0x60, 0x11, 0xF0, 0xEA, 0x03, // 0x3EAF01160200000 + 0x00, 0x00, 0xC0, 0x5F, 0x11, 0xF4, 0xEA, 0x03, // 0x3EAF4115FC00000 + 0x00, 0x00, 0xA0, 0x5D, 0x11, 0xF8, 0xEA, 0x03, // 0x3EAF8115DA00000 + 0x00, 0x00, 0x20, 0x67, 0x11, 0xFC, 0xEA, 0x03, // 0x3EAFC1167200000 + 0x00, 0x00, 0x40, 0x71, 0x11, 0x00, 0xEB, 0x03, // 0x3EB001171400000 + 0x00, 0x00, 0x00, 0xA1, 0x11, 0x04, 0xEB, 0x03, // 0x3EB0411A1000000 + 0x00, 0x00, 0x00, 0xE7, 0x11, 0x08, 0xEB, 0x03, // 0x3EB0811E7000000 + 0x00, 0x00, 0x40, 0x0E, 0x12, 0x0C, 0xEB, 0x03, // 0x3EB0C120E400000 + 0x00, 0x00, 0x20, 0x33, 0x12, 0x10, 0xEB, 0x03, // 0x3EB101233200000 + 0x00, 0x00, 0xC0, 0x4E, 0x12, 0x14, 0xEB, 0x03, // 0x3EB14124EC00000 + 0x00, 0x00, 0x80, 0xCF, 0x12, 0x18, 0xEB, 0x03, // 0x3EB1812CF800000 + 0x00, 0x00, 0x60, 0xDC, 0x12, 0x1C, 0xEB, 0x03, // 0x3EB1C12DC600000 + 0x00, 0x00, 0xC0, 0xEA, 0x12, 0x20, 0xEB, 0x03, // 0x3EB2012EAC00000 + 0x00, 0x00, 0x60, 0xFB, 0x12, 0x24, 0xEB, 0x03, // 0x3EB2412FB600000 + 0x00, 0x00, 0xE0, 0xFF, 0x12, 0x28, 0xEB, 0x03, // 0x3EB2812FFE00000 + 0x00, 0x00, 0x60, 0x01, 0x13, 0x2C, 0xEB, 0x03, // 0x3EB2C1301600000 + 0x00, 0x00, 0x60, 0x07, 0x13, 0x30, 0xEB, 0x03, // 0x3EB301307600000 + 0x00, 0x00, 0x40, 0x62, 0x13, 0x34, 0xEB, 0x03, // 0x3EB341362400000 + 0x00, 0x00, 0x80, 0xF3, 0x13, 0x38, 0xEB, 0x03, // 0x3EB3813F3800000 + 0x00, 0x00, 0x40, 0x09, 0x45, 0x3C, 0xEB, 0x03, // 0x3EB3C4509400000 + 0x00, 0x00, 0x80, 0x08, 0x45, 0x40, 0xEB, 0x03, // 0x3EB404508800000 + 0x00, 0x00, 0xA0, 0x7A, 0x46, 0x44, 0xEB, 0x03, // 0x3EB44467AA00000 + 0x00, 0x00, 0xA0, 0x73, 0x07, 0x48, 0xEB, 0x03, // 0x3EB480773A00000 + 0x00, 0x00, 0x00, 0x03, 0x08, 0x4C, 0xEB, 0x03, // 0x3EB4C0803000000 + 0x00, 0x00, 0x20, 0x07, 0x08, 0x50, 0xEB, 0x03, // 0x3EB500807200000 + 0x00, 0x00, 0x20, 0x49, 0x4A, 0x54, 0xEB, 0x03, // 0x3EB544A49200000 + 0x00, 0x00, 0x00, 0x9A, 0x4B, 0x58, 0xEB, 0x03, // 0x3EB584B9A000000 + 0x00, 0x00, 0x60, 0xDA, 0x4F, 0x5C, 0xEB, 0x03, // 0x3EB5C4FDA600000 + 0x00, 0x00, 0x60, 0xE8, 0x13, 0x60, 0xEB, 0x03, // 0x3EB6013E8600000 + 0x00, 0x00, 0xC0, 0xF1, 0x13, 0x64, 0xEB, 0x03, // 0x3EB6413F1C00000 + 0xB4, 0x05, 0x20, 0xBB, 0x00, 0x74, 0xEC, 0x03, // 0x3EC7400BB2005B4 + 0xB7, 0x05, 0x40, 0xBE, 0x00, 0x7C, 0xEC, 0x03, // 0x3EC7C00BE4005B7 + 0xC1, 0x05, 0x20, 0xBD, 0x00, 0xA8, 0xEC, 0x03, // 0x3ECA800BD2005C1 + 0xC2, 0x05, 0x20, 0xBD, 0x00, 0xAC, 0xEC, 0x03, // 0x3ECAC00BD2005C2 + 0xC1, 0x05, 0x20, 0x69, 0x1F, 0xB0, 0xEC, 0x03, // 0x3ECB01F692005C1 + 0xC2, 0x05, 0x20, 0x69, 0x1F, 0xB4, 0xEC, 0x03, // 0x3ECB41F692005C2 + 0xB7, 0x05, 0x00, 0xBA, 0x00, 0xB8, 0xEC, 0x03, // 0x3ECB800BA0005B7 + 0xB8, 0x05, 0x00, 0xBA, 0x00, 0xBC, 0xEC, 0x03, // 0x3ECBC00BA0005B8 + 0xBC, 0x05, 0x00, 0xBA, 0x00, 0xC0, 0xEC, 0x03, // 0x3ECC000BA0005BC + 0xBC, 0x05, 0x20, 0xBA, 0x00, 0xC4, 0xEC, 0x03, // 0x3ECC400BA2005BC + 0xBC, 0x05, 0x40, 0xBA, 0x00, 0xC8, 0xEC, 0x03, // 0x3ECC800BA4005BC + 0xBC, 0x05, 0x60, 0xBA, 0x00, 0xCC, 0xEC, 0x03, // 0x3ECCC00BA6005BC + 0xBC, 0x05, 0x80, 0xBA, 0x00, 0xD0, 0xEC, 0x03, // 0x3ECD000BA8005BC + 0xBC, 0x05, 0xA0, 0xBA, 0x00, 0xD4, 0xEC, 0x03, // 0x3ECD400BAA005BC + 0xBC, 0x05, 0xC0, 0xBA, 0x00, 0xD8, 0xEC, 0x03, // 0x3ECD800BAC005BC + 0xBC, 0x05, 0x00, 0xBB, 0x00, 0xE0, 0xEC, 0x03, // 0x3ECE000BB0005BC + 0xBC, 0x05, 0x20, 0xBB, 0x00, 0xE4, 0xEC, 0x03, // 0x3ECE400BB2005BC + 0xBC, 0x05, 0x40, 0xBB, 0x00, 0xE8, 0xEC, 0x03, // 0x3ECE800BB4005BC + 0xBC, 0x05, 0x60, 0xBB, 0x00, 0xEC, 0xEC, 0x03, // 0x3ECEC00BB6005BC + 0xBC, 0x05, 0x80, 0xBB, 0x00, 0xF0, 0xEC, 0x03, // 0x3ECF000BB8005BC + 0xBC, 0x05, 0xC0, 0xBB, 0x00, 0xF8, 0xEC, 0x03, // 0x3ECF800BBC005BC + 0xBC, 0x05, 0x00, 0xBC, 0x00, 0x00, 0xED, 0x03, // 0x3ED0000BC0005BC + 0xBC, 0x05, 0x20, 0xBC, 0x00, 0x04, 0xED, 0x03, // 0x3ED0400BC2005BC + 0xBC, 0x05, 0x60, 0xBC, 0x00, 0x0C, 0xED, 0x03, // 0x3ED0C00BC6005BC + 0xBC, 0x05, 0x80, 0xBC, 0x00, 0x10, 0xED, 0x03, // 0x3ED1000BC8005BC + 0xBC, 0x05, 0xC0, 0xBC, 0x00, 0x18, 0xED, 0x03, // 0x3ED1800BCC005BC + 0xBC, 0x05, 0xE0, 0xBC, 0x00, 0x1C, 0xED, 0x03, // 0x3ED1C00BCE005BC + 0xBC, 0x05, 0x00, 0xBD, 0x00, 0x20, 0xED, 0x03, // 0x3ED2000BD0005BC + 0xBC, 0x05, 0x20, 0xBD, 0x00, 0x24, 0xED, 0x03, // 0x3ED2400BD2005BC + 0xBC, 0x05, 0x40, 0xBD, 0x00, 0x28, 0xED, 0x03, // 0x3ED2800BD4005BC + 0xB9, 0x05, 0xA0, 0xBA, 0x00, 0x2C, 0xED, 0x03, // 0x3ED2C00BAA005B9 + 0xBF, 0x05, 0x20, 0xBA, 0x00, 0x30, 0xED, 0x03, // 0x3ED3000BA2005BF + 0xBF, 0x05, 0x60, 0xBB, 0x00, 0x34, 0xED, 0x03, // 0x3ED3400BB6005BF + 0xBF, 0x05, 0x80, 0xBC, 0x00, 0x38, 0xED, 0x03, // 0x3ED3800BC8005BF + 0x07, 0x03, 0x40, 0xBA, 0x20, 0x24, 0x17, 0x04, // 0x4172420BA400307 + 0x07, 0x03, 0x40, 0xBB, 0x20, 0x90, 0x17, 0x04, // 0x4179020BB400307 + 0xBA, 0x10, 0x21, 0x13, 0x22, 0x68, 0x42, 0x04, // 0x4426822132110BA + 0xBA, 0x10, 0x61, 0x13, 0x22, 0x70, 0x42, 0x04, // 0x4427022136110BA + 0xBA, 0x10, 0xA1, 0x14, 0x22, 0xAC, 0x42, 0x04, // 0x442AC2214A110BA + 0x27, 0x11, 0x21, 0x26, 0x22, 0xB8, 0x44, 0x04, // 0x444B82226211127 + 0x27, 0x11, 0x41, 0x26, 0x22, 0xBC, 0x44, 0x04, // 0x444BC2226411127 + 0x3E, 0x13, 0xE1, 0x68, 0x22, 0x2C, 0x4D, 0x04, // 0x44D2C2268E1133E + 0x57, 0x13, 0xE1, 0x68, 0x22, 0x30, 0x4D, 0x04, // 0x44D302268E11357 + 0xC9, 0x13, 0x41, 0x70, 0x22, 0x0C, 0x4E, 0x04, // 0x44E0C22704113C9 + 0xBB, 0x13, 0x81, 0x70, 0x22, 0x14, 0x4E, 0x04, // 0x44E1422708113BB + 0xC2, 0x13, 0x61, 0x71, 0x22, 0x38, 0x4E, 0x04, // 0x44E3822716113C2 + 0xC9, 0x13, 0x01, 0x72, 0x22, 0x44, 0x4E, 0x04, // 0x44E4422720113C9 + 0xC2, 0x13, 0x41, 0x78, 0x22, 0x14, 0x4F, 0x04, // 0x44F1422784113C2 + 0xB8, 0x13, 0x41, 0x78, 0x22, 0x1C, 0x4F, 0x04, // 0x44F1C22784113B8 + 0xC9, 0x13, 0x41, 0x78, 0x22, 0x20, 0x4F, 0x04, // 0x44F2022784113C9 + 0xBA, 0x14, 0x21, 0x97, 0x22, 0xEC, 0x52, 0x04, // 0x452EC22972114BA + 0xB0, 0x14, 0x21, 0x97, 0x22, 0xF0, 0x52, 0x04, // 0x452F022972114B0 + 0xBD, 0x14, 0x21, 0x97, 0x22, 0xF8, 0x52, 0x04, // 0x452F822972114BD + 0xAF, 0x15, 0x01, 0xB7, 0x22, 0xE8, 0x56, 0x04, // 0x456E822B70115AF + 0xAF, 0x15, 0x21, 0xB7, 0x22, 0xEC, 0x56, 0x04, // 0x456EC22B72115AF + 0x30, 0x19, 0xA1, 0x26, 0x23, 0xE0, 0x64, 0x04, // 0x464E02326A11930 + 0x1E, 0x61, 0xC1, 0x23, 0x2C, 0x84, 0x84, 0x05, // 0x584842C23C1611E + 0x29, 0x61, 0xC1, 0x23, 0x2C, 0x88, 0x84, 0x05, // 0x584882C23C16129 + 0x1F, 0x61, 0xC1, 0x23, 0x2C, 0x8C, 0x84, 0x05, // 0x5848C2C23C1611F + 0x1F, 0x61, 0x21, 0x25, 0x2C, 0x90, 0x84, 0x05, // 0x584902C2521611F + 0x20, 0x61, 0xC1, 0x23, 0x2C, 0x94, 0x84, 0x05, // 0x584942C23C16120 + 0x1F, 0x61, 0x21, 0x24, 0x2C, 0x98, 0x84, 0x05, // 0x584982C2421611F + 0x1F, 0x61, 0x41, 0x24, 0x2C, 0x9C, 0x84, 0x05, // 0x5849C2C2441611F + 0x20, 0x61, 0x21, 0x24, 0x2C, 0xA0, 0x84, 0x05, // 0x584A02C24216120 + 0x67, 0x6D, 0xE1, 0xAC, 0x2D, 0xA0, 0xB5, 0x05, // 0x5B5A02DACE16D67 + 0x67, 0x6D, 0x61, 0xAC, 0x2D, 0xA4, 0xB5, 0x05, // 0x5B5A42DAC616D67 + 0x67, 0x6D, 0x21, 0xAD, 0x2D, 0xA8, 0xB5, 0x05, // 0x5B5A82DAD216D67 + 0x65, 0xD1, 0xE1, 0x2A, 0x3A, 0x78, 0x45, 0x07, // 0x745783A2AE1D165 + 0x65, 0xD1, 0x01, 0x2B, 0x3A, 0x7C, 0x45, 0x07, // 0x7457C3A2B01D165 + 0x6E, 0xD1, 0xE1, 0x2B, 0x3A, 0x80, 0x45, 0x07, // 0x745803A2BE1D16E + 0x6F, 0xD1, 0xE1, 0x2B, 0x3A, 0x84, 0x45, 0x07, // 0x745843A2BE1D16F + 0x70, 0xD1, 0xE1, 0x2B, 0x3A, 0x88, 0x45, 0x07, // 0x745883A2BE1D170 + 0x71, 0xD1, 0xE1, 0x2B, 0x3A, 0x8C, 0x45, 0x07, // 0x7458C3A2BE1D171 + 0x72, 0xD1, 0xE1, 0x2B, 0x3A, 0x90, 0x45, 0x07, // 0x745903A2BE1D172 + 0x65, 0xD1, 0x21, 0x37, 0x3A, 0xEC, 0x46, 0x07, // 0x746EC3A3721D165 + 0x65, 0xD1, 0x41, 0x37, 0x3A, 0xF0, 0x46, 0x07, // 0x746F03A3741D165 + 0x6E, 0xD1, 0x61, 0x37, 0x3A, 0xF4, 0x46, 0x07, // 0x746F43A3761D16E + 0x6E, 0xD1, 0x81, 0x37, 0x3A, 0xF8, 0x46, 0x07, // 0x746F83A3781D16E + 0x6F, 0xD1, 0x61, 0x37, 0x3A, 0xFC, 0x46, 0x07, // 0x746FC3A3761D16F + 0x6F, 0xD1, 0x81, 0x37, 0x3A, 0x00, 0x47, 0x07, // 0x747003A3781D16F + 0x00, 0x00, 0xA0, 0xC7, 0x09, 0x00, 0xE0, 0x0B, // 0xBE00009C7A00000 + 0x00, 0x00, 0x00, 0xC7, 0x09, 0x04, 0xE0, 0x0B, // 0xBE00409C7000000 + 0x00, 0x00, 0x20, 0xC8, 0x09, 0x08, 0xE0, 0x0B, // 0xBE00809C8200000 + 0x00, 0x00, 0x40, 0x24, 0x40, 0x0C, 0xE0, 0x0B, // 0xBE00C4024400000 + 0x00, 0x00, 0x00, 0xEC, 0x09, 0x10, 0xE0, 0x0B, // 0xBE01009EC000000 + 0x00, 0x00, 0xC0, 0xF5, 0x09, 0x14, 0xE0, 0x0B, // 0xBE01409F5C00000 + 0x00, 0x00, 0x60, 0xF7, 0x09, 0x18, 0xE0, 0x0B, // 0xBE01809F7600000 + 0x00, 0x00, 0x40, 0x00, 0x0A, 0x1C, 0xE0, 0x0B, // 0xBE01C0A00400000 + 0x00, 0x00, 0x40, 0x0F, 0x0A, 0x20, 0xE0, 0x0B, // 0xBE0200A0F400000 + 0x00, 0x00, 0x20, 0x13, 0x0A, 0x24, 0xE0, 0x0B, // 0xBE0240A13200000 + 0x00, 0x00, 0xE0, 0x1C, 0x0A, 0x28, 0xE0, 0x0B, // 0xBE0280A1CE00000 + 0x00, 0x00, 0xE0, 0x19, 0x0A, 0x2C, 0xE0, 0x0B, // 0xBE02C0A19E00000 + 0x00, 0x00, 0xC0, 0x93, 0x06, 0x30, 0xE0, 0x0B, // 0xBE0300693C00000 + 0x00, 0x00, 0x40, 0xC7, 0x40, 0x34, 0xE0, 0x0B, // 0xBE03440C7400000 + 0x00, 0x00, 0xA0, 0x29, 0x0A, 0x38, 0xE0, 0x0B, // 0xBE0380A29A00000 + 0x00, 0x00, 0x80, 0x2A, 0x0A, 0x3C, 0xE0, 0x0B, // 0xBE03C0A2A800000 + 0x00, 0x00, 0x80, 0x2C, 0x0A, 0x40, 0xE0, 0x0B, // 0xBE0400A2C800000 + 0x00, 0x00, 0xE0, 0x2E, 0x0A, 0x44, 0xE0, 0x0B, // 0xBE0440A2EE00000 + 0x00, 0x00, 0x80, 0xA3, 0x40, 0x48, 0xE0, 0x0B, // 0xBE04840A3800000 + 0x00, 0x00, 0x20, 0x97, 0x06, 0x4C, 0xE0, 0x0B, // 0xBE04C0697200000 + 0x00, 0x00, 0xE0, 0x2C, 0x0A, 0x50, 0xE0, 0x0B, // 0xBE0500A2CE00000 + 0x00, 0x00, 0xA0, 0x31, 0x0A, 0x54, 0xE0, 0x0B, // 0xBE0540A31A00000 + 0x00, 0x00, 0x60, 0xA9, 0x40, 0x58, 0xE0, 0x0B, // 0xBE05840A9600000 + 0x00, 0x00, 0xE0, 0x32, 0x0A, 0x5C, 0xE0, 0x0B, // 0xBE05C0A32E00000 + 0x00, 0x00, 0x80, 0x34, 0x0A, 0x60, 0xE0, 0x0B, // 0xBE0600A34800000 + 0x00, 0x00, 0x80, 0xD9, 0x09, 0x64, 0xE0, 0x0B, // 0xBE06409D9800000 + 0x00, 0x00, 0x80, 0x35, 0x0A, 0x68, 0xE0, 0x0B, // 0xBE0680A35800000 + 0x00, 0x00, 0xA0, 0x36, 0x0A, 0x6C, 0xE0, 0x0B, // 0xBE06C0A36A00000 + 0x00, 0x00, 0xE0, 0x3B, 0x52, 0x70, 0xE0, 0x0B, // 0xBE070523BE00000 + 0x00, 0x00, 0xA0, 0x3E, 0x0A, 0x74, 0xE0, 0x0B, // 0xBE0740A3EA00000 + 0x00, 0x00, 0x60, 0x40, 0x0A, 0x78, 0xE0, 0x0B, // 0xBE0780A40600000 + 0x00, 0x00, 0xE0, 0x9B, 0x06, 0x7C, 0xE0, 0x0B, // 0xBE07C069BE00000 + 0x00, 0x00, 0x60, 0x47, 0x0A, 0x80, 0xE0, 0x0B, // 0xBE0800A47600000 + 0x00, 0x00, 0xC0, 0x48, 0x0A, 0x84, 0xE0, 0x0B, // 0xBE0840A48C00000 + 0x00, 0x00, 0x40, 0x4E, 0x0A, 0x88, 0xE0, 0x0B, // 0xBE0880A4E400000 + 0x00, 0x00, 0xE0, 0x4E, 0x0A, 0x8C, 0xE0, 0x0B, // 0xBE08C0A4EE00000 + 0x00, 0x00, 0xA0, 0xA2, 0x06, 0x90, 0xE0, 0x0B, // 0xBE09006A2A00000 + 0x00, 0x00, 0xE0, 0x58, 0x0A, 0x94, 0xE0, 0x0B, // 0xBE0940A58E00000 + 0x00, 0x00, 0x20, 0x59, 0x0A, 0x98, 0xE0, 0x0B, // 0xBE0980A59200000 + 0x00, 0x00, 0x80, 0x5C, 0x0A, 0x9C, 0xE0, 0x0B, // 0xBE09C0A5C800000 + 0x00, 0x00, 0x40, 0x5F, 0x0A, 0xA0, 0xE0, 0x0B, // 0xBE0A00A5F400000 + 0x00, 0x00, 0xA0, 0x60, 0x0A, 0xA4, 0xE0, 0x0B, // 0xBE0A40A60A00000 + 0x00, 0x00, 0xC0, 0x60, 0x0A, 0xA8, 0xE0, 0x0B, // 0xBE0A80A60C00000 + 0x00, 0x00, 0xE0, 0x62, 0x0A, 0xAC, 0xE0, 0x0B, // 0xBE0AC0A62E00000 + 0x00, 0x00, 0x20, 0x69, 0x0A, 0xB0, 0xE0, 0x0B, // 0xBE0B00A69200000 + 0x00, 0x00, 0x20, 0x6A, 0x0A, 0xB4, 0xE0, 0x0B, // 0xBE0B40A6A200000 + 0x00, 0x00, 0x40, 0x6B, 0x0A, 0xB8, 0xE0, 0x0B, // 0xBE0B80A6B400000 + 0x00, 0x00, 0x60, 0x6E, 0x0A, 0xBC, 0xE0, 0x0B, // 0xBE0BC0A6E600000 + 0x00, 0x00, 0xA0, 0x6F, 0x0A, 0xC0, 0xE0, 0x0B, // 0xBE0C00A6FA00000 + 0x00, 0x00, 0xE0, 0x6F, 0x0A, 0xC4, 0xE0, 0x0B, // 0xBE0C40A6FE00000 + 0x00, 0x00, 0xE0, 0x6F, 0x0A, 0xC8, 0xE0, 0x0B, // 0xBE0C80A6FE00000 + 0x00, 0x00, 0xE0, 0x6F, 0x0A, 0xCC, 0xE0, 0x0B, // 0xBE0CC0A6FE00000 + 0x00, 0x00, 0x80, 0x45, 0x41, 0xD0, 0xE0, 0x0B, // 0xBE0D04145800000 + 0x00, 0x00, 0x00, 0x0E, 0x0E, 0xD4, 0xE0, 0x0B, // 0xBE0D40E0E000000 + 0x00, 0x00, 0x40, 0x79, 0x0A, 0xD8, 0xE0, 0x0B, // 0xBE0D80A79400000 + 0x00, 0x00, 0xE0, 0x7B, 0x0A, 0xDC, 0xE0, 0x0B, // 0xBE0DC0A7BE00000 + 0x00, 0x00, 0x60, 0x6C, 0x41, 0xE0, 0xE0, 0x0B, // 0xBE0E0416C600000 + 0x00, 0x00, 0x60, 0x7D, 0x0A, 0xE4, 0xE0, 0x0B, // 0xBE0E40A7D600000 + 0x00, 0x00, 0x20, 0x7E, 0x0A, 0xE8, 0xE0, 0x0B, // 0xBE0E80A7E200000 + 0x00, 0x00, 0xC0, 0x80, 0x0A, 0xEC, 0xE0, 0x0B, // 0xBE0EC0A80C00000 + 0x00, 0x00, 0xC0, 0x93, 0x0A, 0xF0, 0xE0, 0x0B, // 0xBE0F00A93C00000 + 0x00, 0x00, 0x00, 0x87, 0x0A, 0xF4, 0xE0, 0x0B, // 0xBE0F40A87000000 + 0x00, 0x00, 0x00, 0x89, 0x0A, 0xF8, 0xE0, 0x0B, // 0xBE0F80A89000000 + 0x00, 0x00, 0x00, 0x8D, 0x0A, 0xFC, 0xE0, 0x0B, // 0xBE0FC0A8D000000 + 0x00, 0x00, 0x40, 0x94, 0x0A, 0x00, 0xE1, 0x0B, // 0xBE1000A94400000 + 0x00, 0x00, 0xC0, 0x9E, 0x0A, 0x04, 0xE1, 0x0B, // 0xBE1040A9EC00000 + 0x00, 0x00, 0x00, 0xA2, 0x0A, 0x08, 0xE1, 0x0B, // 0xBE1080AA2000000 + 0x00, 0x00, 0x60, 0xAA, 0x0A, 0x0C, 0xE1, 0x0B, // 0xBE10C0AAA600000 + 0x00, 0x00, 0x60, 0xAC, 0x0A, 0x10, 0xE1, 0x0B, // 0xBE1100AAC600000 + 0x00, 0x00, 0x80, 0xB0, 0x0A, 0x14, 0xE1, 0x0B, // 0xBE1140AB0800000 + 0x00, 0x00, 0x80, 0xB0, 0x0A, 0x18, 0xE1, 0x0B, // 0xBE1180AB0800000 + 0x00, 0x00, 0x20, 0xB3, 0x0A, 0x1C, 0xE1, 0x0B, // 0xBE11C0AB3200000 + 0x00, 0x00, 0x60, 0xB5, 0x0A, 0x20, 0xE1, 0x0B, // 0xBE1200AB5600000 + 0x00, 0x00, 0x60, 0xB6, 0x0A, 0x24, 0xE1, 0x0B, // 0xBE1240AB6600000 + 0x00, 0x00, 0x40, 0xB8, 0x0A, 0x28, 0xE1, 0x0B, // 0xBE1280AB8400000 + 0x00, 0x00, 0xC0, 0xE2, 0x0A, 0x2C, 0xE1, 0x0B, // 0xBE12C0AE2C00000 + 0x00, 0x00, 0xC0, 0xC0, 0x0A, 0x30, 0xE1, 0x0B, // 0xBE1300AC0C00000 + 0x00, 0x00, 0xE0, 0xE2, 0x0A, 0x34, 0xE1, 0x0B, // 0xBE1340AE2E00000 + 0x00, 0x00, 0x20, 0xCA, 0x0A, 0x38, 0xE1, 0x0B, // 0xBE1380ACA200000 + 0x00, 0x00, 0x80, 0xCE, 0x0A, 0x3C, 0xE1, 0x0B, // 0xBE13C0ACE800000 + 0x00, 0x00, 0xE0, 0x40, 0x0A, 0x40, 0xE1, 0x0B, // 0xBE1400A40E00000 + 0x00, 0x00, 0xC0, 0x1D, 0x0B, 0x44, 0xE1, 0x0B, // 0xBE1440B1DC00000 + 0x00, 0x00, 0xC0, 0xF9, 0x0A, 0x48, 0xE1, 0x0B, // 0xBE1480AF9C00000 + 0x00, 0x00, 0x80, 0xFE, 0x0A, 0x4C, 0xE1, 0x0B, // 0xBE14C0AFE800000 + 0x00, 0x00, 0xA0, 0x01, 0x0B, 0x50, 0xE1, 0x0B, // 0xBE1500B01A00000 + 0x00, 0x00, 0x60, 0xF1, 0x0A, 0x54, 0xE1, 0x0B, // 0xBE1540AF1600000 + 0x00, 0x00, 0x40, 0x06, 0x0B, 0x58, 0xE1, 0x0B, // 0xBE1580B06400000 + 0x00, 0x00, 0x20, 0x06, 0x0B, 0x5C, 0xE1, 0x0B, // 0xBE15C0B06200000 + 0x00, 0x00, 0x80, 0x15, 0x0B, 0x60, 0xE1, 0x0B, // 0xBE1600B15800000 + 0x00, 0x00, 0x80, 0x9C, 0x42, 0x64, 0xE1, 0x0B, // 0xBE164429C800000 + 0x00, 0x00, 0x40, 0x1E, 0x0B, 0x68, 0xE1, 0x0B, // 0xBE1680B1E400000 + 0x00, 0x00, 0xE0, 0x1E, 0x0B, 0x6C, 0xE1, 0x0B, // 0xBE16C0B1EE00000 + 0x00, 0x00, 0xC0, 0x20, 0x0B, 0x70, 0xE1, 0x0B, // 0xBE1700B20C00000 + 0x00, 0x00, 0x40, 0x23, 0x0B, 0x74, 0xE1, 0x0B, // 0xBE1740B23400000 + 0x00, 0x00, 0x40, 0x24, 0x0B, 0x78, 0xE1, 0x0B, // 0xBE1780B24400000 + 0x00, 0x00, 0x40, 0x2C, 0x0B, 0x7C, 0xE1, 0x0B, // 0xBE17C0B2C400000 + 0x00, 0x00, 0x00, 0xD5, 0x42, 0x80, 0xE1, 0x0B, // 0xBE18042D5000000 + 0x00, 0x00, 0x40, 0xDD, 0x42, 0x84, 0xE1, 0x0B, // 0xBE18442DD400000 + 0x00, 0x00, 0x80, 0x3D, 0x0B, 0x88, 0xE1, 0x0B, // 0xBE1880B3D800000 + 0x00, 0x00, 0x60, 0x43, 0x0B, 0x8C, 0xE1, 0x0B, // 0xBE18C0B43600000 + 0x00, 0x00, 0xE0, 0x44, 0x0B, 0x90, 0xE1, 0x0B, // 0xBE1900B44E00000 + 0x00, 0x00, 0x00, 0x3B, 0x0B, 0x94, 0xE1, 0x0B, // 0xBE1940B3B000000 + 0x00, 0x00, 0xC0, 0x4C, 0x0B, 0x98, 0xE1, 0x0B, // 0xBE1980B4CC00000 + 0x00, 0x00, 0xC0, 0xDD, 0x06, 0x9C, 0xE1, 0x0B, // 0xBE19C06DDC00000 + 0x00, 0x00, 0x80, 0xDF, 0x06, 0xA0, 0xE1, 0x0B, // 0xBE1A006DF800000 + 0x00, 0x00, 0x00, 0x61, 0x0B, 0xA4, 0xE1, 0x0B, // 0xBE1A40B61000000 + 0x00, 0x00, 0xC0, 0x67, 0x0B, 0xA8, 0xE1, 0x0B, // 0xBE1A80B67C00000 + 0x00, 0x00, 0xC0, 0x67, 0x0B, 0xAC, 0xE1, 0x0B, // 0xBE1AC0B67C00000 + 0x00, 0x00, 0x00, 0x39, 0x43, 0xB0, 0xE1, 0x0B, // 0xBE1B04339000000 + 0x00, 0x00, 0x60, 0x78, 0x0B, 0xB4, 0xE1, 0x0B, // 0xBE1B40B78600000 + 0x00, 0x00, 0x00, 0x7B, 0x0B, 0xB8, 0xE1, 0x0B, // 0xBE1B80B7B000000 + 0x00, 0x00, 0xE0, 0x7C, 0x0B, 0xBC, 0xE1, 0x0B, // 0xBE1BC0B7CE00000 + 0x00, 0x00, 0x60, 0x7E, 0x0B, 0xC0, 0xE1, 0x0B, // 0xBE1C00B7E600000 + 0x00, 0x00, 0x00, 0x63, 0x43, 0xC4, 0xE1, 0x0B, // 0xBE1C44363000000 + 0x00, 0x00, 0xE0, 0x7F, 0x0B, 0xC8, 0xE1, 0x0B, // 0xBE1C80B7FE00000 + 0x00, 0x00, 0xC0, 0x80, 0x0B, 0xCC, 0xE1, 0x0B, // 0xBE1CC0B80C00000 + 0x00, 0x00, 0x60, 0xEA, 0x0B, 0xD0, 0xE1, 0x0B, // 0xBE1D00BEA600000 + 0x00, 0x00, 0x40, 0x84, 0x0B, 0xD4, 0xE1, 0x0B, // 0xBE1D40B84400000 + 0x00, 0x00, 0x20, 0xF0, 0x06, 0xD8, 0xE1, 0x0B, // 0xBE1D806F0200000 + 0x00, 0x00, 0x00, 0x8C, 0x0B, 0xDC, 0xE1, 0x0B, // 0xBE1DC0B8C000000 + 0x00, 0x00, 0xC0, 0x8D, 0x0B, 0xE0, 0xE1, 0x0B, // 0xBE1E00B8DC00000 + 0x00, 0x00, 0x00, 0x98, 0x0B, 0xE4, 0xE1, 0x0B, // 0xBE1E40B98000000 + 0x00, 0x00, 0xA0, 0x91, 0x0B, 0xE8, 0xE1, 0x0B, // 0xBE1E80B91A00000 + 0x00, 0x00, 0x80, 0xBC, 0x43, 0xEC, 0xE1, 0x0B, // 0xBE1EC43BC800000 + 0x00, 0x00, 0x60, 0xA8, 0x0B, 0xF0, 0xE1, 0x0B, // 0xBE1F00BA8600000 + 0x00, 0x00, 0xC0, 0xBC, 0x43, 0xF4, 0xE1, 0x0B, // 0xBE1F443BCC00000 + 0x00, 0x00, 0xC0, 0xAD, 0x0B, 0xF8, 0xE1, 0x0B, // 0xBE1F80BADC00000 + 0x00, 0x00, 0x60, 0xAD, 0x0B, 0xFC, 0xE1, 0x0B, // 0xBE1FC0BAD600000 + 0x00, 0x00, 0x80, 0xAF, 0x0B, 0x00, 0xE2, 0x0B, // 0xBE2000BAF800000 + 0x00, 0x00, 0x20, 0xBC, 0x0B, 0x04, 0xE2, 0x0B, // 0xBE2040BBC200000 + 0x00, 0x00, 0x40, 0xBC, 0x0B, 0x08, 0xE2, 0x0B, // 0xBE2080BBC400000 + 0x00, 0x00, 0xE0, 0x05, 0x07, 0x0C, 0xE2, 0x0B, // 0xBE20C0705E00000 + 0x00, 0x00, 0xA0, 0xBF, 0x0B, 0x10, 0xE2, 0x0B, // 0xBE2100BBFA00000 + 0x00, 0x00, 0x00, 0xC5, 0x0B, 0x14, 0xE2, 0x0B, // 0xBE2140BC5000000 + 0x00, 0x00, 0xA0, 0xC7, 0x0B, 0x18, 0xE2, 0x0B, // 0xBE2180BC7A00000 + 0x00, 0x00, 0x20, 0xCD, 0x0B, 0x1C, 0xE2, 0x0B, // 0xBE21C0BCD200000 + 0x00, 0x00, 0x40, 0x0C, 0x07, 0x20, 0xE2, 0x0B, // 0xBE220070C400000 + 0x00, 0x00, 0x60, 0x30, 0x44, 0x24, 0xE2, 0x0B, // 0xBE2244430600000 + 0x00, 0x00, 0x80, 0x0F, 0x07, 0x28, 0xE2, 0x0B, // 0xBE228070F800000 + 0x00, 0x00, 0x00, 0xD6, 0x0B, 0x2C, 0xE2, 0x0B, // 0xBE22C0BD6000000 + 0x00, 0x00, 0x60, 0xD6, 0x0B, 0x30, 0xE2, 0x0B, // 0xBE2300BD6600000 + 0x00, 0x00, 0xC0, 0xD6, 0x0B, 0x34, 0xE2, 0x0B, // 0xBE2340BD6C00000 + 0x00, 0x00, 0x40, 0xD9, 0x0B, 0x38, 0xE2, 0x0B, // 0xBE2380BD9400000 + 0x00, 0x00, 0x40, 0x72, 0x54, 0x3C, 0xE2, 0x0B, // 0xBE23C5472400000 + 0x00, 0x00, 0xC0, 0xDF, 0x0B, 0x40, 0xE2, 0x0B, // 0xBE2400BDFC00000 + 0x00, 0x00, 0x20, 0x66, 0x44, 0x44, 0xE2, 0x0B, // 0xBE2444466200000 + 0x00, 0x00, 0x20, 0x66, 0x44, 0x48, 0xE2, 0x0B, // 0xBE2484466200000 + 0x00, 0x00, 0x20, 0x40, 0x10, 0x4C, 0xE2, 0x0B, // 0xBE24C1040200000 + 0x00, 0x00, 0x40, 0xE4, 0x0B, 0x50, 0xE2, 0x0B, // 0xBE2500BE4400000 + 0x00, 0x00, 0x40, 0xE4, 0x0B, 0x54, 0xE2, 0x0B, // 0xBE2540BE4400000 + 0x00, 0x00, 0xE0, 0x18, 0x07, 0x58, 0xE2, 0x0B, // 0xBE2580718E00000 + 0x00, 0x00, 0x00, 0x57, 0x46, 0x5C, 0xE2, 0x0B, // 0xBE25C4657000000 + 0x00, 0x00, 0x40, 0x3B, 0x4C, 0x60, 0xE2, 0x0B, // 0xBE2604C3B400000 + 0x00, 0x00, 0x40, 0xEC, 0x0B, 0x64, 0xE2, 0x0B, // 0xBE2640BEC400000 + 0x00, 0x00, 0x60, 0xED, 0x0B, 0x68, 0xE2, 0x0B, // 0xBE2680BED600000 + 0x00, 0x00, 0x60, 0x1C, 0x07, 0x6C, 0xE2, 0x0B, // 0xBE26C071C600000 + 0x00, 0x00, 0x40, 0xF3, 0x0B, 0x70, 0xE2, 0x0B, // 0xBE2700BF3400000 + 0x00, 0x00, 0xA0, 0xF9, 0x0B, 0x74, 0xE2, 0x0B, // 0xBE2740BF9A00000 + 0x00, 0x00, 0xE0, 0xFA, 0x0B, 0x78, 0xE2, 0x0B, // 0xBE2780BFAE00000 + 0x00, 0x00, 0x20, 0xFF, 0x0B, 0x7C, 0xE2, 0x0B, // 0xBE27C0BFF200000 + 0x00, 0x00, 0x20, 0x10, 0x0C, 0x80, 0xE2, 0x0B, // 0xBE2800C10200000 + 0x00, 0x00, 0x40, 0x27, 0x07, 0x84, 0xE2, 0x0B, // 0xBE2840727400000 + 0x00, 0x00, 0x80, 0x23, 0x07, 0x88, 0xE2, 0x0B, // 0xBE2880723800000 + 0x00, 0x00, 0x80, 0x12, 0x0C, 0x8C, 0xE2, 0x0B, // 0xBE28C0C12800000 + 0x00, 0x00, 0x80, 0xDA, 0x44, 0x90, 0xE2, 0x0B, // 0xBE29044DA800000 + 0x00, 0x00, 0xE0, 0x18, 0x0C, 0x94, 0xE2, 0x0B, // 0xBE2940C18E00000 + 0x00, 0x00, 0x00, 0x29, 0x0C, 0x98, 0xE2, 0x0B, // 0xBE2980C29000000 + 0x00, 0x00, 0x80, 0x29, 0x0C, 0x9C, 0xE2, 0x0B, // 0xBE29C0C29800000 + 0x00, 0x00, 0xC0, 0x29, 0x0C, 0xA0, 0xE2, 0x0B, // 0xBE2A00C29C00000 + 0x00, 0x00, 0x80, 0x29, 0x0C, 0xA4, 0xE2, 0x0B, // 0xBE2A40C29800000 + 0x00, 0x00, 0x40, 0x2F, 0x0C, 0xA8, 0xE2, 0x0B, // 0xBE2A80C2F400000 + 0x00, 0x00, 0xC0, 0x31, 0x0C, 0xAC, 0xE2, 0x0B, // 0xBE2AC0C31C00000 + 0x00, 0x00, 0x40, 0x36, 0x0C, 0xB0, 0xE2, 0x0B, // 0xBE2B00C36400000 + 0x00, 0x00, 0x80, 0x34, 0x0C, 0xB4, 0xE2, 0x0B, // 0xBE2B40C34800000 + 0x00, 0x00, 0xE0, 0x35, 0x0C, 0xB8, 0xE2, 0x0B, // 0xBE2B80C35E00000 + 0x00, 0x00, 0xC0, 0x3B, 0x0C, 0xBC, 0xE2, 0x0B, // 0xBE2BC0C3BC00000 + 0x00, 0x00, 0x40, 0x3E, 0x0C, 0xC0, 0xE2, 0x0B, // 0xBE2C00C3E400000 + 0x00, 0x00, 0xC0, 0x3E, 0x0C, 0xC4, 0xE2, 0x0B, // 0xBE2C40C3EC00000 + 0x00, 0x00, 0x00, 0x42, 0x0C, 0xC8, 0xE2, 0x0B, // 0xBE2C80C42000000 + 0x00, 0x00, 0x60, 0x43, 0x0C, 0xCC, 0xE2, 0x0B, // 0xBE2CC0C43600000 + 0x00, 0x00, 0xA0, 0x4B, 0x0C, 0xD0, 0xE2, 0x0B, // 0xBE2D00C4BA00000 + 0x00, 0x00, 0x20, 0x56, 0x0C, 0xD4, 0xE2, 0x0B, // 0xBE2D40C56200000 + 0x00, 0x00, 0x80, 0x5A, 0x0C, 0xD8, 0xE2, 0x0B, // 0xBE2D80C5A800000 + 0x00, 0x00, 0x00, 0x6A, 0x0C, 0xDC, 0xE2, 0x0B, // 0xBE2DC0C6A000000 + 0x00, 0x00, 0x80, 0x61, 0x45, 0xE0, 0xE2, 0x0B, // 0xBE2E04561800000 + 0x00, 0x00, 0xA0, 0x67, 0x0C, 0xE4, 0xE2, 0x0B, // 0xBE2E40C67A00000 + 0x00, 0x00, 0x80, 0x5F, 0x0C, 0xE8, 0xE2, 0x0B, // 0xBE2E80C5F800000 + 0x00, 0x00, 0x00, 0x6D, 0x0C, 0xEC, 0xE2, 0x0B, // 0xBE2EC0C6D000000 + 0x00, 0x00, 0x60, 0x70, 0x0C, 0xF0, 0xE2, 0x0B, // 0xBE2F00C70600000 + 0x00, 0x00, 0x80, 0x7C, 0x0C, 0xF4, 0xE2, 0x0B, // 0xBE2F40C7C800000 + 0x00, 0x00, 0x20, 0x7E, 0x45, 0xF8, 0xE2, 0x0B, // 0xBE2F8457E200000 + 0x00, 0x00, 0x40, 0x84, 0x0C, 0xFC, 0xE2, 0x0B, // 0xBE2FC0C84400000 + 0x00, 0x00, 0xA0, 0x78, 0x0C, 0x00, 0xE3, 0x0B, // 0xBE3000C78A00000 + 0x00, 0x00, 0x20, 0x75, 0x0C, 0x04, 0xE3, 0x0B, // 0xBE3040C75200000 + 0x00, 0x00, 0xC0, 0x45, 0x07, 0x08, 0xE3, 0x0B, // 0xBE3080745C00000 + 0x00, 0x00, 0x20, 0x8D, 0x0C, 0x0C, 0xE3, 0x0B, // 0xBE30C0C8D200000 + 0x00, 0x00, 0xC0, 0x8F, 0x0C, 0x10, 0xE3, 0x0B, // 0xBE3100C8FC00000 + 0x00, 0x00, 0xA0, 0x93, 0x0C, 0x14, 0xE3, 0x0B, // 0xBE3140C93A00000 + 0x00, 0x00, 0xE0, 0x8E, 0x0C, 0x18, 0xE3, 0x0B, // 0xBE3180C8EE00000 + 0x00, 0x00, 0x80, 0x4D, 0x07, 0x1C, 0xE3, 0x0B, // 0xBE31C074D800000 + 0x00, 0x00, 0xE0, 0xA9, 0x0C, 0x20, 0xE3, 0x0B, // 0xBE3200CA9E00000 + 0x00, 0x00, 0x80, 0xAD, 0x0C, 0x24, 0xE3, 0x0B, // 0xBE3240CAD800000 + 0x00, 0x00, 0x40, 0x01, 0x46, 0x28, 0xE3, 0x0B, // 0xBE3284601400000 + 0x00, 0x00, 0x60, 0xBC, 0x0C, 0x2C, 0xE3, 0x0B, // 0xBE32C0CBC600000 + 0x00, 0x00, 0x00, 0xDF, 0x0C, 0x30, 0xE3, 0x0B, // 0xBE3300CDF000000 + 0x00, 0x00, 0x20, 0xC9, 0x0C, 0x34, 0xE3, 0x0B, // 0xBE3340CC9200000 + 0x00, 0x00, 0x20, 0x63, 0x07, 0x38, 0xE3, 0x0B, // 0xBE3380763200000 + 0x00, 0x00, 0x20, 0xD2, 0x0C, 0x3C, 0xE3, 0x0B, // 0xBE33C0CD2200000 + 0x00, 0x00, 0x00, 0x61, 0x07, 0x40, 0xE3, 0x0B, // 0xBE3400761000000 + 0x00, 0x00, 0x80, 0x5C, 0x07, 0x44, 0xE3, 0x0B, // 0xBE344075C800000 + 0x00, 0x00, 0x40, 0x32, 0x0A, 0x48, 0xE3, 0x0B, // 0xBE3480A32400000 + 0x00, 0x00, 0xA0, 0x32, 0x0A, 0x4C, 0xE3, 0x0B, // 0xBE34C0A32A00000 + 0x00, 0x00, 0x00, 0xE0, 0x0C, 0x50, 0xE3, 0x0B, // 0xBE3500CE0000000 + 0x00, 0x00, 0x80, 0xD3, 0x0C, 0x54, 0xE3, 0x0B, // 0xBE3540CD3800000 + 0x00, 0x00, 0xA0, 0x15, 0x10, 0x58, 0xE3, 0x0B, // 0xBE3581015A00000 + 0x00, 0x00, 0x20, 0x7B, 0x08, 0x5C, 0xE3, 0x0B, // 0xBE35C087B200000 + 0x00, 0x00, 0xE0, 0xE2, 0x0C, 0x60, 0xE3, 0x0B, // 0xBE3600CE2E00000 + 0x00, 0x00, 0x60, 0xE3, 0x0C, 0x64, 0xE3, 0x0B, // 0xBE3640CE3600000 + 0x00, 0x00, 0x20, 0xE4, 0x0C, 0x68, 0xE3, 0x0B, // 0xBE3680CE4200000 + 0x00, 0x00, 0xC0, 0xEB, 0x0C, 0x6C, 0xE3, 0x0B, // 0xBE36C0CEBC00000 + 0x00, 0x00, 0x60, 0xEA, 0x0C, 0x70, 0xE3, 0x0B, // 0xBE3700CEA600000 + 0x00, 0x00, 0x60, 0x78, 0x46, 0x74, 0xE3, 0x0B, // 0xBE3744678600000 + 0x00, 0x00, 0x20, 0x69, 0x07, 0x78, 0xE3, 0x0B, // 0xBE3780769200000 + 0x00, 0x00, 0x40, 0xFF, 0x0C, 0x7C, 0xE3, 0x0B, // 0xBE37C0CFF400000 + 0x00, 0x00, 0xA0, 0xF0, 0x0C, 0x80, 0xE3, 0x0B, // 0xBE3800CF0A00000 + 0x00, 0x00, 0x40, 0x0A, 0x0D, 0x84, 0xE3, 0x0B, // 0xBE3840D0A400000 + 0x00, 0x00, 0xA0, 0x10, 0x0D, 0x88, 0xE3, 0x0B, // 0xBE3880D10A00000 + 0x00, 0x00, 0xA0, 0x8D, 0x46, 0x8C, 0xE3, 0x0B, // 0xBE38C468DA00000 + 0x00, 0x00, 0xC0, 0x11, 0x0D, 0x90, 0xE3, 0x0B, // 0xBE3900D11C00000 + 0x00, 0x00, 0xE0, 0x03, 0x0D, 0x94, 0xE3, 0x0B, // 0xBE3940D03E00000 + 0x00, 0x00, 0x80, 0x22, 0x0D, 0x98, 0xE3, 0x0B, // 0xBE3980D22800000 + 0x00, 0x00, 0xA0, 0x73, 0x07, 0x9C, 0xE3, 0x0B, // 0xBE39C0773A00000 + 0x00, 0x00, 0x40, 0x28, 0x0D, 0xA0, 0xE3, 0x0B, // 0xBE3A00D28400000 + 0x00, 0x00, 0x60, 0x34, 0x0D, 0xA4, 0xE3, 0x0B, // 0xBE3A40D34600000 + 0x00, 0x00, 0x40, 0x3D, 0x0D, 0xA8, 0xE3, 0x0B, // 0xBE3A80D3D400000 + 0x00, 0x00, 0x00, 0x55, 0x0D, 0xAC, 0xE3, 0x0B, // 0xBE3AC0D55000000 + 0x00, 0x00, 0x60, 0xD4, 0x46, 0xB0, 0xE3, 0x0B, // 0xBE3B046D4600000 + 0x00, 0x00, 0x60, 0x5B, 0x0D, 0xB4, 0xE3, 0x0B, // 0xBE3B40D5B600000 + 0x00, 0x00, 0x00, 0x83, 0x07, 0xB8, 0xE3, 0x0B, // 0xBE3B80783000000 + 0x00, 0x00, 0x20, 0x64, 0x0D, 0xBC, 0xE3, 0x0B, // 0xBE3BC0D64200000 + 0x00, 0x00, 0xE0, 0x14, 0x47, 0xC0, 0xE3, 0x0B, // 0xBE3C04714E00000 + 0x00, 0x00, 0x80, 0x6A, 0x0D, 0xC4, 0xE3, 0x0B, // 0xBE3C40D6A800000 + 0x00, 0x00, 0xC0, 0x89, 0x07, 0xC8, 0xE3, 0x0B, // 0xBE3C80789C00000 + 0x00, 0x00, 0x40, 0x6E, 0x0D, 0xCC, 0xE3, 0x0B, // 0xBE3CC0D6E400000 + 0x00, 0x00, 0xE0, 0x73, 0x0D, 0xD0, 0xE3, 0x0B, // 0xBE3D00D73E00000 + 0x00, 0x00, 0x40, 0x77, 0x0D, 0xD4, 0xE3, 0x0B, // 0xBE3D40D77400000 + 0x00, 0x00, 0x60, 0x77, 0x0D, 0xD8, 0xE3, 0x0B, // 0xBE3D80D77600000 + 0x00, 0x00, 0xA0, 0x51, 0x47, 0xDC, 0xE3, 0x0B, // 0xBE3DC4751A00000 + 0x00, 0x00, 0x60, 0xA1, 0x43, 0xE0, 0xE3, 0x0B, // 0xBE3E043A1600000 + 0x00, 0x00, 0x40, 0x5F, 0x47, 0xE4, 0xE3, 0x0B, // 0xBE3E4475F400000 + 0x00, 0x00, 0xC0, 0x89, 0x0D, 0xE8, 0xE3, 0x0B, // 0xBE3E80D89C00000 + 0x00, 0x00, 0x80, 0x97, 0x47, 0xEC, 0xE3, 0x0B, // 0xBE3EC4797800000 + 0x00, 0x00, 0xE0, 0x97, 0x0D, 0xF0, 0xE3, 0x0B, // 0xBE3F00D97E00000 + 0x00, 0x00, 0xA0, 0x99, 0x0D, 0xF4, 0xE3, 0x0B, // 0xBE3F40D99A00000 + 0x00, 0x00, 0xE0, 0x8C, 0x0D, 0xF8, 0xE3, 0x0B, // 0xBE3F80D8CE00000 + 0x00, 0x00, 0xC0, 0xA2, 0x0D, 0xFC, 0xE3, 0x0B, // 0xBE3FC0DA2C00000 + 0x00, 0x00, 0xC0, 0xA7, 0x0D, 0x00, 0xE4, 0x0B, // 0xBE4000DA7C00000 + 0x00, 0x00, 0xE0, 0xAE, 0x0D, 0x04, 0xE4, 0x0B, // 0xBE4040DAEE00000 + 0x00, 0x00, 0x20, 0xA8, 0x0D, 0x08, 0xE4, 0x0B, // 0xBE4080DA8200000 + 0x00, 0x00, 0x20, 0xAD, 0x0D, 0x0C, 0xE4, 0x0B, // 0xBE40C0DAD200000 + 0x00, 0x00, 0x00, 0xAF, 0x0D, 0x10, 0xE4, 0x0B, // 0xBE4100DAF000000 + 0x00, 0x00, 0xA0, 0xB0, 0x0D, 0x14, 0xE4, 0x0B, // 0xBE4140DB0A00000 + 0x00, 0x00, 0xC0, 0xA3, 0x47, 0x18, 0xE4, 0x0B, // 0xBE41847A3C00000 + 0x00, 0x00, 0x80, 0xA6, 0x0D, 0x1C, 0xE4, 0x0B, // 0xBE41C0DA6800000 + 0x00, 0x00, 0xE0, 0xC5, 0x0D, 0x20, 0xE4, 0x0B, // 0xBE4200DC5E00000 + 0x00, 0x00, 0xC0, 0xCD, 0x0D, 0x24, 0xE4, 0x0B, // 0xBE4240DCDC00000 + 0x00, 0x00, 0x60, 0xA6, 0x07, 0x28, 0xE4, 0x0B, // 0xBE42807A6600000 + 0x00, 0x00, 0x60, 0xD9, 0x0D, 0x2C, 0xE4, 0x0B, // 0xBE42C0DD9600000 + 0x00, 0x00, 0xE0, 0xD8, 0x0D, 0x30, 0xE4, 0x0B, // 0xBE4300DD8E00000 + 0x00, 0x00, 0x20, 0xDA, 0x47, 0x34, 0xE4, 0x0B, // 0xBE43447DA200000 + 0x00, 0x00, 0x20, 0xBF, 0x0D, 0x38, 0xE4, 0x0B, // 0xBE4380DBF200000 + 0x00, 0x00, 0xC0, 0xED, 0x0D, 0x3C, 0xE4, 0x0B, // 0xBE43C0DEDC00000 + 0x00, 0x00, 0xC0, 0xEB, 0x47, 0x40, 0xE4, 0x0B, // 0xBE44047EBC00000 + 0x00, 0x00, 0xC0, 0xF1, 0x47, 0x44, 0xE4, 0x0B, // 0xBE44447F1C00000 + 0x00, 0x00, 0xC0, 0xF8, 0x0D, 0x48, 0xE4, 0x0B, // 0xBE4480DF8C00000 + 0x00, 0x00, 0x20, 0x07, 0x0E, 0x4C, 0xE4, 0x0B, // 0xBE44C0E07200000 + 0x00, 0x00, 0xC0, 0x03, 0x0E, 0x50, 0xE4, 0x0B, // 0xBE4500E03C00000 + 0x00, 0x00, 0x60, 0x03, 0x0E, 0x54, 0xE4, 0x0B, // 0xBE4540E03600000 + 0x00, 0x00, 0xC0, 0xB2, 0x07, 0x58, 0xE4, 0x0B, // 0xBE45807B2C00000 + 0x00, 0x00, 0x40, 0x09, 0x0E, 0x5C, 0xE4, 0x0B, // 0xBE45C0E09400000 + 0x00, 0x00, 0xA0, 0x0F, 0x0E, 0x60, 0xE4, 0x0B, // 0xBE4600E0FA00000 + 0x00, 0x00, 0xE0, 0x0E, 0x0E, 0x64, 0xE4, 0x0B, // 0xBE4640E0EE00000 + 0x00, 0x00, 0xA0, 0x15, 0x0E, 0x68, 0xE4, 0x0B, // 0xBE4680E15A00000 + 0x00, 0x00, 0xA0, 0xA4, 0x40, 0x6C, 0xE4, 0x0B, // 0xBE46C40A4A00000 + 0x00, 0x00, 0xA0, 0x28, 0x0E, 0x70, 0xE4, 0x0B, // 0xBE4700E28A00000 + 0x00, 0x00, 0x60, 0x4C, 0x48, 0x74, 0xE4, 0x0B, // 0xBE474484C600000 + 0x00, 0x00, 0x80, 0x33, 0x0E, 0x78, 0xE4, 0x0B, // 0xBE4780E33800000 + 0x00, 0x00, 0x60, 0x75, 0x48, 0x7C, 0xE4, 0x0B, // 0xBE47C4875600000 + 0x00, 0x00, 0x00, 0x45, 0x0E, 0x80, 0xE4, 0x0B, // 0xBE4800E45000000 + 0x00, 0x00, 0xA0, 0x46, 0x0E, 0x84, 0xE4, 0x0B, // 0xBE4840E46A00000 + 0x00, 0x00, 0x00, 0x4A, 0x0E, 0x88, 0xE4, 0x0B, // 0xBE4880E4A000000 + 0x00, 0x00, 0x00, 0xC1, 0x48, 0x8C, 0xE4, 0x0B, // 0xBE48C48C1000000 + 0x00, 0x00, 0x00, 0x50, 0x0E, 0x90, 0xE4, 0x0B, // 0xBE4900E50000000 + 0x00, 0x00, 0xA0, 0x52, 0x0E, 0x94, 0xE4, 0x0B, // 0xBE4940E52A00000 + 0x00, 0x00, 0xA0, 0xE6, 0x48, 0x98, 0xE4, 0x0B, // 0xBE49848E6A00000 + 0x00, 0x00, 0x80, 0x02, 0x49, 0x9C, 0xE4, 0x0B, // 0xBE49C4902800000 + 0x00, 0x00, 0x40, 0x6F, 0x0E, 0xA0, 0xE4, 0x0B, // 0xBE4A00E6F400000 + 0x00, 0x00, 0x60, 0x71, 0x0E, 0xA4, 0xE4, 0x0B, // 0xBE4A40E71600000 + 0x00, 0x00, 0x80, 0xD5, 0x07, 0xA8, 0xE4, 0x0B, // 0xBE4A807D5800000 + 0x00, 0x00, 0xA0, 0x74, 0x0E, 0xAC, 0xE4, 0x0B, // 0xBE4AC0E74A00000 + 0x00, 0x00, 0x00, 0xD7, 0x07, 0xB0, 0xE4, 0x0B, // 0xBE4B007D7000000 + 0x00, 0x00, 0x00, 0xD7, 0x07, 0xB4, 0xE4, 0x0B, // 0xBE4B407D7000000 + 0x00, 0x00, 0xE0, 0x88, 0x0E, 0xB8, 0xE4, 0x0B, // 0xBE4B80E88E00000 + 0x00, 0x00, 0x80, 0x8B, 0x0E, 0xBC, 0xE4, 0x0B, // 0xBE4BC0E8B800000 + 0x00, 0x00, 0x20, 0x8E, 0x0E, 0xC0, 0xE4, 0x0B, // 0xBE4C00E8E200000 + 0x00, 0x00, 0xA0, 0x90, 0x0E, 0xC4, 0xE4, 0x0B, // 0xBE4C40E90A00000 + 0x00, 0x00, 0x40, 0x99, 0x0E, 0xC8, 0xE4, 0x0B, // 0xBE4C80E99400000 + 0x00, 0x00, 0x60, 0xE3, 0x07, 0xCC, 0xE4, 0x0B, // 0xBE4CC07E3600000 + 0x00, 0x00, 0x80, 0xA4, 0x0E, 0xD0, 0xE4, 0x0B, // 0xBE4D00EA4800000 + 0x00, 0x00, 0xC0, 0x86, 0x49, 0xD4, 0xE4, 0x0B, // 0xBE4D44986C00000 + 0x00, 0x00, 0xC0, 0xA7, 0x0E, 0xD8, 0xE4, 0x0B, // 0xBE4D80EA7C00000 + 0x00, 0x00, 0x40, 0x92, 0x49, 0xDC, 0xE4, 0x0B, // 0xBE4DC4992400000 + 0x00, 0x00, 0x00, 0xAE, 0x0E, 0xE0, 0xE4, 0x0B, // 0xBE4E00EAE000000 + 0x00, 0x00, 0xE0, 0x33, 0x44, 0xE4, 0xE4, 0x0B, // 0xBE4E44433E00000 + 0x00, 0x00, 0x00, 0xC2, 0x0E, 0xE8, 0xE4, 0x0B, // 0xBE4E80EC2000000 + 0x00, 0x00, 0x20, 0xF4, 0x49, 0xEC, 0xE4, 0x0B, // 0xBE4EC49F4200000 + 0x00, 0x00, 0x00, 0xF7, 0x49, 0xF0, 0xE4, 0x0B, // 0xBE4F049F7000000 + 0x00, 0x00, 0x80, 0x08, 0x4A, 0xF4, 0xE4, 0x0B, // 0xBE4F44A08800000 + 0x00, 0x00, 0x80, 0xFF, 0x07, 0xF8, 0xE4, 0x0B, // 0xBE4F807FF800000 + 0x00, 0x00, 0x00, 0x01, 0x08, 0xFC, 0xE4, 0x0B, // 0xBE4FC0801000000 + 0x00, 0x00, 0x80, 0xDE, 0x0E, 0x00, 0xE5, 0x0B, // 0xBE5000EDE800000 + 0x00, 0x00, 0x60, 0x1E, 0x4A, 0x04, 0xE5, 0x0B, // 0xBE5044A1E600000 + 0x00, 0x00, 0x40, 0x1E, 0x4A, 0x08, 0xE5, 0x0B, // 0xBE5084A1E400000 + 0x00, 0x00, 0x20, 0x23, 0x4A, 0x0C, 0xE5, 0x0B, // 0xBE50C4A23200000 + 0x00, 0x00, 0x60, 0x26, 0x4A, 0x10, 0xE5, 0x0B, // 0xBE5104A26600000 + 0x00, 0x00, 0xC0, 0xE3, 0x0E, 0x14, 0xE5, 0x0B, // 0xBE5140EE3C00000 + 0x00, 0x00, 0xE0, 0xE3, 0x0E, 0x18, 0xE5, 0x0B, // 0xBE5180EE3E00000 + 0x00, 0x00, 0xE0, 0xE3, 0x0E, 0x1C, 0xE5, 0x0B, // 0xBE51C0EE3E00000 + 0x00, 0x00, 0x40, 0xE9, 0x0E, 0x20, 0xE5, 0x0B, // 0xBE5200EE9400000 + 0x00, 0x00, 0x20, 0x07, 0x08, 0x24, 0xE5, 0x0B, // 0xBE5240807200000 + 0x00, 0x00, 0x60, 0xF1, 0x0E, 0x28, 0xE5, 0x0B, // 0xBE5280EF1600000 + 0x00, 0x00, 0xC0, 0x08, 0x08, 0x2C, 0xE5, 0x0B, // 0xBE52C0808C00000 + 0x00, 0x00, 0xC0, 0x12, 0x08, 0x30, 0xE5, 0x0B, // 0xBE5300812C00000 + 0x00, 0x00, 0xA0, 0x83, 0x4A, 0x34, 0xE5, 0x0B, // 0xBE5344A83A00000 + 0x00, 0x00, 0xC0, 0x09, 0x0F, 0x38, 0xE5, 0x0B, // 0xBE5380F09C00000 + 0x00, 0x00, 0x80, 0x11, 0x0F, 0x3C, 0xE5, 0x0B, // 0xBE53C0F11800000 + 0x00, 0x00, 0x80, 0x19, 0x0F, 0x40, 0xE5, 0x0B, // 0xBE5400F19800000 + 0x00, 0x00, 0x60, 0x1C, 0x08, 0x44, 0xE5, 0x0B, // 0xBE544081C600000 + 0x00, 0x00, 0xC0, 0xC4, 0x4A, 0x48, 0xE5, 0x0B, // 0xBE5484AC4C00000 + 0x00, 0x00, 0xC0, 0x2A, 0x0F, 0x4C, 0xE5, 0x0B, // 0xBE54C0F2AC00000 + 0x00, 0x00, 0x40, 0xD3, 0x4A, 0x50, 0xE5, 0x0B, // 0xBE5504AD3400000 + 0x00, 0x00, 0xA0, 0xD8, 0x4A, 0x54, 0xE5, 0x0B, // 0xBE5544AD8A00000 + 0x00, 0x00, 0xE0, 0x31, 0x0F, 0x58, 0xE5, 0x0B, // 0xBE5580F31E00000 + 0x00, 0x00, 0x60, 0x3D, 0x0F, 0x5C, 0xE5, 0x0B, // 0xBE55C0F3D600000 + 0x00, 0x00, 0xE0, 0x25, 0x08, 0x60, 0xE5, 0x0B, // 0xBE5600825E00000 + 0x00, 0x00, 0x00, 0x48, 0x0F, 0x64, 0xE5, 0x0B, // 0xBE5640F48000000 + 0x00, 0x00, 0x40, 0x49, 0x0F, 0x68, 0xE5, 0x0B, // 0xBE5680F49400000 + 0x00, 0x00, 0xE0, 0x49, 0x0F, 0x6C, 0xE5, 0x0B, // 0xBE56C0F49E00000 + 0x00, 0x00, 0x80, 0x2F, 0x4B, 0x70, 0xE5, 0x0B, // 0xBE5704B2F800000 + 0x00, 0x00, 0xE0, 0x54, 0x4B, 0x74, 0xE5, 0x0B, // 0xBE5744B54E00000 + 0x00, 0x00, 0xE0, 0x54, 0x4B, 0x78, 0xE5, 0x0B, // 0xBE5784B54E00000 + 0x00, 0x00, 0xC0, 0x5D, 0x0F, 0x7C, 0xE5, 0x0B, // 0xBE57C0F5DC00000 + 0x00, 0x00, 0x40, 0x40, 0x08, 0x80, 0xE5, 0x0B, // 0xBE5800840400000 + 0x00, 0x00, 0x60, 0x75, 0x4B, 0x84, 0xE5, 0x0B, // 0xBE5844B75600000 + 0x00, 0x00, 0xC0, 0x78, 0x0F, 0x88, 0xE5, 0x0B, // 0xBE5880F78C00000 + 0x00, 0x00, 0x20, 0x79, 0x0F, 0x8C, 0xE5, 0x0B, // 0xBE58C0F79200000 + 0x00, 0x00, 0xE0, 0x44, 0x08, 0x90, 0xE5, 0x0B, // 0xBE5900844E00000 + 0x00, 0x00, 0x00, 0x90, 0x4B, 0x94, 0xE5, 0x0B, // 0xBE5944B90000000 + 0x00, 0x00, 0x40, 0x9A, 0x0F, 0x98, 0xE5, 0x0B, // 0xBE5980F9A400000 + 0x00, 0x00, 0x00, 0x54, 0x08, 0x9C, 0xE5, 0x0B, // 0xBE59C0854000000 + 0x00, 0x00, 0x00, 0x9D, 0x0F, 0xA0, 0xE5, 0x0B, // 0xBE5A00F9D000000 + 0x00, 0x00, 0x60, 0x9C, 0x0F, 0xA4, 0xE5, 0x0B, // 0xBE5A40F9C600000 + 0x00, 0x00, 0x00, 0xA0, 0x0F, 0xA8, 0xE5, 0x0B, // 0xBE5A80FA0000000 + 0x00, 0x00, 0xC0, 0xF0, 0x4B, 0xAC, 0xE5, 0x0B, // 0xBE5AC4BF0C00000 + 0x00, 0x00, 0x60, 0xAC, 0x0F, 0xB0, 0xE5, 0x0B, // 0xBE5B00FAC600000 + 0x00, 0x00, 0x20, 0x60, 0x08, 0xB4, 0xE5, 0x0B, // 0xBE5B40860200000 + 0x00, 0x00, 0xE0, 0xB8, 0x0F, 0xB8, 0xE5, 0x0B, // 0xBE5B80FB8E00000 + 0x00, 0x00, 0x40, 0xC0, 0x0F, 0xBC, 0xE5, 0x0B, // 0xBE5BC0FC0400000 + 0x00, 0x00, 0xA0, 0xC8, 0x0F, 0xC0, 0xE5, 0x0B, // 0xBE5C00FC8A00000 + 0x00, 0x00, 0x80, 0x66, 0x08, 0xC4, 0xE5, 0x0B, // 0xBE5C40866800000 + 0x00, 0x00, 0x00, 0x45, 0x4C, 0xC8, 0xE5, 0x0B, // 0xBE5C84C45000000 + 0x00, 0x00, 0xE0, 0x48, 0x4C, 0xCC, 0xE5, 0x0B, // 0xBE5CC4C48E00000 + 0x00, 0x00, 0x20, 0x6B, 0x08, 0xD0, 0xE5, 0x0B, // 0xBE5D0086B200000 + 0x00, 0x00, 0x20, 0x5B, 0x4C, 0xD4, 0xE5, 0x0B, // 0xBE5D44C5B200000 + 0x00, 0x00, 0x40, 0xEF, 0x0F, 0xD8, 0xE5, 0x0B, // 0xBE5D80FEF400000 + 0x00, 0x00, 0xC0, 0x67, 0x4C, 0xDC, 0xE5, 0x0B, // 0xBE5DC4C67C00000 + 0x00, 0x00, 0xA0, 0xF2, 0x0F, 0xE0, 0xE5, 0x0B, // 0xBE5E00FF2A00000 + 0x00, 0x00, 0x40, 0xFF, 0x0F, 0xE4, 0xE5, 0x0B, // 0xBE5E40FFF400000 + 0x00, 0x00, 0xA0, 0x00, 0x10, 0xE8, 0xE5, 0x0B, // 0xBE5E81000A00000 + 0x00, 0x00, 0x40, 0x9B, 0x4C, 0xEC, 0xE5, 0x0B, // 0xBE5EC4C9B400000 + 0x00, 0x00, 0x60, 0xA4, 0x4C, 0xF0, 0xE5, 0x0B, // 0xBE5F04CA4600000 + 0x00, 0x00, 0x00, 0x0C, 0x10, 0xF4, 0xE5, 0x0B, // 0xBE5F4100C000000 + 0x00, 0x00, 0x00, 0xB5, 0x4C, 0xF8, 0xE5, 0x0B, // 0xBE5F84CB5000000 + 0x00, 0x00, 0x00, 0x0E, 0x10, 0xFC, 0xE5, 0x0B, // 0xBE5FC100E000000 + 0x00, 0x00, 0xE0, 0x6B, 0x46, 0x00, 0xE6, 0x0B, // 0xBE600466BE00000 + 0x00, 0x00, 0xA0, 0x7A, 0x08, 0x04, 0xE6, 0x0B, // 0xBE604087AA00000 + 0x00, 0x00, 0x40, 0x16, 0x10, 0x08, 0xE6, 0x0B, // 0xBE6081016400000 + 0x00, 0x00, 0x60, 0x20, 0x10, 0x0C, 0xE6, 0x0B, // 0xBE60C1020600000 + 0x00, 0x00, 0x60, 0x81, 0x08, 0x10, 0xE6, 0x0B, // 0xBE6100881600000 + 0x00, 0x00, 0xC0, 0x27, 0x10, 0x14, 0xE6, 0x0B, // 0xBE6141027C00000 + 0x00, 0x00, 0xA0, 0x56, 0x0B, 0x18, 0xE6, 0x0B, // 0xBE6180B56A00000 + 0x00, 0x00, 0xE0, 0xF4, 0x4C, 0x1C, 0xE6, 0x0B, // 0xBE61C4CF4E00000 + 0x00, 0x00, 0xA0, 0xF6, 0x4C, 0x20, 0xE6, 0x0B, // 0xBE6204CF6A00000 + 0x00, 0x00, 0x60, 0x72, 0x46, 0x24, 0xE6, 0x0B, // 0xBE6244672600000 + 0x00, 0x00, 0x80, 0x73, 0x46, 0x28, 0xE6, 0x0B, // 0xBE6284673800000 + 0x00, 0x00, 0x20, 0x40, 0x10, 0x2C, 0xE6, 0x0B, // 0xBE62C1040200000 + 0x00, 0x00, 0x80, 0x40, 0x10, 0x30, 0xE6, 0x0B, // 0xBE6301040800000 + 0x00, 0x00, 0xC0, 0xF3, 0x11, 0x34, 0xE6, 0x0B, // 0xBE63411F3C00000 + 0x00, 0x00, 0x60, 0x8D, 0x08, 0x38, 0xE6, 0x0B, // 0xBE638088D600000 + 0x00, 0x00, 0x20, 0x52, 0x10, 0x3C, 0xE6, 0x0B, // 0xBE63C1052200000 + 0x00, 0x00, 0x60, 0x51, 0x10, 0x40, 0xE6, 0x0B, // 0xBE6401051600000 + 0x00, 0x00, 0xA0, 0x53, 0x10, 0x44, 0xE6, 0x0B, // 0xBE6441053A00000 + 0x00, 0x00, 0x60, 0x56, 0x0A, 0x48, 0xE6, 0x0B, // 0xBE6480A56600000 + 0x00, 0x00, 0x20, 0x56, 0x10, 0x4C, 0xE6, 0x0B, // 0xBE64C1056200000 + 0x00, 0x00, 0x60, 0x56, 0x10, 0x50, 0xE6, 0x0B, // 0xBE6501056600000 + 0x00, 0x00, 0xA0, 0x57, 0x10, 0x54, 0xE6, 0x0B, // 0xBE6541057A00000 + 0x00, 0x00, 0xC0, 0x5C, 0x10, 0x58, 0xE6, 0x0B, // 0xBE658105CC00000 + 0x00, 0x00, 0x80, 0x67, 0x4D, 0x5C, 0xE6, 0x0B, // 0xBE65C4D67800000 + 0x00, 0x00, 0xA0, 0x5C, 0x10, 0x60, 0xE6, 0x0B, // 0xBE660105CA00000 + 0x00, 0x00, 0xA0, 0x63, 0x10, 0x64, 0xE6, 0x0B, // 0xBE6641063A00000 + 0x00, 0x00, 0x60, 0x6C, 0x10, 0x68, 0xE6, 0x0B, // 0xBE668106C600000 + 0x00, 0x00, 0xA0, 0x75, 0x10, 0x6C, 0xE6, 0x0B, // 0xBE66C1075A00000 + 0x00, 0x00, 0x60, 0x64, 0x10, 0x70, 0xE6, 0x0B, // 0xBE6701064600000 + 0x00, 0x00, 0xA0, 0x77, 0x10, 0x74, 0xE6, 0x0B, // 0xBE6741077A00000 + 0x00, 0x00, 0xE0, 0x7C, 0x10, 0x78, 0xE6, 0x0B, // 0xBE678107CE00000 + 0x00, 0x00, 0xE0, 0x8A, 0x10, 0x7C, 0xE6, 0x0B, // 0xBE67C108AE00000 + 0x00, 0x00, 0x60, 0x6A, 0x10, 0x80, 0xE6, 0x0B, // 0xBE680106A600000 + 0x00, 0x00, 0x40, 0x79, 0x10, 0x84, 0xE6, 0x0B, // 0xBE6841079400000 + 0x00, 0x00, 0x80, 0x79, 0x10, 0x88, 0xE6, 0x0B, // 0xBE6881079800000 + 0x00, 0x00, 0x80, 0x7B, 0x10, 0x8C, 0xE6, 0x0B, // 0xBE68C107B800000 + 0x00, 0x00, 0xC0, 0x86, 0x4D, 0x90, 0xE6, 0x0B, // 0xBE6904D86C00000 + 0x00, 0x00, 0x60, 0xAD, 0x4D, 0x94, 0xE6, 0x0B, // 0xBE6944DAD600000 + 0x00, 0x00, 0xA0, 0x9A, 0x4D, 0x98, 0xE6, 0x0B, // 0xBE6984D9AA00000 + 0x00, 0x00, 0x60, 0xA5, 0x08, 0x9C, 0xE6, 0x0B, // 0xBE69C08A5600000 + 0x00, 0x00, 0x20, 0x9E, 0x10, 0xA0, 0xE6, 0x0B, // 0xBE6A0109E200000 + 0x00, 0x00, 0x60, 0x9E, 0x10, 0xA4, 0xE6, 0x0B, // 0xBE6A4109E600000 + 0x00, 0x00, 0xC0, 0xA2, 0x10, 0xA8, 0xE6, 0x0B, // 0xBE6A810A2C00000 + 0x00, 0x00, 0x40, 0x79, 0x4E, 0xAC, 0xE6, 0x0B, // 0xBE6AC4E79400000 + 0x00, 0x00, 0x80, 0xAC, 0x10, 0xB0, 0xE6, 0x0B, // 0xBE6B010AC800000 + 0x00, 0x00, 0x80, 0xE5, 0x4D, 0xB4, 0xE6, 0x0B, // 0xBE6B44DE5800000 + 0x00, 0x00, 0xA0, 0xAB, 0x08, 0xB8, 0xE6, 0x0B, // 0xBE6B808ABA00000 + 0x00, 0x00, 0x20, 0xAC, 0x08, 0xBC, 0xE6, 0x0B, // 0xBE6BC08AC200000 + 0x00, 0x00, 0x20, 0xF6, 0x4D, 0xC0, 0xE6, 0x0B, // 0xBE6C04DF6200000 + 0x00, 0x00, 0x40, 0x1A, 0x4E, 0xC4, 0xE6, 0x0B, // 0xBE6C44E1A400000 + 0x00, 0x00, 0x60, 0xAD, 0x08, 0xC8, 0xE6, 0x0B, // 0xBE6C808AD600000 + 0x00, 0x00, 0x00, 0xCA, 0x10, 0xCC, 0xE6, 0x0B, // 0xBE6CC10CA000000 + 0x00, 0x00, 0x80, 0xCB, 0x10, 0xD0, 0xE6, 0x0B, // 0xBE6D010CB800000 + 0x00, 0x00, 0xE0, 0xCC, 0x10, 0xD4, 0xE6, 0x0B, // 0xBE6D410CCE00000 + 0x00, 0x00, 0x20, 0xCD, 0x10, 0xD8, 0xE6, 0x0B, // 0xBE6D810CD200000 + 0x00, 0x00, 0x20, 0xD5, 0x10, 0xDC, 0xE6, 0x0B, // 0xBE6DC10D5200000 + 0x00, 0x00, 0x00, 0xD1, 0x10, 0xE0, 0xE6, 0x0B, // 0xBE6E010D1000000 + 0x00, 0x00, 0xC0, 0xE1, 0x10, 0xE4, 0xE6, 0x0B, // 0xBE6E410E1C00000 + 0x00, 0x00, 0x40, 0xDC, 0x10, 0xE8, 0xE6, 0x0B, // 0xBE6E810DC400000 + 0x00, 0x00, 0x20, 0xEF, 0x10, 0xEC, 0xE6, 0x0B, // 0xBE6EC10EF200000 + 0x00, 0x00, 0x00, 0xE5, 0x10, 0xF0, 0xE6, 0x0B, // 0xBE6F010E5000000 + 0x00, 0x00, 0x60, 0xED, 0x10, 0xF4, 0xE6, 0x0B, // 0xBE6F410ED600000 + 0x00, 0x00, 0xC0, 0xF0, 0x10, 0xF8, 0xE6, 0x0B, // 0xBE6F810F0C00000 + 0x00, 0x00, 0xE0, 0xBA, 0x08, 0xFC, 0xE6, 0x0B, // 0xBE6FC08BAE00000 + 0x00, 0x00, 0x20, 0xFC, 0x10, 0x00, 0xE7, 0x0B, // 0xBE70010FC200000 + 0x00, 0x00, 0x20, 0x00, 0x11, 0x04, 0xE7, 0x0B, // 0xBE7041100200000 + 0x00, 0x00, 0x20, 0xBF, 0x08, 0x08, 0xE7, 0x0B, // 0xBE70808BF200000 + 0x00, 0x00, 0x00, 0x0C, 0x11, 0x0C, 0xE7, 0x0B, // 0xBE70C110C000000 + 0x00, 0x00, 0x60, 0x0C, 0x11, 0x10, 0xE7, 0x0B, // 0xBE710110C600000 + 0x00, 0x00, 0xE0, 0xCC, 0x4E, 0x14, 0xE7, 0x0B, // 0xBE7144ECCE00000 + 0x00, 0x00, 0xE0, 0x1A, 0x11, 0x18, 0xE7, 0x0B, // 0xBE718111AE00000 + 0x00, 0x00, 0xC0, 0x1B, 0x11, 0x1C, 0xE7, 0x0B, // 0xBE71C111BC00000 + 0x00, 0x00, 0xA0, 0xC6, 0x08, 0x20, 0xE7, 0x0B, // 0xBE72008C6A00000 + 0x00, 0x00, 0x40, 0x1F, 0x11, 0x24, 0xE7, 0x0B, // 0xBE724111F400000 + 0x00, 0x00, 0x60, 0x97, 0x06, 0x28, 0xE7, 0x0B, // 0xBE7280697600000 + 0x00, 0x00, 0xC0, 0x15, 0x4F, 0x2C, 0xE7, 0x0B, // 0xBE72C4F15C00000 + 0x00, 0x00, 0xC0, 0x2C, 0x4F, 0x30, 0xE7, 0x0B, // 0xBE7304F2CC00000 + 0x00, 0x00, 0xC0, 0xD7, 0x08, 0x34, 0xE7, 0x0B, // 0xBE73408D7C00000 + 0x00, 0x00, 0xE0, 0xD8, 0x08, 0x38, 0xE7, 0x0B, // 0xBE73808D8E00000 + 0x00, 0x00, 0x00, 0x54, 0x11, 0x3C, 0xE7, 0x0B, // 0xBE73C1154000000 + 0x00, 0x00, 0xA0, 0x5D, 0x11, 0x40, 0xE7, 0x0B, // 0xBE740115DA00000 + 0x00, 0x00, 0x40, 0x71, 0x11, 0x44, 0xE7, 0x0B, // 0xBE7441171400000 + 0x00, 0x00, 0xA0, 0x8A, 0x11, 0x48, 0xE7, 0x0B, // 0xBE748118AA00000 + 0x00, 0x00, 0x00, 0x95, 0x4F, 0x4C, 0xE7, 0x0B, // 0xBE74C4F95000000 + 0x00, 0x00, 0x60, 0x95, 0x11, 0x50, 0xE7, 0x0B, // 0xBE7501195600000 + 0x00, 0x00, 0x20, 0x98, 0x11, 0x54, 0xE7, 0x0B, // 0xBE7541198200000 + 0x00, 0x00, 0x60, 0xA3, 0x11, 0x58, 0xE7, 0x0B, // 0xBE75811A3600000 + 0x00, 0x00, 0xE0, 0xAE, 0x11, 0x5C, 0xE7, 0x0B, // 0xBE75C11AEE00000 + 0x00, 0x00, 0xE0, 0xE5, 0x4F, 0x60, 0xE7, 0x0B, // 0xBE7604FE5E00000 + 0x00, 0x00, 0x80, 0x00, 0x41, 0x64, 0xE7, 0x0B, // 0xBE7644100800000 + 0x00, 0x00, 0x60, 0xB9, 0x11, 0x68, 0xE7, 0x0B, // 0xBE76811B9600000 + 0x00, 0x00, 0x80, 0xB7, 0x11, 0x6C, 0xE7, 0x0B, // 0xBE76C11B7800000 + 0x00, 0x00, 0x00, 0xBE, 0x11, 0x70, 0xE7, 0x0B, // 0xBE77011BE000000 + 0x00, 0x00, 0xC0, 0x1B, 0x41, 0x74, 0xE7, 0x0B, // 0xBE774411BC00000 + 0x00, 0x00, 0x80, 0xDA, 0x11, 0x78, 0xE7, 0x0B, // 0xBE77811DA800000 + 0x00, 0x00, 0x00, 0xE7, 0x11, 0x7C, 0xE7, 0x0B, // 0xBE77C11E7000000 + 0x00, 0x00, 0x40, 0xBA, 0x50, 0x80, 0xE7, 0x0B, // 0xBE78050BA400000 + 0x00, 0x00, 0xA0, 0xBD, 0x50, 0x84, 0xE7, 0x0B, // 0xBE78450BDA00000 + 0x00, 0x00, 0x80, 0x12, 0x12, 0x88, 0xE7, 0x0B, // 0xBE7881212800000 + 0x00, 0x00, 0x20, 0x1E, 0x12, 0x8C, 0xE7, 0x0B, // 0xBE78C121E200000 + 0x00, 0x00, 0x20, 0x22, 0x12, 0x90, 0xE7, 0x0B, // 0xBE7901222200000 + 0x00, 0x00, 0xC0, 0xE5, 0x50, 0x94, 0xE7, 0x0B, // 0xBE79450E5C00000 + 0x00, 0x00, 0x60, 0x23, 0x12, 0x98, 0xE7, 0x0B, // 0xBE7981223600000 + 0x00, 0x00, 0x00, 0x47, 0x12, 0x9C, 0xE7, 0x0B, // 0xBE79C1247000000 + 0x00, 0x00, 0xE0, 0x5A, 0x12, 0xA0, 0xE7, 0x0B, // 0xBE7A0125AE00000 + 0x00, 0x00, 0x00, 0x5B, 0x12, 0xA4, 0xE7, 0x0B, // 0xBE7A4125B000000 + 0x00, 0x00, 0x80, 0x4F, 0x12, 0xA8, 0xE7, 0x0B, // 0xBE7A8124F800000 + 0x00, 0x00, 0x20, 0x7F, 0x12, 0xAC, 0xE7, 0x0B, // 0xBE7AC127F200000 + 0x00, 0x00, 0xA0, 0x82, 0x12, 0xB0, 0xE7, 0x0B, // 0xBE7B01282A00000 + 0x00, 0x00, 0x40, 0x7F, 0x51, 0xB4, 0xE7, 0x0B, // 0xBE7B4517F400000 + 0x00, 0x00, 0x60, 0xB1, 0x12, 0xB8, 0xE7, 0x0B, // 0xBE7B812B1600000 + 0x00, 0x00, 0xA0, 0x32, 0x09, 0xBC, 0xE7, 0x0B, // 0xBE7BC0932A00000 + 0x00, 0x00, 0xE0, 0xB6, 0x12, 0xC0, 0xE7, 0x0B, // 0xBE7C012B6E00000 + 0x00, 0x00, 0xE0, 0xAE, 0x51, 0xC4, 0xE7, 0x0B, // 0xBE7C451AEE00000 + 0x00, 0x00, 0xC0, 0x3C, 0x09, 0xC8, 0xE7, 0x0B, // 0xBE7C8093CC00000 + 0x00, 0x00, 0x60, 0xD8, 0x12, 0xCC, 0xE7, 0x0B, // 0xBE7CC12D8600000 + 0x00, 0x00, 0x40, 0xB6, 0x0B, 0xD0, 0xE7, 0x0B, // 0xBE7D00BB6400000 + 0x00, 0x00, 0x60, 0xE4, 0x12, 0xD4, 0xE7, 0x0B, // 0xBE7D412E4600000 + 0x00, 0x00, 0xA0, 0x28, 0x52, 0xD8, 0xE7, 0x0B, // 0xBE7D85228A00000 + 0x00, 0x00, 0x40, 0x43, 0x52, 0xDC, 0xE7, 0x0B, // 0xBE7DC5243400000 + 0x00, 0x00, 0xC0, 0x4D, 0x09, 0xE0, 0xE7, 0x0B, // 0xBE7E0094DC00000 + 0x00, 0x00, 0xC0, 0x4E, 0x09, 0xE4, 0xE7, 0x0B, // 0xBE7E4094EC00000 + 0x00, 0x00, 0x00, 0xFC, 0x12, 0xE8, 0xE7, 0x0B, // 0xBE7E812FC000000 + 0x00, 0x00, 0x40, 0x81, 0x52, 0xEC, 0xE7, 0x0B, // 0xBE7EC5281400000 + 0x00, 0x00, 0x40, 0x56, 0x09, 0xF0, 0xE7, 0x0B, // 0xBE7F00956400000 + 0x00, 0x00, 0xC0, 0x92, 0x52, 0xF4, 0xE7, 0x0B, // 0xBE7F45292C00000 + 0x00, 0x00, 0x60, 0x01, 0x13, 0xF8, 0xE7, 0x0B, // 0xBE7F81301600000 + 0x00, 0x00, 0x60, 0x01, 0x13, 0xFC, 0xE7, 0x0B, // 0xBE7FC1301600000 + 0x00, 0x00, 0x20, 0x05, 0x13, 0x00, 0xE8, 0x0B, // 0xBE8001305200000 + 0x00, 0x00, 0xC0, 0xB6, 0x52, 0x04, 0xE8, 0x0B, // 0xBE80452B6C00000 + 0x00, 0x00, 0x40, 0x1C, 0x13, 0x08, 0xE8, 0x0B, // 0xBE808131C400000 + 0x00, 0x00, 0x60, 0x66, 0x09, 0x0C, 0xE8, 0x0B, // 0xBE80C0966600000 + 0x00, 0x00, 0x20, 0x25, 0x13, 0x10, 0xE8, 0x0B, // 0xBE8101325200000 + 0x00, 0x00, 0xE0, 0x34, 0x13, 0x14, 0xE8, 0x0B, // 0xBE8141334E00000 + 0x00, 0x00, 0x40, 0x38, 0x13, 0x18, 0xE8, 0x0B, // 0xBE8181338400000 + 0x00, 0x00, 0xC0, 0x3F, 0x13, 0x1C, 0xE8, 0x0B, // 0xBE81C133FC00000 + 0x00, 0x00, 0xC0, 0x79, 0x09, 0x20, 0xE8, 0x0B, // 0xBE8200979C00000 + 0x00, 0x00, 0x00, 0x66, 0x53, 0x24, 0xE8, 0x0B, // 0xBE8245366000000 + 0x00, 0x00, 0x40, 0x62, 0x13, 0x28, 0xE8, 0x0B, // 0xBE8281362400000 + 0x00, 0x00, 0x00, 0x88, 0x13, 0x2C, 0xE8, 0x0B, // 0xBE82C1388000000 + 0x00, 0x00, 0xA0, 0x9F, 0x13, 0x30, 0xE8, 0x0B, // 0xBE830139FA00000 + 0x00, 0x00, 0xC0, 0x99, 0x09, 0x34, 0xE8, 0x0B, // 0xBE8340999C00000 + 0x00, 0x00, 0xA0, 0x9D, 0x09, 0x38, 0xE8, 0x0B, // 0xBE838099DA00000 + 0x00, 0x00, 0xE0, 0xAC, 0x13, 0x3C, 0xE8, 0x0B, // 0xBE83C13ACE00000 + 0x00, 0x00, 0xC0, 0x19, 0x54, 0x40, 0xE8, 0x0B, // 0xBE8405419C00000 + 0x00, 0x00, 0x00, 0x9F, 0x09, 0x44, 0xE8, 0x0B, // 0xBE844099F000000 + 0x00, 0x00, 0xA0, 0x20, 0x54, 0x48, 0xE8, 0x0B, // 0xBE8485420A00000 + 0x00, 0x00, 0xC0, 0x41, 0x54, 0x4C, 0xE8, 0x0B, // 0xBE84C5441C00000 + 0x00, 0x00, 0x20, 0x52, 0x54, 0x50, 0xE8, 0x0B, // 0xBE8505452200000 + 0x00, 0x00, 0x60, 0xD7, 0x13, 0x54, 0xE8, 0x0B, // 0xBE85413D7600000 + 0x00, 0x00, 0xC0, 0xAA, 0x09, 0x58, 0xE8, 0x0B, // 0xBE85809AAC00000 + 0x00, 0x00, 0x20, 0xDF, 0x13, 0x5C, 0xE8, 0x0B, // 0xBE85C13DF200000 + 0x00, 0x00, 0xC0, 0xDF, 0x13, 0x60, 0xE8, 0x0B, // 0xBE86013DFC00000 + 0x00, 0x00, 0xA0, 0xE0, 0x13, 0x64, 0xE8, 0x0B, // 0xBE86413E0A00000 + 0x00, 0x00, 0xE0, 0xE1, 0x13, 0x68, 0xE8, 0x0B, // 0xBE86813E1E00000 + 0x00, 0x00, 0xC0, 0xE2, 0x13, 0x6C, 0xE8, 0x0B, // 0xBE86C13E2C00000 + 0x00, 0x00, 0x60, 0xE7, 0x13, 0x70, 0xE8, 0x0B, // 0xBE87013E7600000 + 0x00, 0x00, 0x00, 0xC0, 0x54, 0x74, 0xE8, 0x0B, // 0xBE87454C0000000 + }; + + /// + /// Gets the canonical compositions, ordered by the pair of characters that join so that the table can be searched by the pair. + /// + /// Each entry packs three scalar values into 21 bits each, most significant first: the first character of the pair, + /// the second, and the character they compose to. + /// + /// + /// A pair appears only when it may be recomposed after being taken apart. A character excluded from composition, + /// one whose own combining class is non-zero, and one whose decomposition begins with a combining mark are all left + /// out, so composing never contradicts the decomposition it reverses. Hangul syllables compose by arithmetic and are + /// absent for that reason. + /// + /// + /// Each entry occupies eight bytes, least significant first, and is read with + /// . + /// + /// + public static ReadOnlySpan Compositions => new byte[] + { + 0x6E, 0x22, 0x00, 0x67, 0x00, 0xF0, 0x00, 0x00, // 0xF0006700226E + 0x60, 0x22, 0x00, 0x67, 0x00, 0xF4, 0x00, 0x00, // 0xF40067002260 + 0x6F, 0x22, 0x00, 0x67, 0x00, 0xF8, 0x00, 0x00, // 0xF8006700226F + 0xC0, 0x00, 0x00, 0x60, 0x00, 0x04, 0x01, 0x00, // 0x10400600000C0 + 0xC1, 0x00, 0x20, 0x60, 0x00, 0x04, 0x01, 0x00, // 0x10400602000C1 + 0xC2, 0x00, 0x40, 0x60, 0x00, 0x04, 0x01, 0x00, // 0x10400604000C2 + 0xC3, 0x00, 0x60, 0x60, 0x00, 0x04, 0x01, 0x00, // 0x10400606000C3 + 0x00, 0x01, 0x80, 0x60, 0x00, 0x04, 0x01, 0x00, // 0x1040060800100 + 0x02, 0x01, 0xC0, 0x60, 0x00, 0x04, 0x01, 0x00, // 0x1040060C00102 + 0x26, 0x02, 0xE0, 0x60, 0x00, 0x04, 0x01, 0x00, // 0x1040060E00226 + 0xC4, 0x00, 0x00, 0x61, 0x00, 0x04, 0x01, 0x00, // 0x10400610000C4 + 0xA2, 0x1E, 0x20, 0x61, 0x00, 0x04, 0x01, 0x00, // 0x1040061201EA2 + 0xC5, 0x00, 0x40, 0x61, 0x00, 0x04, 0x01, 0x00, // 0x10400614000C5 + 0xCD, 0x01, 0x80, 0x61, 0x00, 0x04, 0x01, 0x00, // 0x10400618001CD + 0x00, 0x02, 0xE0, 0x61, 0x00, 0x04, 0x01, 0x00, // 0x1040061E00200 + 0x02, 0x02, 0x20, 0x62, 0x00, 0x04, 0x01, 0x00, // 0x1040062200202 + 0xA0, 0x1E, 0x60, 0x64, 0x00, 0x04, 0x01, 0x00, // 0x1040064601EA0 + 0x00, 0x1E, 0xA0, 0x64, 0x00, 0x04, 0x01, 0x00, // 0x1040064A01E00 + 0x04, 0x01, 0x00, 0x65, 0x00, 0x04, 0x01, 0x00, // 0x1040065000104 + 0x02, 0x1E, 0xE0, 0x60, 0x00, 0x08, 0x01, 0x00, // 0x1080060E01E02 + 0x04, 0x1E, 0x60, 0x64, 0x00, 0x08, 0x01, 0x00, // 0x1080064601E04 + 0x06, 0x1E, 0x20, 0x66, 0x00, 0x08, 0x01, 0x00, // 0x1080066201E06 + 0x06, 0x01, 0x20, 0x60, 0x00, 0x0C, 0x01, 0x00, // 0x10C0060200106 + 0x08, 0x01, 0x40, 0x60, 0x00, 0x0C, 0x01, 0x00, // 0x10C0060400108 + 0x0A, 0x01, 0xE0, 0x60, 0x00, 0x0C, 0x01, 0x00, // 0x10C0060E0010A + 0x0C, 0x01, 0x80, 0x61, 0x00, 0x0C, 0x01, 0x00, // 0x10C006180010C + 0xC7, 0x00, 0xE0, 0x64, 0x00, 0x0C, 0x01, 0x00, // 0x10C0064E000C7 + 0x0A, 0x1E, 0xE0, 0x60, 0x00, 0x10, 0x01, 0x00, // 0x1100060E01E0A + 0x0E, 0x01, 0x80, 0x61, 0x00, 0x10, 0x01, 0x00, // 0x110006180010E + 0x0C, 0x1E, 0x60, 0x64, 0x00, 0x10, 0x01, 0x00, // 0x1100064601E0C + 0x10, 0x1E, 0xE0, 0x64, 0x00, 0x10, 0x01, 0x00, // 0x1100064E01E10 + 0x12, 0x1E, 0xA0, 0x65, 0x00, 0x10, 0x01, 0x00, // 0x1100065A01E12 + 0x0E, 0x1E, 0x20, 0x66, 0x00, 0x10, 0x01, 0x00, // 0x1100066201E0E + 0xC8, 0x00, 0x00, 0x60, 0x00, 0x14, 0x01, 0x00, // 0x11400600000C8 + 0xC9, 0x00, 0x20, 0x60, 0x00, 0x14, 0x01, 0x00, // 0x11400602000C9 + 0xCA, 0x00, 0x40, 0x60, 0x00, 0x14, 0x01, 0x00, // 0x11400604000CA + 0xBC, 0x1E, 0x60, 0x60, 0x00, 0x14, 0x01, 0x00, // 0x1140060601EBC + 0x12, 0x01, 0x80, 0x60, 0x00, 0x14, 0x01, 0x00, // 0x1140060800112 + 0x14, 0x01, 0xC0, 0x60, 0x00, 0x14, 0x01, 0x00, // 0x1140060C00114 + 0x16, 0x01, 0xE0, 0x60, 0x00, 0x14, 0x01, 0x00, // 0x1140060E00116 + 0xCB, 0x00, 0x00, 0x61, 0x00, 0x14, 0x01, 0x00, // 0x11400610000CB + 0xBA, 0x1E, 0x20, 0x61, 0x00, 0x14, 0x01, 0x00, // 0x1140061201EBA + 0x1A, 0x01, 0x80, 0x61, 0x00, 0x14, 0x01, 0x00, // 0x114006180011A + 0x04, 0x02, 0xE0, 0x61, 0x00, 0x14, 0x01, 0x00, // 0x1140061E00204 + 0x06, 0x02, 0x20, 0x62, 0x00, 0x14, 0x01, 0x00, // 0x1140062200206 + 0xB8, 0x1E, 0x60, 0x64, 0x00, 0x14, 0x01, 0x00, // 0x1140064601EB8 + 0x28, 0x02, 0xE0, 0x64, 0x00, 0x14, 0x01, 0x00, // 0x1140064E00228 + 0x18, 0x01, 0x00, 0x65, 0x00, 0x14, 0x01, 0x00, // 0x1140065000118 + 0x18, 0x1E, 0xA0, 0x65, 0x00, 0x14, 0x01, 0x00, // 0x1140065A01E18 + 0x1A, 0x1E, 0x00, 0x66, 0x00, 0x14, 0x01, 0x00, // 0x1140066001E1A + 0x1E, 0x1E, 0xE0, 0x60, 0x00, 0x18, 0x01, 0x00, // 0x1180060E01E1E + 0xF4, 0x01, 0x20, 0x60, 0x00, 0x1C, 0x01, 0x00, // 0x11C00602001F4 + 0x1C, 0x01, 0x40, 0x60, 0x00, 0x1C, 0x01, 0x00, // 0x11C006040011C + 0x20, 0x1E, 0x80, 0x60, 0x00, 0x1C, 0x01, 0x00, // 0x11C0060801E20 + 0x1E, 0x01, 0xC0, 0x60, 0x00, 0x1C, 0x01, 0x00, // 0x11C0060C0011E + 0x20, 0x01, 0xE0, 0x60, 0x00, 0x1C, 0x01, 0x00, // 0x11C0060E00120 + 0xE6, 0x01, 0x80, 0x61, 0x00, 0x1C, 0x01, 0x00, // 0x11C00618001E6 + 0x22, 0x01, 0xE0, 0x64, 0x00, 0x1C, 0x01, 0x00, // 0x11C0064E00122 + 0x24, 0x01, 0x40, 0x60, 0x00, 0x20, 0x01, 0x00, // 0x1200060400124 + 0x22, 0x1E, 0xE0, 0x60, 0x00, 0x20, 0x01, 0x00, // 0x1200060E01E22 + 0x26, 0x1E, 0x00, 0x61, 0x00, 0x20, 0x01, 0x00, // 0x1200061001E26 + 0x1E, 0x02, 0x80, 0x61, 0x00, 0x20, 0x01, 0x00, // 0x120006180021E + 0x24, 0x1E, 0x60, 0x64, 0x00, 0x20, 0x01, 0x00, // 0x1200064601E24 + 0x28, 0x1E, 0xE0, 0x64, 0x00, 0x20, 0x01, 0x00, // 0x1200064E01E28 + 0x2A, 0x1E, 0xC0, 0x65, 0x00, 0x20, 0x01, 0x00, // 0x1200065C01E2A + 0xCC, 0x00, 0x00, 0x60, 0x00, 0x24, 0x01, 0x00, // 0x12400600000CC + 0xCD, 0x00, 0x20, 0x60, 0x00, 0x24, 0x01, 0x00, // 0x12400602000CD + 0xCE, 0x00, 0x40, 0x60, 0x00, 0x24, 0x01, 0x00, // 0x12400604000CE + 0x28, 0x01, 0x60, 0x60, 0x00, 0x24, 0x01, 0x00, // 0x1240060600128 + 0x2A, 0x01, 0x80, 0x60, 0x00, 0x24, 0x01, 0x00, // 0x124006080012A + 0x2C, 0x01, 0xC0, 0x60, 0x00, 0x24, 0x01, 0x00, // 0x1240060C0012C + 0x30, 0x01, 0xE0, 0x60, 0x00, 0x24, 0x01, 0x00, // 0x1240060E00130 + 0xCF, 0x00, 0x00, 0x61, 0x00, 0x24, 0x01, 0x00, // 0x12400610000CF + 0xC8, 0x1E, 0x20, 0x61, 0x00, 0x24, 0x01, 0x00, // 0x1240061201EC8 + 0xCF, 0x01, 0x80, 0x61, 0x00, 0x24, 0x01, 0x00, // 0x12400618001CF + 0x08, 0x02, 0xE0, 0x61, 0x00, 0x24, 0x01, 0x00, // 0x1240061E00208 + 0x0A, 0x02, 0x20, 0x62, 0x00, 0x24, 0x01, 0x00, // 0x124006220020A + 0xCA, 0x1E, 0x60, 0x64, 0x00, 0x24, 0x01, 0x00, // 0x1240064601ECA + 0x2E, 0x01, 0x00, 0x65, 0x00, 0x24, 0x01, 0x00, // 0x124006500012E + 0x2C, 0x1E, 0x00, 0x66, 0x00, 0x24, 0x01, 0x00, // 0x1240066001E2C + 0x34, 0x01, 0x40, 0x60, 0x00, 0x28, 0x01, 0x00, // 0x1280060400134 + 0x30, 0x1E, 0x20, 0x60, 0x00, 0x2C, 0x01, 0x00, // 0x12C0060201E30 + 0xE8, 0x01, 0x80, 0x61, 0x00, 0x2C, 0x01, 0x00, // 0x12C00618001E8 + 0x32, 0x1E, 0x60, 0x64, 0x00, 0x2C, 0x01, 0x00, // 0x12C0064601E32 + 0x36, 0x01, 0xE0, 0x64, 0x00, 0x2C, 0x01, 0x00, // 0x12C0064E00136 + 0x34, 0x1E, 0x20, 0x66, 0x00, 0x2C, 0x01, 0x00, // 0x12C0066201E34 + 0x39, 0x01, 0x20, 0x60, 0x00, 0x30, 0x01, 0x00, // 0x1300060200139 + 0x3D, 0x01, 0x80, 0x61, 0x00, 0x30, 0x01, 0x00, // 0x130006180013D + 0x36, 0x1E, 0x60, 0x64, 0x00, 0x30, 0x01, 0x00, // 0x1300064601E36 + 0x3B, 0x01, 0xE0, 0x64, 0x00, 0x30, 0x01, 0x00, // 0x1300064E0013B + 0x3C, 0x1E, 0xA0, 0x65, 0x00, 0x30, 0x01, 0x00, // 0x1300065A01E3C + 0x3A, 0x1E, 0x20, 0x66, 0x00, 0x30, 0x01, 0x00, // 0x1300066201E3A + 0x3E, 0x1E, 0x20, 0x60, 0x00, 0x34, 0x01, 0x00, // 0x1340060201E3E + 0x40, 0x1E, 0xE0, 0x60, 0x00, 0x34, 0x01, 0x00, // 0x1340060E01E40 + 0x42, 0x1E, 0x60, 0x64, 0x00, 0x34, 0x01, 0x00, // 0x1340064601E42 + 0xF8, 0x01, 0x00, 0x60, 0x00, 0x38, 0x01, 0x00, // 0x13800600001F8 + 0x43, 0x01, 0x20, 0x60, 0x00, 0x38, 0x01, 0x00, // 0x1380060200143 + 0xD1, 0x00, 0x60, 0x60, 0x00, 0x38, 0x01, 0x00, // 0x13800606000D1 + 0x44, 0x1E, 0xE0, 0x60, 0x00, 0x38, 0x01, 0x00, // 0x1380060E01E44 + 0x47, 0x01, 0x80, 0x61, 0x00, 0x38, 0x01, 0x00, // 0x1380061800147 + 0x46, 0x1E, 0x60, 0x64, 0x00, 0x38, 0x01, 0x00, // 0x1380064601E46 + 0x45, 0x01, 0xE0, 0x64, 0x00, 0x38, 0x01, 0x00, // 0x1380064E00145 + 0x4A, 0x1E, 0xA0, 0x65, 0x00, 0x38, 0x01, 0x00, // 0x1380065A01E4A + 0x48, 0x1E, 0x20, 0x66, 0x00, 0x38, 0x01, 0x00, // 0x1380066201E48 + 0xD2, 0x00, 0x00, 0x60, 0x00, 0x3C, 0x01, 0x00, // 0x13C00600000D2 + 0xD3, 0x00, 0x20, 0x60, 0x00, 0x3C, 0x01, 0x00, // 0x13C00602000D3 + 0xD4, 0x00, 0x40, 0x60, 0x00, 0x3C, 0x01, 0x00, // 0x13C00604000D4 + 0xD5, 0x00, 0x60, 0x60, 0x00, 0x3C, 0x01, 0x00, // 0x13C00606000D5 + 0x4C, 0x01, 0x80, 0x60, 0x00, 0x3C, 0x01, 0x00, // 0x13C006080014C + 0x4E, 0x01, 0xC0, 0x60, 0x00, 0x3C, 0x01, 0x00, // 0x13C0060C0014E + 0x2E, 0x02, 0xE0, 0x60, 0x00, 0x3C, 0x01, 0x00, // 0x13C0060E0022E + 0xD6, 0x00, 0x00, 0x61, 0x00, 0x3C, 0x01, 0x00, // 0x13C00610000D6 + 0xCE, 0x1E, 0x20, 0x61, 0x00, 0x3C, 0x01, 0x00, // 0x13C0061201ECE + 0x50, 0x01, 0x60, 0x61, 0x00, 0x3C, 0x01, 0x00, // 0x13C0061600150 + 0xD1, 0x01, 0x80, 0x61, 0x00, 0x3C, 0x01, 0x00, // 0x13C00618001D1 + 0x0C, 0x02, 0xE0, 0x61, 0x00, 0x3C, 0x01, 0x00, // 0x13C0061E0020C + 0x0E, 0x02, 0x20, 0x62, 0x00, 0x3C, 0x01, 0x00, // 0x13C006220020E + 0xA0, 0x01, 0x60, 0x63, 0x00, 0x3C, 0x01, 0x00, // 0x13C00636001A0 + 0xCC, 0x1E, 0x60, 0x64, 0x00, 0x3C, 0x01, 0x00, // 0x13C0064601ECC + 0xEA, 0x01, 0x00, 0x65, 0x00, 0x3C, 0x01, 0x00, // 0x13C00650001EA + 0x54, 0x1E, 0x20, 0x60, 0x00, 0x40, 0x01, 0x00, // 0x1400060201E54 + 0x56, 0x1E, 0xE0, 0x60, 0x00, 0x40, 0x01, 0x00, // 0x1400060E01E56 + 0x54, 0x01, 0x20, 0x60, 0x00, 0x48, 0x01, 0x00, // 0x1480060200154 + 0x58, 0x1E, 0xE0, 0x60, 0x00, 0x48, 0x01, 0x00, // 0x1480060E01E58 + 0x58, 0x01, 0x80, 0x61, 0x00, 0x48, 0x01, 0x00, // 0x1480061800158 + 0x10, 0x02, 0xE0, 0x61, 0x00, 0x48, 0x01, 0x00, // 0x1480061E00210 + 0x12, 0x02, 0x20, 0x62, 0x00, 0x48, 0x01, 0x00, // 0x1480062200212 + 0x5A, 0x1E, 0x60, 0x64, 0x00, 0x48, 0x01, 0x00, // 0x1480064601E5A + 0x56, 0x01, 0xE0, 0x64, 0x00, 0x48, 0x01, 0x00, // 0x1480064E00156 + 0x5E, 0x1E, 0x20, 0x66, 0x00, 0x48, 0x01, 0x00, // 0x1480066201E5E + 0x5A, 0x01, 0x20, 0x60, 0x00, 0x4C, 0x01, 0x00, // 0x14C006020015A + 0x5C, 0x01, 0x40, 0x60, 0x00, 0x4C, 0x01, 0x00, // 0x14C006040015C + 0x60, 0x1E, 0xE0, 0x60, 0x00, 0x4C, 0x01, 0x00, // 0x14C0060E01E60 + 0x60, 0x01, 0x80, 0x61, 0x00, 0x4C, 0x01, 0x00, // 0x14C0061800160 + 0x62, 0x1E, 0x60, 0x64, 0x00, 0x4C, 0x01, 0x00, // 0x14C0064601E62 + 0x18, 0x02, 0xC0, 0x64, 0x00, 0x4C, 0x01, 0x00, // 0x14C0064C00218 + 0x5E, 0x01, 0xE0, 0x64, 0x00, 0x4C, 0x01, 0x00, // 0x14C0064E0015E + 0x6A, 0x1E, 0xE0, 0x60, 0x00, 0x50, 0x01, 0x00, // 0x1500060E01E6A + 0x64, 0x01, 0x80, 0x61, 0x00, 0x50, 0x01, 0x00, // 0x1500061800164 + 0x6C, 0x1E, 0x60, 0x64, 0x00, 0x50, 0x01, 0x00, // 0x1500064601E6C + 0x1A, 0x02, 0xC0, 0x64, 0x00, 0x50, 0x01, 0x00, // 0x1500064C0021A + 0x62, 0x01, 0xE0, 0x64, 0x00, 0x50, 0x01, 0x00, // 0x1500064E00162 + 0x70, 0x1E, 0xA0, 0x65, 0x00, 0x50, 0x01, 0x00, // 0x1500065A01E70 + 0x6E, 0x1E, 0x20, 0x66, 0x00, 0x50, 0x01, 0x00, // 0x1500066201E6E + 0xD9, 0x00, 0x00, 0x60, 0x00, 0x54, 0x01, 0x00, // 0x15400600000D9 + 0xDA, 0x00, 0x20, 0x60, 0x00, 0x54, 0x01, 0x00, // 0x15400602000DA + 0xDB, 0x00, 0x40, 0x60, 0x00, 0x54, 0x01, 0x00, // 0x15400604000DB + 0x68, 0x01, 0x60, 0x60, 0x00, 0x54, 0x01, 0x00, // 0x1540060600168 + 0x6A, 0x01, 0x80, 0x60, 0x00, 0x54, 0x01, 0x00, // 0x154006080016A + 0x6C, 0x01, 0xC0, 0x60, 0x00, 0x54, 0x01, 0x00, // 0x1540060C0016C + 0xDC, 0x00, 0x00, 0x61, 0x00, 0x54, 0x01, 0x00, // 0x15400610000DC + 0xE6, 0x1E, 0x20, 0x61, 0x00, 0x54, 0x01, 0x00, // 0x1540061201EE6 + 0x6E, 0x01, 0x40, 0x61, 0x00, 0x54, 0x01, 0x00, // 0x154006140016E + 0x70, 0x01, 0x60, 0x61, 0x00, 0x54, 0x01, 0x00, // 0x1540061600170 + 0xD3, 0x01, 0x80, 0x61, 0x00, 0x54, 0x01, 0x00, // 0x15400618001D3 + 0x14, 0x02, 0xE0, 0x61, 0x00, 0x54, 0x01, 0x00, // 0x1540061E00214 + 0x16, 0x02, 0x20, 0x62, 0x00, 0x54, 0x01, 0x00, // 0x1540062200216 + 0xAF, 0x01, 0x60, 0x63, 0x00, 0x54, 0x01, 0x00, // 0x15400636001AF + 0xE4, 0x1E, 0x60, 0x64, 0x00, 0x54, 0x01, 0x00, // 0x1540064601EE4 + 0x72, 0x1E, 0x80, 0x64, 0x00, 0x54, 0x01, 0x00, // 0x1540064801E72 + 0x72, 0x01, 0x00, 0x65, 0x00, 0x54, 0x01, 0x00, // 0x1540065000172 + 0x76, 0x1E, 0xA0, 0x65, 0x00, 0x54, 0x01, 0x00, // 0x1540065A01E76 + 0x74, 0x1E, 0x00, 0x66, 0x00, 0x54, 0x01, 0x00, // 0x1540066001E74 + 0x7C, 0x1E, 0x60, 0x60, 0x00, 0x58, 0x01, 0x00, // 0x1580060601E7C + 0x7E, 0x1E, 0x60, 0x64, 0x00, 0x58, 0x01, 0x00, // 0x1580064601E7E + 0x80, 0x1E, 0x00, 0x60, 0x00, 0x5C, 0x01, 0x00, // 0x15C0060001E80 + 0x82, 0x1E, 0x20, 0x60, 0x00, 0x5C, 0x01, 0x00, // 0x15C0060201E82 + 0x74, 0x01, 0x40, 0x60, 0x00, 0x5C, 0x01, 0x00, // 0x15C0060400174 + 0x86, 0x1E, 0xE0, 0x60, 0x00, 0x5C, 0x01, 0x00, // 0x15C0060E01E86 + 0x84, 0x1E, 0x00, 0x61, 0x00, 0x5C, 0x01, 0x00, // 0x15C0061001E84 + 0x88, 0x1E, 0x60, 0x64, 0x00, 0x5C, 0x01, 0x00, // 0x15C0064601E88 + 0x8A, 0x1E, 0xE0, 0x60, 0x00, 0x60, 0x01, 0x00, // 0x1600060E01E8A + 0x8C, 0x1E, 0x00, 0x61, 0x00, 0x60, 0x01, 0x00, // 0x1600061001E8C + 0xF2, 0x1E, 0x00, 0x60, 0x00, 0x64, 0x01, 0x00, // 0x1640060001EF2 + 0xDD, 0x00, 0x20, 0x60, 0x00, 0x64, 0x01, 0x00, // 0x16400602000DD + 0x76, 0x01, 0x40, 0x60, 0x00, 0x64, 0x01, 0x00, // 0x1640060400176 + 0xF8, 0x1E, 0x60, 0x60, 0x00, 0x64, 0x01, 0x00, // 0x1640060601EF8 + 0x32, 0x02, 0x80, 0x60, 0x00, 0x64, 0x01, 0x00, // 0x1640060800232 + 0x8E, 0x1E, 0xE0, 0x60, 0x00, 0x64, 0x01, 0x00, // 0x1640060E01E8E + 0x78, 0x01, 0x00, 0x61, 0x00, 0x64, 0x01, 0x00, // 0x1640061000178 + 0xF6, 0x1E, 0x20, 0x61, 0x00, 0x64, 0x01, 0x00, // 0x1640061201EF6 + 0xF4, 0x1E, 0x60, 0x64, 0x00, 0x64, 0x01, 0x00, // 0x1640064601EF4 + 0x79, 0x01, 0x20, 0x60, 0x00, 0x68, 0x01, 0x00, // 0x1680060200179 + 0x90, 0x1E, 0x40, 0x60, 0x00, 0x68, 0x01, 0x00, // 0x1680060401E90 + 0x7B, 0x01, 0xE0, 0x60, 0x00, 0x68, 0x01, 0x00, // 0x1680060E0017B + 0x7D, 0x01, 0x80, 0x61, 0x00, 0x68, 0x01, 0x00, // 0x168006180017D + 0x92, 0x1E, 0x60, 0x64, 0x00, 0x68, 0x01, 0x00, // 0x1680064601E92 + 0x94, 0x1E, 0x20, 0x66, 0x00, 0x68, 0x01, 0x00, // 0x1680066201E94 + 0xE0, 0x00, 0x00, 0x60, 0x00, 0x84, 0x01, 0x00, // 0x18400600000E0 + 0xE1, 0x00, 0x20, 0x60, 0x00, 0x84, 0x01, 0x00, // 0x18400602000E1 + 0xE2, 0x00, 0x40, 0x60, 0x00, 0x84, 0x01, 0x00, // 0x18400604000E2 + 0xE3, 0x00, 0x60, 0x60, 0x00, 0x84, 0x01, 0x00, // 0x18400606000E3 + 0x01, 0x01, 0x80, 0x60, 0x00, 0x84, 0x01, 0x00, // 0x1840060800101 + 0x03, 0x01, 0xC0, 0x60, 0x00, 0x84, 0x01, 0x00, // 0x1840060C00103 + 0x27, 0x02, 0xE0, 0x60, 0x00, 0x84, 0x01, 0x00, // 0x1840060E00227 + 0xE4, 0x00, 0x00, 0x61, 0x00, 0x84, 0x01, 0x00, // 0x18400610000E4 + 0xA3, 0x1E, 0x20, 0x61, 0x00, 0x84, 0x01, 0x00, // 0x1840061201EA3 + 0xE5, 0x00, 0x40, 0x61, 0x00, 0x84, 0x01, 0x00, // 0x18400614000E5 + 0xCE, 0x01, 0x80, 0x61, 0x00, 0x84, 0x01, 0x00, // 0x18400618001CE + 0x01, 0x02, 0xE0, 0x61, 0x00, 0x84, 0x01, 0x00, // 0x1840061E00201 + 0x03, 0x02, 0x20, 0x62, 0x00, 0x84, 0x01, 0x00, // 0x1840062200203 + 0xA1, 0x1E, 0x60, 0x64, 0x00, 0x84, 0x01, 0x00, // 0x1840064601EA1 + 0x01, 0x1E, 0xA0, 0x64, 0x00, 0x84, 0x01, 0x00, // 0x1840064A01E01 + 0x05, 0x01, 0x00, 0x65, 0x00, 0x84, 0x01, 0x00, // 0x1840065000105 + 0x03, 0x1E, 0xE0, 0x60, 0x00, 0x88, 0x01, 0x00, // 0x1880060E01E03 + 0x05, 0x1E, 0x60, 0x64, 0x00, 0x88, 0x01, 0x00, // 0x1880064601E05 + 0x07, 0x1E, 0x20, 0x66, 0x00, 0x88, 0x01, 0x00, // 0x1880066201E07 + 0x07, 0x01, 0x20, 0x60, 0x00, 0x8C, 0x01, 0x00, // 0x18C0060200107 + 0x09, 0x01, 0x40, 0x60, 0x00, 0x8C, 0x01, 0x00, // 0x18C0060400109 + 0x0B, 0x01, 0xE0, 0x60, 0x00, 0x8C, 0x01, 0x00, // 0x18C0060E0010B + 0x0D, 0x01, 0x80, 0x61, 0x00, 0x8C, 0x01, 0x00, // 0x18C006180010D + 0xE7, 0x00, 0xE0, 0x64, 0x00, 0x8C, 0x01, 0x00, // 0x18C0064E000E7 + 0x0B, 0x1E, 0xE0, 0x60, 0x00, 0x90, 0x01, 0x00, // 0x1900060E01E0B + 0x0F, 0x01, 0x80, 0x61, 0x00, 0x90, 0x01, 0x00, // 0x190006180010F + 0x0D, 0x1E, 0x60, 0x64, 0x00, 0x90, 0x01, 0x00, // 0x1900064601E0D + 0x11, 0x1E, 0xE0, 0x64, 0x00, 0x90, 0x01, 0x00, // 0x1900064E01E11 + 0x13, 0x1E, 0xA0, 0x65, 0x00, 0x90, 0x01, 0x00, // 0x1900065A01E13 + 0x0F, 0x1E, 0x20, 0x66, 0x00, 0x90, 0x01, 0x00, // 0x1900066201E0F + 0xE8, 0x00, 0x00, 0x60, 0x00, 0x94, 0x01, 0x00, // 0x19400600000E8 + 0xE9, 0x00, 0x20, 0x60, 0x00, 0x94, 0x01, 0x00, // 0x19400602000E9 + 0xEA, 0x00, 0x40, 0x60, 0x00, 0x94, 0x01, 0x00, // 0x19400604000EA + 0xBD, 0x1E, 0x60, 0x60, 0x00, 0x94, 0x01, 0x00, // 0x1940060601EBD + 0x13, 0x01, 0x80, 0x60, 0x00, 0x94, 0x01, 0x00, // 0x1940060800113 + 0x15, 0x01, 0xC0, 0x60, 0x00, 0x94, 0x01, 0x00, // 0x1940060C00115 + 0x17, 0x01, 0xE0, 0x60, 0x00, 0x94, 0x01, 0x00, // 0x1940060E00117 + 0xEB, 0x00, 0x00, 0x61, 0x00, 0x94, 0x01, 0x00, // 0x19400610000EB + 0xBB, 0x1E, 0x20, 0x61, 0x00, 0x94, 0x01, 0x00, // 0x1940061201EBB + 0x1B, 0x01, 0x80, 0x61, 0x00, 0x94, 0x01, 0x00, // 0x194006180011B + 0x05, 0x02, 0xE0, 0x61, 0x00, 0x94, 0x01, 0x00, // 0x1940061E00205 + 0x07, 0x02, 0x20, 0x62, 0x00, 0x94, 0x01, 0x00, // 0x1940062200207 + 0xB9, 0x1E, 0x60, 0x64, 0x00, 0x94, 0x01, 0x00, // 0x1940064601EB9 + 0x29, 0x02, 0xE0, 0x64, 0x00, 0x94, 0x01, 0x00, // 0x1940064E00229 + 0x19, 0x01, 0x00, 0x65, 0x00, 0x94, 0x01, 0x00, // 0x1940065000119 + 0x19, 0x1E, 0xA0, 0x65, 0x00, 0x94, 0x01, 0x00, // 0x1940065A01E19 + 0x1B, 0x1E, 0x00, 0x66, 0x00, 0x94, 0x01, 0x00, // 0x1940066001E1B + 0x1F, 0x1E, 0xE0, 0x60, 0x00, 0x98, 0x01, 0x00, // 0x1980060E01E1F + 0xF5, 0x01, 0x20, 0x60, 0x00, 0x9C, 0x01, 0x00, // 0x19C00602001F5 + 0x1D, 0x01, 0x40, 0x60, 0x00, 0x9C, 0x01, 0x00, // 0x19C006040011D + 0x21, 0x1E, 0x80, 0x60, 0x00, 0x9C, 0x01, 0x00, // 0x19C0060801E21 + 0x1F, 0x01, 0xC0, 0x60, 0x00, 0x9C, 0x01, 0x00, // 0x19C0060C0011F + 0x21, 0x01, 0xE0, 0x60, 0x00, 0x9C, 0x01, 0x00, // 0x19C0060E00121 + 0xE7, 0x01, 0x80, 0x61, 0x00, 0x9C, 0x01, 0x00, // 0x19C00618001E7 + 0x23, 0x01, 0xE0, 0x64, 0x00, 0x9C, 0x01, 0x00, // 0x19C0064E00123 + 0x25, 0x01, 0x40, 0x60, 0x00, 0xA0, 0x01, 0x00, // 0x1A00060400125 + 0x23, 0x1E, 0xE0, 0x60, 0x00, 0xA0, 0x01, 0x00, // 0x1A00060E01E23 + 0x27, 0x1E, 0x00, 0x61, 0x00, 0xA0, 0x01, 0x00, // 0x1A00061001E27 + 0x1F, 0x02, 0x80, 0x61, 0x00, 0xA0, 0x01, 0x00, // 0x1A0006180021F + 0x25, 0x1E, 0x60, 0x64, 0x00, 0xA0, 0x01, 0x00, // 0x1A00064601E25 + 0x29, 0x1E, 0xE0, 0x64, 0x00, 0xA0, 0x01, 0x00, // 0x1A00064E01E29 + 0x2B, 0x1E, 0xC0, 0x65, 0x00, 0xA0, 0x01, 0x00, // 0x1A00065C01E2B + 0x96, 0x1E, 0x20, 0x66, 0x00, 0xA0, 0x01, 0x00, // 0x1A00066201E96 + 0xEC, 0x00, 0x00, 0x60, 0x00, 0xA4, 0x01, 0x00, // 0x1A400600000EC + 0xED, 0x00, 0x20, 0x60, 0x00, 0xA4, 0x01, 0x00, // 0x1A400602000ED + 0xEE, 0x00, 0x40, 0x60, 0x00, 0xA4, 0x01, 0x00, // 0x1A400604000EE + 0x29, 0x01, 0x60, 0x60, 0x00, 0xA4, 0x01, 0x00, // 0x1A40060600129 + 0x2B, 0x01, 0x80, 0x60, 0x00, 0xA4, 0x01, 0x00, // 0x1A4006080012B + 0x2D, 0x01, 0xC0, 0x60, 0x00, 0xA4, 0x01, 0x00, // 0x1A40060C0012D + 0xEF, 0x00, 0x00, 0x61, 0x00, 0xA4, 0x01, 0x00, // 0x1A400610000EF + 0xC9, 0x1E, 0x20, 0x61, 0x00, 0xA4, 0x01, 0x00, // 0x1A40061201EC9 + 0xD0, 0x01, 0x80, 0x61, 0x00, 0xA4, 0x01, 0x00, // 0x1A400618001D0 + 0x09, 0x02, 0xE0, 0x61, 0x00, 0xA4, 0x01, 0x00, // 0x1A40061E00209 + 0x0B, 0x02, 0x20, 0x62, 0x00, 0xA4, 0x01, 0x00, // 0x1A4006220020B + 0xCB, 0x1E, 0x60, 0x64, 0x00, 0xA4, 0x01, 0x00, // 0x1A40064601ECB + 0x2F, 0x01, 0x00, 0x65, 0x00, 0xA4, 0x01, 0x00, // 0x1A4006500012F + 0x2D, 0x1E, 0x00, 0x66, 0x00, 0xA4, 0x01, 0x00, // 0x1A40066001E2D + 0x35, 0x01, 0x40, 0x60, 0x00, 0xA8, 0x01, 0x00, // 0x1A80060400135 + 0xF0, 0x01, 0x80, 0x61, 0x00, 0xA8, 0x01, 0x00, // 0x1A800618001F0 + 0x31, 0x1E, 0x20, 0x60, 0x00, 0xAC, 0x01, 0x00, // 0x1AC0060201E31 + 0xE9, 0x01, 0x80, 0x61, 0x00, 0xAC, 0x01, 0x00, // 0x1AC00618001E9 + 0x33, 0x1E, 0x60, 0x64, 0x00, 0xAC, 0x01, 0x00, // 0x1AC0064601E33 + 0x37, 0x01, 0xE0, 0x64, 0x00, 0xAC, 0x01, 0x00, // 0x1AC0064E00137 + 0x35, 0x1E, 0x20, 0x66, 0x00, 0xAC, 0x01, 0x00, // 0x1AC0066201E35 + 0x3A, 0x01, 0x20, 0x60, 0x00, 0xB0, 0x01, 0x00, // 0x1B0006020013A + 0x3E, 0x01, 0x80, 0x61, 0x00, 0xB0, 0x01, 0x00, // 0x1B0006180013E + 0x37, 0x1E, 0x60, 0x64, 0x00, 0xB0, 0x01, 0x00, // 0x1B00064601E37 + 0x3C, 0x01, 0xE0, 0x64, 0x00, 0xB0, 0x01, 0x00, // 0x1B00064E0013C + 0x3D, 0x1E, 0xA0, 0x65, 0x00, 0xB0, 0x01, 0x00, // 0x1B00065A01E3D + 0x3B, 0x1E, 0x20, 0x66, 0x00, 0xB0, 0x01, 0x00, // 0x1B00066201E3B + 0x3F, 0x1E, 0x20, 0x60, 0x00, 0xB4, 0x01, 0x00, // 0x1B40060201E3F + 0x41, 0x1E, 0xE0, 0x60, 0x00, 0xB4, 0x01, 0x00, // 0x1B40060E01E41 + 0x43, 0x1E, 0x60, 0x64, 0x00, 0xB4, 0x01, 0x00, // 0x1B40064601E43 + 0xF9, 0x01, 0x00, 0x60, 0x00, 0xB8, 0x01, 0x00, // 0x1B800600001F9 + 0x44, 0x01, 0x20, 0x60, 0x00, 0xB8, 0x01, 0x00, // 0x1B80060200144 + 0xF1, 0x00, 0x60, 0x60, 0x00, 0xB8, 0x01, 0x00, // 0x1B800606000F1 + 0x45, 0x1E, 0xE0, 0x60, 0x00, 0xB8, 0x01, 0x00, // 0x1B80060E01E45 + 0x48, 0x01, 0x80, 0x61, 0x00, 0xB8, 0x01, 0x00, // 0x1B80061800148 + 0x47, 0x1E, 0x60, 0x64, 0x00, 0xB8, 0x01, 0x00, // 0x1B80064601E47 + 0x46, 0x01, 0xE0, 0x64, 0x00, 0xB8, 0x01, 0x00, // 0x1B80064E00146 + 0x4B, 0x1E, 0xA0, 0x65, 0x00, 0xB8, 0x01, 0x00, // 0x1B80065A01E4B + 0x49, 0x1E, 0x20, 0x66, 0x00, 0xB8, 0x01, 0x00, // 0x1B80066201E49 + 0xF2, 0x00, 0x00, 0x60, 0x00, 0xBC, 0x01, 0x00, // 0x1BC00600000F2 + 0xF3, 0x00, 0x20, 0x60, 0x00, 0xBC, 0x01, 0x00, // 0x1BC00602000F3 + 0xF4, 0x00, 0x40, 0x60, 0x00, 0xBC, 0x01, 0x00, // 0x1BC00604000F4 + 0xF5, 0x00, 0x60, 0x60, 0x00, 0xBC, 0x01, 0x00, // 0x1BC00606000F5 + 0x4D, 0x01, 0x80, 0x60, 0x00, 0xBC, 0x01, 0x00, // 0x1BC006080014D + 0x4F, 0x01, 0xC0, 0x60, 0x00, 0xBC, 0x01, 0x00, // 0x1BC0060C0014F + 0x2F, 0x02, 0xE0, 0x60, 0x00, 0xBC, 0x01, 0x00, // 0x1BC0060E0022F + 0xF6, 0x00, 0x00, 0x61, 0x00, 0xBC, 0x01, 0x00, // 0x1BC00610000F6 + 0xCF, 0x1E, 0x20, 0x61, 0x00, 0xBC, 0x01, 0x00, // 0x1BC0061201ECF + 0x51, 0x01, 0x60, 0x61, 0x00, 0xBC, 0x01, 0x00, // 0x1BC0061600151 + 0xD2, 0x01, 0x80, 0x61, 0x00, 0xBC, 0x01, 0x00, // 0x1BC00618001D2 + 0x0D, 0x02, 0xE0, 0x61, 0x00, 0xBC, 0x01, 0x00, // 0x1BC0061E0020D + 0x0F, 0x02, 0x20, 0x62, 0x00, 0xBC, 0x01, 0x00, // 0x1BC006220020F + 0xA1, 0x01, 0x60, 0x63, 0x00, 0xBC, 0x01, 0x00, // 0x1BC00636001A1 + 0xCD, 0x1E, 0x60, 0x64, 0x00, 0xBC, 0x01, 0x00, // 0x1BC0064601ECD + 0xEB, 0x01, 0x00, 0x65, 0x00, 0xBC, 0x01, 0x00, // 0x1BC00650001EB + 0x55, 0x1E, 0x20, 0x60, 0x00, 0xC0, 0x01, 0x00, // 0x1C00060201E55 + 0x57, 0x1E, 0xE0, 0x60, 0x00, 0xC0, 0x01, 0x00, // 0x1C00060E01E57 + 0x55, 0x01, 0x20, 0x60, 0x00, 0xC8, 0x01, 0x00, // 0x1C80060200155 + 0x59, 0x1E, 0xE0, 0x60, 0x00, 0xC8, 0x01, 0x00, // 0x1C80060E01E59 + 0x59, 0x01, 0x80, 0x61, 0x00, 0xC8, 0x01, 0x00, // 0x1C80061800159 + 0x11, 0x02, 0xE0, 0x61, 0x00, 0xC8, 0x01, 0x00, // 0x1C80061E00211 + 0x13, 0x02, 0x20, 0x62, 0x00, 0xC8, 0x01, 0x00, // 0x1C80062200213 + 0x5B, 0x1E, 0x60, 0x64, 0x00, 0xC8, 0x01, 0x00, // 0x1C80064601E5B + 0x57, 0x01, 0xE0, 0x64, 0x00, 0xC8, 0x01, 0x00, // 0x1C80064E00157 + 0x5F, 0x1E, 0x20, 0x66, 0x00, 0xC8, 0x01, 0x00, // 0x1C80066201E5F + 0x5B, 0x01, 0x20, 0x60, 0x00, 0xCC, 0x01, 0x00, // 0x1CC006020015B + 0x5D, 0x01, 0x40, 0x60, 0x00, 0xCC, 0x01, 0x00, // 0x1CC006040015D + 0x61, 0x1E, 0xE0, 0x60, 0x00, 0xCC, 0x01, 0x00, // 0x1CC0060E01E61 + 0x61, 0x01, 0x80, 0x61, 0x00, 0xCC, 0x01, 0x00, // 0x1CC0061800161 + 0x63, 0x1E, 0x60, 0x64, 0x00, 0xCC, 0x01, 0x00, // 0x1CC0064601E63 + 0x19, 0x02, 0xC0, 0x64, 0x00, 0xCC, 0x01, 0x00, // 0x1CC0064C00219 + 0x5F, 0x01, 0xE0, 0x64, 0x00, 0xCC, 0x01, 0x00, // 0x1CC0064E0015F + 0x6B, 0x1E, 0xE0, 0x60, 0x00, 0xD0, 0x01, 0x00, // 0x1D00060E01E6B + 0x97, 0x1E, 0x00, 0x61, 0x00, 0xD0, 0x01, 0x00, // 0x1D00061001E97 + 0x65, 0x01, 0x80, 0x61, 0x00, 0xD0, 0x01, 0x00, // 0x1D00061800165 + 0x6D, 0x1E, 0x60, 0x64, 0x00, 0xD0, 0x01, 0x00, // 0x1D00064601E6D + 0x1B, 0x02, 0xC0, 0x64, 0x00, 0xD0, 0x01, 0x00, // 0x1D00064C0021B + 0x63, 0x01, 0xE0, 0x64, 0x00, 0xD0, 0x01, 0x00, // 0x1D00064E00163 + 0x71, 0x1E, 0xA0, 0x65, 0x00, 0xD0, 0x01, 0x00, // 0x1D00065A01E71 + 0x6F, 0x1E, 0x20, 0x66, 0x00, 0xD0, 0x01, 0x00, // 0x1D00066201E6F + 0xF9, 0x00, 0x00, 0x60, 0x00, 0xD4, 0x01, 0x00, // 0x1D400600000F9 + 0xFA, 0x00, 0x20, 0x60, 0x00, 0xD4, 0x01, 0x00, // 0x1D400602000FA + 0xFB, 0x00, 0x40, 0x60, 0x00, 0xD4, 0x01, 0x00, // 0x1D400604000FB + 0x69, 0x01, 0x60, 0x60, 0x00, 0xD4, 0x01, 0x00, // 0x1D40060600169 + 0x6B, 0x01, 0x80, 0x60, 0x00, 0xD4, 0x01, 0x00, // 0x1D4006080016B + 0x6D, 0x01, 0xC0, 0x60, 0x00, 0xD4, 0x01, 0x00, // 0x1D40060C0016D + 0xFC, 0x00, 0x00, 0x61, 0x00, 0xD4, 0x01, 0x00, // 0x1D400610000FC + 0xE7, 0x1E, 0x20, 0x61, 0x00, 0xD4, 0x01, 0x00, // 0x1D40061201EE7 + 0x6F, 0x01, 0x40, 0x61, 0x00, 0xD4, 0x01, 0x00, // 0x1D4006140016F + 0x71, 0x01, 0x60, 0x61, 0x00, 0xD4, 0x01, 0x00, // 0x1D40061600171 + 0xD4, 0x01, 0x80, 0x61, 0x00, 0xD4, 0x01, 0x00, // 0x1D400618001D4 + 0x15, 0x02, 0xE0, 0x61, 0x00, 0xD4, 0x01, 0x00, // 0x1D40061E00215 + 0x17, 0x02, 0x20, 0x62, 0x00, 0xD4, 0x01, 0x00, // 0x1D40062200217 + 0xB0, 0x01, 0x60, 0x63, 0x00, 0xD4, 0x01, 0x00, // 0x1D400636001B0 + 0xE5, 0x1E, 0x60, 0x64, 0x00, 0xD4, 0x01, 0x00, // 0x1D40064601EE5 + 0x73, 0x1E, 0x80, 0x64, 0x00, 0xD4, 0x01, 0x00, // 0x1D40064801E73 + 0x73, 0x01, 0x00, 0x65, 0x00, 0xD4, 0x01, 0x00, // 0x1D40065000173 + 0x77, 0x1E, 0xA0, 0x65, 0x00, 0xD4, 0x01, 0x00, // 0x1D40065A01E77 + 0x75, 0x1E, 0x00, 0x66, 0x00, 0xD4, 0x01, 0x00, // 0x1D40066001E75 + 0x7D, 0x1E, 0x60, 0x60, 0x00, 0xD8, 0x01, 0x00, // 0x1D80060601E7D + 0x7F, 0x1E, 0x60, 0x64, 0x00, 0xD8, 0x01, 0x00, // 0x1D80064601E7F + 0x81, 0x1E, 0x00, 0x60, 0x00, 0xDC, 0x01, 0x00, // 0x1DC0060001E81 + 0x83, 0x1E, 0x20, 0x60, 0x00, 0xDC, 0x01, 0x00, // 0x1DC0060201E83 + 0x75, 0x01, 0x40, 0x60, 0x00, 0xDC, 0x01, 0x00, // 0x1DC0060400175 + 0x87, 0x1E, 0xE0, 0x60, 0x00, 0xDC, 0x01, 0x00, // 0x1DC0060E01E87 + 0x85, 0x1E, 0x00, 0x61, 0x00, 0xDC, 0x01, 0x00, // 0x1DC0061001E85 + 0x98, 0x1E, 0x40, 0x61, 0x00, 0xDC, 0x01, 0x00, // 0x1DC0061401E98 + 0x89, 0x1E, 0x60, 0x64, 0x00, 0xDC, 0x01, 0x00, // 0x1DC0064601E89 + 0x8B, 0x1E, 0xE0, 0x60, 0x00, 0xE0, 0x01, 0x00, // 0x1E00060E01E8B + 0x8D, 0x1E, 0x00, 0x61, 0x00, 0xE0, 0x01, 0x00, // 0x1E00061001E8D + 0xF3, 0x1E, 0x00, 0x60, 0x00, 0xE4, 0x01, 0x00, // 0x1E40060001EF3 + 0xFD, 0x00, 0x20, 0x60, 0x00, 0xE4, 0x01, 0x00, // 0x1E400602000FD + 0x77, 0x01, 0x40, 0x60, 0x00, 0xE4, 0x01, 0x00, // 0x1E40060400177 + 0xF9, 0x1E, 0x60, 0x60, 0x00, 0xE4, 0x01, 0x00, // 0x1E40060601EF9 + 0x33, 0x02, 0x80, 0x60, 0x00, 0xE4, 0x01, 0x00, // 0x1E40060800233 + 0x8F, 0x1E, 0xE0, 0x60, 0x00, 0xE4, 0x01, 0x00, // 0x1E40060E01E8F + 0xFF, 0x00, 0x00, 0x61, 0x00, 0xE4, 0x01, 0x00, // 0x1E400610000FF + 0xF7, 0x1E, 0x20, 0x61, 0x00, 0xE4, 0x01, 0x00, // 0x1E40061201EF7 + 0x99, 0x1E, 0x40, 0x61, 0x00, 0xE4, 0x01, 0x00, // 0x1E40061401E99 + 0xF5, 0x1E, 0x60, 0x64, 0x00, 0xE4, 0x01, 0x00, // 0x1E40064601EF5 + 0x7A, 0x01, 0x20, 0x60, 0x00, 0xE8, 0x01, 0x00, // 0x1E8006020017A + 0x91, 0x1E, 0x40, 0x60, 0x00, 0xE8, 0x01, 0x00, // 0x1E80060401E91 + 0x7C, 0x01, 0xE0, 0x60, 0x00, 0xE8, 0x01, 0x00, // 0x1E80060E0017C + 0x7E, 0x01, 0x80, 0x61, 0x00, 0xE8, 0x01, 0x00, // 0x1E8006180017E + 0x93, 0x1E, 0x60, 0x64, 0x00, 0xE8, 0x01, 0x00, // 0x1E80064601E93 + 0x95, 0x1E, 0x20, 0x66, 0x00, 0xE8, 0x01, 0x00, // 0x1E80066201E95 + 0xED, 0x1F, 0x00, 0x60, 0x00, 0xA0, 0x02, 0x00, // 0x2A00060001FED + 0x85, 0x03, 0x20, 0x60, 0x00, 0xA0, 0x02, 0x00, // 0x2A00060200385 + 0xC1, 0x1F, 0x40, 0x68, 0x00, 0xA0, 0x02, 0x00, // 0x2A00068401FC1 + 0xA6, 0x1E, 0x00, 0x60, 0x00, 0x08, 0x03, 0x00, // 0x3080060001EA6 + 0xA4, 0x1E, 0x20, 0x60, 0x00, 0x08, 0x03, 0x00, // 0x3080060201EA4 + 0xAA, 0x1E, 0x60, 0x60, 0x00, 0x08, 0x03, 0x00, // 0x3080060601EAA + 0xA8, 0x1E, 0x20, 0x61, 0x00, 0x08, 0x03, 0x00, // 0x3080061201EA8 + 0xDE, 0x01, 0x80, 0x60, 0x00, 0x10, 0x03, 0x00, // 0x31000608001DE + 0xFA, 0x01, 0x20, 0x60, 0x00, 0x14, 0x03, 0x00, // 0x31400602001FA + 0xFC, 0x01, 0x20, 0x60, 0x00, 0x18, 0x03, 0x00, // 0x31800602001FC + 0xE2, 0x01, 0x80, 0x60, 0x00, 0x18, 0x03, 0x00, // 0x31800608001E2 + 0x08, 0x1E, 0x20, 0x60, 0x00, 0x1C, 0x03, 0x00, // 0x31C0060201E08 + 0xC0, 0x1E, 0x00, 0x60, 0x00, 0x28, 0x03, 0x00, // 0x3280060001EC0 + 0xBE, 0x1E, 0x20, 0x60, 0x00, 0x28, 0x03, 0x00, // 0x3280060201EBE + 0xC4, 0x1E, 0x60, 0x60, 0x00, 0x28, 0x03, 0x00, // 0x3280060601EC4 + 0xC2, 0x1E, 0x20, 0x61, 0x00, 0x28, 0x03, 0x00, // 0x3280061201EC2 + 0x2E, 0x1E, 0x20, 0x60, 0x00, 0x3C, 0x03, 0x00, // 0x33C0060201E2E + 0xD2, 0x1E, 0x00, 0x60, 0x00, 0x50, 0x03, 0x00, // 0x3500060001ED2 + 0xD0, 0x1E, 0x20, 0x60, 0x00, 0x50, 0x03, 0x00, // 0x3500060201ED0 + 0xD6, 0x1E, 0x60, 0x60, 0x00, 0x50, 0x03, 0x00, // 0x3500060601ED6 + 0xD4, 0x1E, 0x20, 0x61, 0x00, 0x50, 0x03, 0x00, // 0x3500061201ED4 + 0x4C, 0x1E, 0x20, 0x60, 0x00, 0x54, 0x03, 0x00, // 0x3540060201E4C + 0x2C, 0x02, 0x80, 0x60, 0x00, 0x54, 0x03, 0x00, // 0x354006080022C + 0x4E, 0x1E, 0x00, 0x61, 0x00, 0x54, 0x03, 0x00, // 0x3540061001E4E + 0x2A, 0x02, 0x80, 0x60, 0x00, 0x58, 0x03, 0x00, // 0x358006080022A + 0xFE, 0x01, 0x20, 0x60, 0x00, 0x60, 0x03, 0x00, // 0x36000602001FE + 0xDB, 0x01, 0x00, 0x60, 0x00, 0x70, 0x03, 0x00, // 0x37000600001DB + 0xD7, 0x01, 0x20, 0x60, 0x00, 0x70, 0x03, 0x00, // 0x37000602001D7 + 0xD5, 0x01, 0x80, 0x60, 0x00, 0x70, 0x03, 0x00, // 0x37000608001D5 + 0xD9, 0x01, 0x80, 0x61, 0x00, 0x70, 0x03, 0x00, // 0x37000618001D9 + 0xA7, 0x1E, 0x00, 0x60, 0x00, 0x88, 0x03, 0x00, // 0x3880060001EA7 + 0xA5, 0x1E, 0x20, 0x60, 0x00, 0x88, 0x03, 0x00, // 0x3880060201EA5 + 0xAB, 0x1E, 0x60, 0x60, 0x00, 0x88, 0x03, 0x00, // 0x3880060601EAB + 0xA9, 0x1E, 0x20, 0x61, 0x00, 0x88, 0x03, 0x00, // 0x3880061201EA9 + 0xDF, 0x01, 0x80, 0x60, 0x00, 0x90, 0x03, 0x00, // 0x39000608001DF + 0xFB, 0x01, 0x20, 0x60, 0x00, 0x94, 0x03, 0x00, // 0x39400602001FB + 0xFD, 0x01, 0x20, 0x60, 0x00, 0x98, 0x03, 0x00, // 0x39800602001FD + 0xE3, 0x01, 0x80, 0x60, 0x00, 0x98, 0x03, 0x00, // 0x39800608001E3 + 0x09, 0x1E, 0x20, 0x60, 0x00, 0x9C, 0x03, 0x00, // 0x39C0060201E09 + 0xC1, 0x1E, 0x00, 0x60, 0x00, 0xA8, 0x03, 0x00, // 0x3A80060001EC1 + 0xBF, 0x1E, 0x20, 0x60, 0x00, 0xA8, 0x03, 0x00, // 0x3A80060201EBF + 0xC5, 0x1E, 0x60, 0x60, 0x00, 0xA8, 0x03, 0x00, // 0x3A80060601EC5 + 0xC3, 0x1E, 0x20, 0x61, 0x00, 0xA8, 0x03, 0x00, // 0x3A80061201EC3 + 0x2F, 0x1E, 0x20, 0x60, 0x00, 0xBC, 0x03, 0x00, // 0x3BC0060201E2F + 0xD3, 0x1E, 0x00, 0x60, 0x00, 0xD0, 0x03, 0x00, // 0x3D00060001ED3 + 0xD1, 0x1E, 0x20, 0x60, 0x00, 0xD0, 0x03, 0x00, // 0x3D00060201ED1 + 0xD7, 0x1E, 0x60, 0x60, 0x00, 0xD0, 0x03, 0x00, // 0x3D00060601ED7 + 0xD5, 0x1E, 0x20, 0x61, 0x00, 0xD0, 0x03, 0x00, // 0x3D00061201ED5 + 0x4D, 0x1E, 0x20, 0x60, 0x00, 0xD4, 0x03, 0x00, // 0x3D40060201E4D + 0x2D, 0x02, 0x80, 0x60, 0x00, 0xD4, 0x03, 0x00, // 0x3D4006080022D + 0x4F, 0x1E, 0x00, 0x61, 0x00, 0xD4, 0x03, 0x00, // 0x3D40061001E4F + 0x2B, 0x02, 0x80, 0x60, 0x00, 0xD8, 0x03, 0x00, // 0x3D8006080022B + 0xFF, 0x01, 0x20, 0x60, 0x00, 0xE0, 0x03, 0x00, // 0x3E000602001FF + 0xDC, 0x01, 0x00, 0x60, 0x00, 0xF0, 0x03, 0x00, // 0x3F000600001DC + 0xD8, 0x01, 0x20, 0x60, 0x00, 0xF0, 0x03, 0x00, // 0x3F000602001D8 + 0xD6, 0x01, 0x80, 0x60, 0x00, 0xF0, 0x03, 0x00, // 0x3F000608001D6 + 0xDA, 0x01, 0x80, 0x61, 0x00, 0xF0, 0x03, 0x00, // 0x3F000618001DA + 0xB0, 0x1E, 0x00, 0x60, 0x00, 0x08, 0x04, 0x00, // 0x4080060001EB0 + 0xAE, 0x1E, 0x20, 0x60, 0x00, 0x08, 0x04, 0x00, // 0x4080060201EAE + 0xB4, 0x1E, 0x60, 0x60, 0x00, 0x08, 0x04, 0x00, // 0x4080060601EB4 + 0xB2, 0x1E, 0x20, 0x61, 0x00, 0x08, 0x04, 0x00, // 0x4080061201EB2 + 0xB1, 0x1E, 0x00, 0x60, 0x00, 0x0C, 0x04, 0x00, // 0x40C0060001EB1 + 0xAF, 0x1E, 0x20, 0x60, 0x00, 0x0C, 0x04, 0x00, // 0x40C0060201EAF + 0xB5, 0x1E, 0x60, 0x60, 0x00, 0x0C, 0x04, 0x00, // 0x40C0060601EB5 + 0xB3, 0x1E, 0x20, 0x61, 0x00, 0x0C, 0x04, 0x00, // 0x40C0061201EB3 + 0x14, 0x1E, 0x00, 0x60, 0x00, 0x48, 0x04, 0x00, // 0x4480060001E14 + 0x16, 0x1E, 0x20, 0x60, 0x00, 0x48, 0x04, 0x00, // 0x4480060201E16 + 0x15, 0x1E, 0x00, 0x60, 0x00, 0x4C, 0x04, 0x00, // 0x44C0060001E15 + 0x17, 0x1E, 0x20, 0x60, 0x00, 0x4C, 0x04, 0x00, // 0x44C0060201E17 + 0x50, 0x1E, 0x00, 0x60, 0x00, 0x30, 0x05, 0x00, // 0x5300060001E50 + 0x52, 0x1E, 0x20, 0x60, 0x00, 0x30, 0x05, 0x00, // 0x5300060201E52 + 0x51, 0x1E, 0x00, 0x60, 0x00, 0x34, 0x05, 0x00, // 0x5340060001E51 + 0x53, 0x1E, 0x20, 0x60, 0x00, 0x34, 0x05, 0x00, // 0x5340060201E53 + 0x64, 0x1E, 0xE0, 0x60, 0x00, 0x68, 0x05, 0x00, // 0x5680060E01E64 + 0x65, 0x1E, 0xE0, 0x60, 0x00, 0x6C, 0x05, 0x00, // 0x56C0060E01E65 + 0x66, 0x1E, 0xE0, 0x60, 0x00, 0x80, 0x05, 0x00, // 0x5800060E01E66 + 0x67, 0x1E, 0xE0, 0x60, 0x00, 0x84, 0x05, 0x00, // 0x5840060E01E67 + 0x78, 0x1E, 0x20, 0x60, 0x00, 0xA0, 0x05, 0x00, // 0x5A00060201E78 + 0x79, 0x1E, 0x20, 0x60, 0x00, 0xA4, 0x05, 0x00, // 0x5A40060201E79 + 0x7A, 0x1E, 0x00, 0x61, 0x00, 0xA8, 0x05, 0x00, // 0x5A80061001E7A + 0x7B, 0x1E, 0x00, 0x61, 0x00, 0xAC, 0x05, 0x00, // 0x5AC0061001E7B + 0x9B, 0x1E, 0xE0, 0x60, 0x00, 0xFC, 0x05, 0x00, // 0x5FC0060E01E9B + 0xDC, 0x1E, 0x00, 0x60, 0x00, 0x80, 0x06, 0x00, // 0x6800060001EDC + 0xDA, 0x1E, 0x20, 0x60, 0x00, 0x80, 0x06, 0x00, // 0x6800060201EDA + 0xE0, 0x1E, 0x60, 0x60, 0x00, 0x80, 0x06, 0x00, // 0x6800060601EE0 + 0xDE, 0x1E, 0x20, 0x61, 0x00, 0x80, 0x06, 0x00, // 0x6800061201EDE + 0xE2, 0x1E, 0x60, 0x64, 0x00, 0x80, 0x06, 0x00, // 0x6800064601EE2 + 0xDD, 0x1E, 0x00, 0x60, 0x00, 0x84, 0x06, 0x00, // 0x6840060001EDD + 0xDB, 0x1E, 0x20, 0x60, 0x00, 0x84, 0x06, 0x00, // 0x6840060201EDB + 0xE1, 0x1E, 0x60, 0x60, 0x00, 0x84, 0x06, 0x00, // 0x6840060601EE1 + 0xDF, 0x1E, 0x20, 0x61, 0x00, 0x84, 0x06, 0x00, // 0x6840061201EDF + 0xE3, 0x1E, 0x60, 0x64, 0x00, 0x84, 0x06, 0x00, // 0x6840064601EE3 + 0xEA, 0x1E, 0x00, 0x60, 0x00, 0xBC, 0x06, 0x00, // 0x6BC0060001EEA + 0xE8, 0x1E, 0x20, 0x60, 0x00, 0xBC, 0x06, 0x00, // 0x6BC0060201EE8 + 0xEE, 0x1E, 0x60, 0x60, 0x00, 0xBC, 0x06, 0x00, // 0x6BC0060601EEE + 0xEC, 0x1E, 0x20, 0x61, 0x00, 0xBC, 0x06, 0x00, // 0x6BC0061201EEC + 0xF0, 0x1E, 0x60, 0x64, 0x00, 0xBC, 0x06, 0x00, // 0x6BC0064601EF0 + 0xEB, 0x1E, 0x00, 0x60, 0x00, 0xC0, 0x06, 0x00, // 0x6C00060001EEB + 0xE9, 0x1E, 0x20, 0x60, 0x00, 0xC0, 0x06, 0x00, // 0x6C00060201EE9 + 0xEF, 0x1E, 0x60, 0x60, 0x00, 0xC0, 0x06, 0x00, // 0x6C00060601EEF + 0xED, 0x1E, 0x20, 0x61, 0x00, 0xC0, 0x06, 0x00, // 0x6C00061201EED + 0xF1, 0x1E, 0x60, 0x64, 0x00, 0xC0, 0x06, 0x00, // 0x6C00064601EF1 + 0xEE, 0x01, 0x80, 0x61, 0x00, 0xDC, 0x06, 0x00, // 0x6DC00618001EE + 0xEC, 0x01, 0x80, 0x60, 0x00, 0xA8, 0x07, 0x00, // 0x7A800608001EC + 0xED, 0x01, 0x80, 0x60, 0x00, 0xAC, 0x07, 0x00, // 0x7AC00608001ED + 0xE0, 0x01, 0x80, 0x60, 0x00, 0x98, 0x08, 0x00, // 0x89800608001E0 + 0xE1, 0x01, 0x80, 0x60, 0x00, 0x9C, 0x08, 0x00, // 0x89C00608001E1 + 0x1C, 0x1E, 0xC0, 0x60, 0x00, 0xA0, 0x08, 0x00, // 0x8A00060C01E1C + 0x1D, 0x1E, 0xC0, 0x60, 0x00, 0xA4, 0x08, 0x00, // 0x8A40060C01E1D + 0x30, 0x02, 0x80, 0x60, 0x00, 0xB8, 0x08, 0x00, // 0x8B80060800230 + 0x31, 0x02, 0x80, 0x60, 0x00, 0xBC, 0x08, 0x00, // 0x8BC0060800231 + 0xEF, 0x01, 0x80, 0x61, 0x00, 0x48, 0x0A, 0x00, // 0xA4800618001EF + 0xBA, 0x1F, 0x00, 0x60, 0x00, 0x44, 0x0E, 0x00, // 0xE440060001FBA + 0x86, 0x03, 0x20, 0x60, 0x00, 0x44, 0x0E, 0x00, // 0xE440060200386 + 0xB9, 0x1F, 0x80, 0x60, 0x00, 0x44, 0x0E, 0x00, // 0xE440060801FB9 + 0xB8, 0x1F, 0xC0, 0x60, 0x00, 0x44, 0x0E, 0x00, // 0xE440060C01FB8 + 0x08, 0x1F, 0x60, 0x62, 0x00, 0x44, 0x0E, 0x00, // 0xE440062601F08 + 0x09, 0x1F, 0x80, 0x62, 0x00, 0x44, 0x0E, 0x00, // 0xE440062801F09 + 0xBC, 0x1F, 0xA0, 0x68, 0x00, 0x44, 0x0E, 0x00, // 0xE440068A01FBC + 0xC8, 0x1F, 0x00, 0x60, 0x00, 0x54, 0x0E, 0x00, // 0xE540060001FC8 + 0x88, 0x03, 0x20, 0x60, 0x00, 0x54, 0x0E, 0x00, // 0xE540060200388 + 0x18, 0x1F, 0x60, 0x62, 0x00, 0x54, 0x0E, 0x00, // 0xE540062601F18 + 0x19, 0x1F, 0x80, 0x62, 0x00, 0x54, 0x0E, 0x00, // 0xE540062801F19 + 0xCA, 0x1F, 0x00, 0x60, 0x00, 0x5C, 0x0E, 0x00, // 0xE5C0060001FCA + 0x89, 0x03, 0x20, 0x60, 0x00, 0x5C, 0x0E, 0x00, // 0xE5C0060200389 + 0x28, 0x1F, 0x60, 0x62, 0x00, 0x5C, 0x0E, 0x00, // 0xE5C0062601F28 + 0x29, 0x1F, 0x80, 0x62, 0x00, 0x5C, 0x0E, 0x00, // 0xE5C0062801F29 + 0xCC, 0x1F, 0xA0, 0x68, 0x00, 0x5C, 0x0E, 0x00, // 0xE5C0068A01FCC + 0xDA, 0x1F, 0x00, 0x60, 0x00, 0x64, 0x0E, 0x00, // 0xE640060001FDA + 0x8A, 0x03, 0x20, 0x60, 0x00, 0x64, 0x0E, 0x00, // 0xE64006020038A + 0xD9, 0x1F, 0x80, 0x60, 0x00, 0x64, 0x0E, 0x00, // 0xE640060801FD9 + 0xD8, 0x1F, 0xC0, 0x60, 0x00, 0x64, 0x0E, 0x00, // 0xE640060C01FD8 + 0xAA, 0x03, 0x00, 0x61, 0x00, 0x64, 0x0E, 0x00, // 0xE6400610003AA + 0x38, 0x1F, 0x60, 0x62, 0x00, 0x64, 0x0E, 0x00, // 0xE640062601F38 + 0x39, 0x1F, 0x80, 0x62, 0x00, 0x64, 0x0E, 0x00, // 0xE640062801F39 + 0xF8, 0x1F, 0x00, 0x60, 0x00, 0x7C, 0x0E, 0x00, // 0xE7C0060001FF8 + 0x8C, 0x03, 0x20, 0x60, 0x00, 0x7C, 0x0E, 0x00, // 0xE7C006020038C + 0x48, 0x1F, 0x60, 0x62, 0x00, 0x7C, 0x0E, 0x00, // 0xE7C0062601F48 + 0x49, 0x1F, 0x80, 0x62, 0x00, 0x7C, 0x0E, 0x00, // 0xE7C0062801F49 + 0xEC, 0x1F, 0x80, 0x62, 0x00, 0x84, 0x0E, 0x00, // 0xE840062801FEC + 0xEA, 0x1F, 0x00, 0x60, 0x00, 0x94, 0x0E, 0x00, // 0xE940060001FEA + 0x8E, 0x03, 0x20, 0x60, 0x00, 0x94, 0x0E, 0x00, // 0xE94006020038E + 0xE9, 0x1F, 0x80, 0x60, 0x00, 0x94, 0x0E, 0x00, // 0xE940060801FE9 + 0xE8, 0x1F, 0xC0, 0x60, 0x00, 0x94, 0x0E, 0x00, // 0xE940060C01FE8 + 0xAB, 0x03, 0x00, 0x61, 0x00, 0x94, 0x0E, 0x00, // 0xE9400610003AB + 0x59, 0x1F, 0x80, 0x62, 0x00, 0x94, 0x0E, 0x00, // 0xE940062801F59 + 0xFA, 0x1F, 0x00, 0x60, 0x00, 0xA4, 0x0E, 0x00, // 0xEA40060001FFA + 0x8F, 0x03, 0x20, 0x60, 0x00, 0xA4, 0x0E, 0x00, // 0xEA4006020038F + 0x68, 0x1F, 0x60, 0x62, 0x00, 0xA4, 0x0E, 0x00, // 0xEA40062601F68 + 0x69, 0x1F, 0x80, 0x62, 0x00, 0xA4, 0x0E, 0x00, // 0xEA40062801F69 + 0xFC, 0x1F, 0xA0, 0x68, 0x00, 0xA4, 0x0E, 0x00, // 0xEA40068A01FFC + 0xB4, 0x1F, 0xA0, 0x68, 0x00, 0xB0, 0x0E, 0x00, // 0xEB00068A01FB4 + 0xC4, 0x1F, 0xA0, 0x68, 0x00, 0xB8, 0x0E, 0x00, // 0xEB80068A01FC4 + 0x70, 0x1F, 0x00, 0x60, 0x00, 0xC4, 0x0E, 0x00, // 0xEC40060001F70 + 0xAC, 0x03, 0x20, 0x60, 0x00, 0xC4, 0x0E, 0x00, // 0xEC400602003AC + 0xB1, 0x1F, 0x80, 0x60, 0x00, 0xC4, 0x0E, 0x00, // 0xEC40060801FB1 + 0xB0, 0x1F, 0xC0, 0x60, 0x00, 0xC4, 0x0E, 0x00, // 0xEC40060C01FB0 + 0x00, 0x1F, 0x60, 0x62, 0x00, 0xC4, 0x0E, 0x00, // 0xEC40062601F00 + 0x01, 0x1F, 0x80, 0x62, 0x00, 0xC4, 0x0E, 0x00, // 0xEC40062801F01 + 0xB6, 0x1F, 0x40, 0x68, 0x00, 0xC4, 0x0E, 0x00, // 0xEC40068401FB6 + 0xB3, 0x1F, 0xA0, 0x68, 0x00, 0xC4, 0x0E, 0x00, // 0xEC40068A01FB3 + 0x72, 0x1F, 0x00, 0x60, 0x00, 0xD4, 0x0E, 0x00, // 0xED40060001F72 + 0xAD, 0x03, 0x20, 0x60, 0x00, 0xD4, 0x0E, 0x00, // 0xED400602003AD + 0x10, 0x1F, 0x60, 0x62, 0x00, 0xD4, 0x0E, 0x00, // 0xED40062601F10 + 0x11, 0x1F, 0x80, 0x62, 0x00, 0xD4, 0x0E, 0x00, // 0xED40062801F11 + 0x74, 0x1F, 0x00, 0x60, 0x00, 0xDC, 0x0E, 0x00, // 0xEDC0060001F74 + 0xAE, 0x03, 0x20, 0x60, 0x00, 0xDC, 0x0E, 0x00, // 0xEDC00602003AE + 0x20, 0x1F, 0x60, 0x62, 0x00, 0xDC, 0x0E, 0x00, // 0xEDC0062601F20 + 0x21, 0x1F, 0x80, 0x62, 0x00, 0xDC, 0x0E, 0x00, // 0xEDC0062801F21 + 0xC6, 0x1F, 0x40, 0x68, 0x00, 0xDC, 0x0E, 0x00, // 0xEDC0068401FC6 + 0xC3, 0x1F, 0xA0, 0x68, 0x00, 0xDC, 0x0E, 0x00, // 0xEDC0068A01FC3 + 0x76, 0x1F, 0x00, 0x60, 0x00, 0xE4, 0x0E, 0x00, // 0xEE40060001F76 + 0xAF, 0x03, 0x20, 0x60, 0x00, 0xE4, 0x0E, 0x00, // 0xEE400602003AF + 0xD1, 0x1F, 0x80, 0x60, 0x00, 0xE4, 0x0E, 0x00, // 0xEE40060801FD1 + 0xD0, 0x1F, 0xC0, 0x60, 0x00, 0xE4, 0x0E, 0x00, // 0xEE40060C01FD0 + 0xCA, 0x03, 0x00, 0x61, 0x00, 0xE4, 0x0E, 0x00, // 0xEE400610003CA + 0x30, 0x1F, 0x60, 0x62, 0x00, 0xE4, 0x0E, 0x00, // 0xEE40062601F30 + 0x31, 0x1F, 0x80, 0x62, 0x00, 0xE4, 0x0E, 0x00, // 0xEE40062801F31 + 0xD6, 0x1F, 0x40, 0x68, 0x00, 0xE4, 0x0E, 0x00, // 0xEE40068401FD6 + 0x78, 0x1F, 0x00, 0x60, 0x00, 0xFC, 0x0E, 0x00, // 0xEFC0060001F78 + 0xCC, 0x03, 0x20, 0x60, 0x00, 0xFC, 0x0E, 0x00, // 0xEFC00602003CC + 0x40, 0x1F, 0x60, 0x62, 0x00, 0xFC, 0x0E, 0x00, // 0xEFC0062601F40 + 0x41, 0x1F, 0x80, 0x62, 0x00, 0xFC, 0x0E, 0x00, // 0xEFC0062801F41 + 0xE4, 0x1F, 0x60, 0x62, 0x00, 0x04, 0x0F, 0x00, // 0xF040062601FE4 + 0xE5, 0x1F, 0x80, 0x62, 0x00, 0x04, 0x0F, 0x00, // 0xF040062801FE5 + 0x7A, 0x1F, 0x00, 0x60, 0x00, 0x14, 0x0F, 0x00, // 0xF140060001F7A + 0xCD, 0x03, 0x20, 0x60, 0x00, 0x14, 0x0F, 0x00, // 0xF1400602003CD + 0xE1, 0x1F, 0x80, 0x60, 0x00, 0x14, 0x0F, 0x00, // 0xF140060801FE1 + 0xE0, 0x1F, 0xC0, 0x60, 0x00, 0x14, 0x0F, 0x00, // 0xF140060C01FE0 + 0xCB, 0x03, 0x00, 0x61, 0x00, 0x14, 0x0F, 0x00, // 0xF1400610003CB + 0x50, 0x1F, 0x60, 0x62, 0x00, 0x14, 0x0F, 0x00, // 0xF140062601F50 + 0x51, 0x1F, 0x80, 0x62, 0x00, 0x14, 0x0F, 0x00, // 0xF140062801F51 + 0xE6, 0x1F, 0x40, 0x68, 0x00, 0x14, 0x0F, 0x00, // 0xF140068401FE6 + 0x7C, 0x1F, 0x00, 0x60, 0x00, 0x24, 0x0F, 0x00, // 0xF240060001F7C + 0xCE, 0x03, 0x20, 0x60, 0x00, 0x24, 0x0F, 0x00, // 0xF2400602003CE + 0x60, 0x1F, 0x60, 0x62, 0x00, 0x24, 0x0F, 0x00, // 0xF240062601F60 + 0x61, 0x1F, 0x80, 0x62, 0x00, 0x24, 0x0F, 0x00, // 0xF240062801F61 + 0xF6, 0x1F, 0x40, 0x68, 0x00, 0x24, 0x0F, 0x00, // 0xF240068401FF6 + 0xF3, 0x1F, 0xA0, 0x68, 0x00, 0x24, 0x0F, 0x00, // 0xF240068A01FF3 + 0xD2, 0x1F, 0x00, 0x60, 0x00, 0x28, 0x0F, 0x00, // 0xF280060001FD2 + 0x90, 0x03, 0x20, 0x60, 0x00, 0x28, 0x0F, 0x00, // 0xF280060200390 + 0xD7, 0x1F, 0x40, 0x68, 0x00, 0x28, 0x0F, 0x00, // 0xF280068401FD7 + 0xE2, 0x1F, 0x00, 0x60, 0x00, 0x2C, 0x0F, 0x00, // 0xF2C0060001FE2 + 0xB0, 0x03, 0x20, 0x60, 0x00, 0x2C, 0x0F, 0x00, // 0xF2C00602003B0 + 0xE7, 0x1F, 0x40, 0x68, 0x00, 0x2C, 0x0F, 0x00, // 0xF2C0068401FE7 + 0xF4, 0x1F, 0xA0, 0x68, 0x00, 0x38, 0x0F, 0x00, // 0xF380068A01FF4 + 0xD3, 0x03, 0x20, 0x60, 0x00, 0x48, 0x0F, 0x00, // 0xF4800602003D3 + 0xD4, 0x03, 0x00, 0x61, 0x00, 0x48, 0x0F, 0x00, // 0xF4800610003D4 + 0x07, 0x04, 0x00, 0x61, 0x00, 0x18, 0x10, 0x00, // 0x10180061000407 + 0xD0, 0x04, 0xC0, 0x60, 0x00, 0x40, 0x10, 0x00, // 0x10400060C004D0 + 0xD2, 0x04, 0x00, 0x61, 0x00, 0x40, 0x10, 0x00, // 0x104000610004D2 + 0x03, 0x04, 0x20, 0x60, 0x00, 0x4C, 0x10, 0x00, // 0x104C0060200403 + 0x00, 0x04, 0x00, 0x60, 0x00, 0x54, 0x10, 0x00, // 0x10540060000400 + 0xD6, 0x04, 0xC0, 0x60, 0x00, 0x54, 0x10, 0x00, // 0x10540060C004D6 + 0x01, 0x04, 0x00, 0x61, 0x00, 0x54, 0x10, 0x00, // 0x10540061000401 + 0xC1, 0x04, 0xC0, 0x60, 0x00, 0x58, 0x10, 0x00, // 0x10580060C004C1 + 0xDC, 0x04, 0x00, 0x61, 0x00, 0x58, 0x10, 0x00, // 0x105800610004DC + 0xDE, 0x04, 0x00, 0x61, 0x00, 0x5C, 0x10, 0x00, // 0x105C00610004DE + 0x0D, 0x04, 0x00, 0x60, 0x00, 0x60, 0x10, 0x00, // 0x1060006000040D + 0xE2, 0x04, 0x80, 0x60, 0x00, 0x60, 0x10, 0x00, // 0x106000608004E2 + 0x19, 0x04, 0xC0, 0x60, 0x00, 0x60, 0x10, 0x00, // 0x10600060C00419 + 0xE4, 0x04, 0x00, 0x61, 0x00, 0x60, 0x10, 0x00, // 0x106000610004E4 + 0x0C, 0x04, 0x20, 0x60, 0x00, 0x68, 0x10, 0x00, // 0x1068006020040C + 0xE6, 0x04, 0x00, 0x61, 0x00, 0x78, 0x10, 0x00, // 0x107800610004E6 + 0xEE, 0x04, 0x80, 0x60, 0x00, 0x8C, 0x10, 0x00, // 0x108C00608004EE + 0x0E, 0x04, 0xC0, 0x60, 0x00, 0x8C, 0x10, 0x00, // 0x108C0060C0040E + 0xF0, 0x04, 0x00, 0x61, 0x00, 0x8C, 0x10, 0x00, // 0x108C00610004F0 + 0xF2, 0x04, 0x60, 0x61, 0x00, 0x8C, 0x10, 0x00, // 0x108C00616004F2 + 0xF4, 0x04, 0x00, 0x61, 0x00, 0x9C, 0x10, 0x00, // 0x109C00610004F4 + 0xF8, 0x04, 0x00, 0x61, 0x00, 0xAC, 0x10, 0x00, // 0x10AC00610004F8 + 0xEC, 0x04, 0x00, 0x61, 0x00, 0xB4, 0x10, 0x00, // 0x10B400610004EC + 0xD1, 0x04, 0xC0, 0x60, 0x00, 0xC0, 0x10, 0x00, // 0x10C00060C004D1 + 0xD3, 0x04, 0x00, 0x61, 0x00, 0xC0, 0x10, 0x00, // 0x10C000610004D3 + 0x53, 0x04, 0x20, 0x60, 0x00, 0xCC, 0x10, 0x00, // 0x10CC0060200453 + 0x50, 0x04, 0x00, 0x60, 0x00, 0xD4, 0x10, 0x00, // 0x10D40060000450 + 0xD7, 0x04, 0xC0, 0x60, 0x00, 0xD4, 0x10, 0x00, // 0x10D40060C004D7 + 0x51, 0x04, 0x00, 0x61, 0x00, 0xD4, 0x10, 0x00, // 0x10D40061000451 + 0xC2, 0x04, 0xC0, 0x60, 0x00, 0xD8, 0x10, 0x00, // 0x10D80060C004C2 + 0xDD, 0x04, 0x00, 0x61, 0x00, 0xD8, 0x10, 0x00, // 0x10D800610004DD + 0xDF, 0x04, 0x00, 0x61, 0x00, 0xDC, 0x10, 0x00, // 0x10DC00610004DF + 0x5D, 0x04, 0x00, 0x60, 0x00, 0xE0, 0x10, 0x00, // 0x10E0006000045D + 0xE3, 0x04, 0x80, 0x60, 0x00, 0xE0, 0x10, 0x00, // 0x10E000608004E3 + 0x39, 0x04, 0xC0, 0x60, 0x00, 0xE0, 0x10, 0x00, // 0x10E00060C00439 + 0xE5, 0x04, 0x00, 0x61, 0x00, 0xE0, 0x10, 0x00, // 0x10E000610004E5 + 0x5C, 0x04, 0x20, 0x60, 0x00, 0xE8, 0x10, 0x00, // 0x10E8006020045C + 0xE7, 0x04, 0x00, 0x61, 0x00, 0xF8, 0x10, 0x00, // 0x10F800610004E7 + 0xEF, 0x04, 0x80, 0x60, 0x00, 0x0C, 0x11, 0x00, // 0x110C00608004EF + 0x5E, 0x04, 0xC0, 0x60, 0x00, 0x0C, 0x11, 0x00, // 0x110C0060C0045E + 0xF1, 0x04, 0x00, 0x61, 0x00, 0x0C, 0x11, 0x00, // 0x110C00610004F1 + 0xF3, 0x04, 0x60, 0x61, 0x00, 0x0C, 0x11, 0x00, // 0x110C00616004F3 + 0xF5, 0x04, 0x00, 0x61, 0x00, 0x1C, 0x11, 0x00, // 0x111C00610004F5 + 0xF9, 0x04, 0x00, 0x61, 0x00, 0x2C, 0x11, 0x00, // 0x112C00610004F9 + 0xED, 0x04, 0x00, 0x61, 0x00, 0x34, 0x11, 0x00, // 0x113400610004ED + 0x57, 0x04, 0x00, 0x61, 0x00, 0x58, 0x11, 0x00, // 0x11580061000457 + 0x76, 0x04, 0xE0, 0x61, 0x00, 0xD0, 0x11, 0x00, // 0x11D00061E00476 + 0x77, 0x04, 0xE0, 0x61, 0x00, 0xD4, 0x11, 0x00, // 0x11D40061E00477 + 0xDA, 0x04, 0x00, 0x61, 0x00, 0x60, 0x13, 0x00, // 0x136000610004DA + 0xDB, 0x04, 0x00, 0x61, 0x00, 0x64, 0x13, 0x00, // 0x136400610004DB + 0xEA, 0x04, 0x00, 0x61, 0x00, 0xA0, 0x13, 0x00, // 0x13A000610004EA + 0xEB, 0x04, 0x00, 0x61, 0x00, 0xA4, 0x13, 0x00, // 0x13A400610004EB + 0x22, 0x06, 0x60, 0xCA, 0x00, 0x9C, 0x18, 0x00, // 0x189C00CA600622 + 0x23, 0x06, 0x80, 0xCA, 0x00, 0x9C, 0x18, 0x00, // 0x189C00CA800623 + 0x25, 0x06, 0xA0, 0xCA, 0x00, 0x9C, 0x18, 0x00, // 0x189C00CAA00625 + 0x24, 0x06, 0x80, 0xCA, 0x00, 0x20, 0x19, 0x00, // 0x192000CA800624 + 0x26, 0x06, 0x80, 0xCA, 0x00, 0x28, 0x19, 0x00, // 0x192800CA800626 + 0xC2, 0x06, 0x80, 0xCA, 0x00, 0x04, 0x1B, 0x00, // 0x1B0400CA8006C2 + 0xD3, 0x06, 0x80, 0xCA, 0x00, 0x48, 0x1B, 0x00, // 0x1B4800CA8006D3 + 0xC0, 0x06, 0x80, 0xCA, 0x00, 0x54, 0x1B, 0x00, // 0x1B5400CA8006C0 + 0x29, 0x09, 0x80, 0x27, 0x01, 0xA0, 0x24, 0x00, // 0x24A00127800929 + 0x31, 0x09, 0x80, 0x27, 0x01, 0xC0, 0x24, 0x00, // 0x24C00127800931 + 0x34, 0x09, 0x80, 0x27, 0x01, 0xCC, 0x24, 0x00, // 0x24CC0127800934 + 0xCB, 0x09, 0xC0, 0x37, 0x01, 0x1C, 0x27, 0x00, // 0x271C0137C009CB + 0xCC, 0x09, 0xE0, 0x3A, 0x01, 0x1C, 0x27, 0x00, // 0x271C013AE009CC + 0x4B, 0x0B, 0xC0, 0x67, 0x01, 0x1C, 0x2D, 0x00, // 0x2D1C0167C00B4B + 0x48, 0x0B, 0xC0, 0x6A, 0x01, 0x1C, 0x2D, 0x00, // 0x2D1C016AC00B48 + 0x4C, 0x0B, 0xE0, 0x6A, 0x01, 0x1C, 0x2D, 0x00, // 0x2D1C016AE00B4C + 0x94, 0x0B, 0xE0, 0x7A, 0x01, 0x48, 0x2E, 0x00, // 0x2E48017AE00B94 + 0xCA, 0x0B, 0xC0, 0x77, 0x01, 0x18, 0x2F, 0x00, // 0x2F180177C00BCA + 0xCC, 0x0B, 0xE0, 0x7A, 0x01, 0x18, 0x2F, 0x00, // 0x2F18017AE00BCC + 0xCB, 0x0B, 0xC0, 0x77, 0x01, 0x1C, 0x2F, 0x00, // 0x2F1C0177C00BCB + 0x48, 0x0C, 0xC0, 0x8A, 0x01, 0x18, 0x31, 0x00, // 0x3118018AC00C48 + 0xC0, 0x0C, 0xA0, 0x9A, 0x01, 0xFC, 0x32, 0x00, // 0x32FC019AA00CC0 + 0xCA, 0x0C, 0x40, 0x98, 0x01, 0x18, 0x33, 0x00, // 0x33180198400CCA + 0xC7, 0x0C, 0xA0, 0x9A, 0x01, 0x18, 0x33, 0x00, // 0x3318019AA00CC7 + 0xC8, 0x0C, 0xC0, 0x9A, 0x01, 0x18, 0x33, 0x00, // 0x3318019AC00CC8 + 0xCB, 0x0C, 0xA0, 0x9A, 0x01, 0x28, 0x33, 0x00, // 0x3328019AA00CCB + 0x4A, 0x0D, 0xC0, 0xA7, 0x01, 0x18, 0x35, 0x00, // 0x351801A7C00D4A + 0x4C, 0x0D, 0xE0, 0xAA, 0x01, 0x18, 0x35, 0x00, // 0x351801AAE00D4C + 0x4B, 0x0D, 0xC0, 0xA7, 0x01, 0x1C, 0x35, 0x00, // 0x351C01A7C00D4B + 0xDA, 0x0D, 0x40, 0xB9, 0x01, 0x64, 0x37, 0x00, // 0x376401B9400DDA + 0xDC, 0x0D, 0xE0, 0xB9, 0x01, 0x64, 0x37, 0x00, // 0x376401B9E00DDC + 0xDE, 0x0D, 0xE0, 0xBB, 0x01, 0x64, 0x37, 0x00, // 0x376401BBE00DDE + 0xDD, 0x0D, 0x40, 0xB9, 0x01, 0x70, 0x37, 0x00, // 0x377001B9400DDD + 0x26, 0x10, 0xC0, 0x05, 0x02, 0x94, 0x40, 0x00, // 0x40940205C01026 + 0x06, 0x1B, 0xA0, 0x66, 0x03, 0x14, 0x6C, 0x00, // 0x6C140366A01B06 + 0x08, 0x1B, 0xA0, 0x66, 0x03, 0x1C, 0x6C, 0x00, // 0x6C1C0366A01B08 + 0x0A, 0x1B, 0xA0, 0x66, 0x03, 0x24, 0x6C, 0x00, // 0x6C240366A01B0A + 0x0C, 0x1B, 0xA0, 0x66, 0x03, 0x2C, 0x6C, 0x00, // 0x6C2C0366A01B0C + 0x0E, 0x1B, 0xA0, 0x66, 0x03, 0x34, 0x6C, 0x00, // 0x6C340366A01B0E + 0x12, 0x1B, 0xA0, 0x66, 0x03, 0x44, 0x6C, 0x00, // 0x6C440366A01B12 + 0x3B, 0x1B, 0xA0, 0x66, 0x03, 0xE8, 0x6C, 0x00, // 0x6CE80366A01B3B + 0x3D, 0x1B, 0xA0, 0x66, 0x03, 0xF0, 0x6C, 0x00, // 0x6CF00366A01B3D + 0x40, 0x1B, 0xA0, 0x66, 0x03, 0xF8, 0x6C, 0x00, // 0x6CF80366A01B40 + 0x41, 0x1B, 0xA0, 0x66, 0x03, 0xFC, 0x6C, 0x00, // 0x6CFC0366A01B41 + 0x43, 0x1B, 0xA0, 0x66, 0x03, 0x08, 0x6D, 0x00, // 0x6D080366A01B43 + 0x38, 0x1E, 0x80, 0x60, 0x00, 0xD8, 0x78, 0x00, // 0x78D80060801E38 + 0x39, 0x1E, 0x80, 0x60, 0x00, 0xDC, 0x78, 0x00, // 0x78DC0060801E39 + 0x5C, 0x1E, 0x80, 0x60, 0x00, 0x68, 0x79, 0x00, // 0x79680060801E5C + 0x5D, 0x1E, 0x80, 0x60, 0x00, 0x6C, 0x79, 0x00, // 0x796C0060801E5D + 0x68, 0x1E, 0xE0, 0x60, 0x00, 0x88, 0x79, 0x00, // 0x79880060E01E68 + 0x69, 0x1E, 0xE0, 0x60, 0x00, 0x8C, 0x79, 0x00, // 0x798C0060E01E69 + 0xAC, 0x1E, 0x40, 0x60, 0x00, 0x80, 0x7A, 0x00, // 0x7A800060401EAC + 0xB6, 0x1E, 0xC0, 0x60, 0x00, 0x80, 0x7A, 0x00, // 0x7A800060C01EB6 + 0xAD, 0x1E, 0x40, 0x60, 0x00, 0x84, 0x7A, 0x00, // 0x7A840060401EAD + 0xB7, 0x1E, 0xC0, 0x60, 0x00, 0x84, 0x7A, 0x00, // 0x7A840060C01EB7 + 0xC6, 0x1E, 0x40, 0x60, 0x00, 0xE0, 0x7A, 0x00, // 0x7AE00060401EC6 + 0xC7, 0x1E, 0x40, 0x60, 0x00, 0xE4, 0x7A, 0x00, // 0x7AE40060401EC7 + 0xD8, 0x1E, 0x40, 0x60, 0x00, 0x30, 0x7B, 0x00, // 0x7B300060401ED8 + 0xD9, 0x1E, 0x40, 0x60, 0x00, 0x34, 0x7B, 0x00, // 0x7B340060401ED9 + 0x02, 0x1F, 0x00, 0x60, 0x00, 0x00, 0x7C, 0x00, // 0x7C000060001F02 + 0x04, 0x1F, 0x20, 0x60, 0x00, 0x00, 0x7C, 0x00, // 0x7C000060201F04 + 0x06, 0x1F, 0x40, 0x68, 0x00, 0x00, 0x7C, 0x00, // 0x7C000068401F06 + 0x80, 0x1F, 0xA0, 0x68, 0x00, 0x00, 0x7C, 0x00, // 0x7C000068A01F80 + 0x03, 0x1F, 0x00, 0x60, 0x00, 0x04, 0x7C, 0x00, // 0x7C040060001F03 + 0x05, 0x1F, 0x20, 0x60, 0x00, 0x04, 0x7C, 0x00, // 0x7C040060201F05 + 0x07, 0x1F, 0x40, 0x68, 0x00, 0x04, 0x7C, 0x00, // 0x7C040068401F07 + 0x81, 0x1F, 0xA0, 0x68, 0x00, 0x04, 0x7C, 0x00, // 0x7C040068A01F81 + 0x82, 0x1F, 0xA0, 0x68, 0x00, 0x08, 0x7C, 0x00, // 0x7C080068A01F82 + 0x83, 0x1F, 0xA0, 0x68, 0x00, 0x0C, 0x7C, 0x00, // 0x7C0C0068A01F83 + 0x84, 0x1F, 0xA0, 0x68, 0x00, 0x10, 0x7C, 0x00, // 0x7C100068A01F84 + 0x85, 0x1F, 0xA0, 0x68, 0x00, 0x14, 0x7C, 0x00, // 0x7C140068A01F85 + 0x86, 0x1F, 0xA0, 0x68, 0x00, 0x18, 0x7C, 0x00, // 0x7C180068A01F86 + 0x87, 0x1F, 0xA0, 0x68, 0x00, 0x1C, 0x7C, 0x00, // 0x7C1C0068A01F87 + 0x0A, 0x1F, 0x00, 0x60, 0x00, 0x20, 0x7C, 0x00, // 0x7C200060001F0A + 0x0C, 0x1F, 0x20, 0x60, 0x00, 0x20, 0x7C, 0x00, // 0x7C200060201F0C + 0x0E, 0x1F, 0x40, 0x68, 0x00, 0x20, 0x7C, 0x00, // 0x7C200068401F0E + 0x88, 0x1F, 0xA0, 0x68, 0x00, 0x20, 0x7C, 0x00, // 0x7C200068A01F88 + 0x0B, 0x1F, 0x00, 0x60, 0x00, 0x24, 0x7C, 0x00, // 0x7C240060001F0B + 0x0D, 0x1F, 0x20, 0x60, 0x00, 0x24, 0x7C, 0x00, // 0x7C240060201F0D + 0x0F, 0x1F, 0x40, 0x68, 0x00, 0x24, 0x7C, 0x00, // 0x7C240068401F0F + 0x89, 0x1F, 0xA0, 0x68, 0x00, 0x24, 0x7C, 0x00, // 0x7C240068A01F89 + 0x8A, 0x1F, 0xA0, 0x68, 0x00, 0x28, 0x7C, 0x00, // 0x7C280068A01F8A + 0x8B, 0x1F, 0xA0, 0x68, 0x00, 0x2C, 0x7C, 0x00, // 0x7C2C0068A01F8B + 0x8C, 0x1F, 0xA0, 0x68, 0x00, 0x30, 0x7C, 0x00, // 0x7C300068A01F8C + 0x8D, 0x1F, 0xA0, 0x68, 0x00, 0x34, 0x7C, 0x00, // 0x7C340068A01F8D + 0x8E, 0x1F, 0xA0, 0x68, 0x00, 0x38, 0x7C, 0x00, // 0x7C380068A01F8E + 0x8F, 0x1F, 0xA0, 0x68, 0x00, 0x3C, 0x7C, 0x00, // 0x7C3C0068A01F8F + 0x12, 0x1F, 0x00, 0x60, 0x00, 0x40, 0x7C, 0x00, // 0x7C400060001F12 + 0x14, 0x1F, 0x20, 0x60, 0x00, 0x40, 0x7C, 0x00, // 0x7C400060201F14 + 0x13, 0x1F, 0x00, 0x60, 0x00, 0x44, 0x7C, 0x00, // 0x7C440060001F13 + 0x15, 0x1F, 0x20, 0x60, 0x00, 0x44, 0x7C, 0x00, // 0x7C440060201F15 + 0x1A, 0x1F, 0x00, 0x60, 0x00, 0x60, 0x7C, 0x00, // 0x7C600060001F1A + 0x1C, 0x1F, 0x20, 0x60, 0x00, 0x60, 0x7C, 0x00, // 0x7C600060201F1C + 0x1B, 0x1F, 0x00, 0x60, 0x00, 0x64, 0x7C, 0x00, // 0x7C640060001F1B + 0x1D, 0x1F, 0x20, 0x60, 0x00, 0x64, 0x7C, 0x00, // 0x7C640060201F1D + 0x22, 0x1F, 0x00, 0x60, 0x00, 0x80, 0x7C, 0x00, // 0x7C800060001F22 + 0x24, 0x1F, 0x20, 0x60, 0x00, 0x80, 0x7C, 0x00, // 0x7C800060201F24 + 0x26, 0x1F, 0x40, 0x68, 0x00, 0x80, 0x7C, 0x00, // 0x7C800068401F26 + 0x90, 0x1F, 0xA0, 0x68, 0x00, 0x80, 0x7C, 0x00, // 0x7C800068A01F90 + 0x23, 0x1F, 0x00, 0x60, 0x00, 0x84, 0x7C, 0x00, // 0x7C840060001F23 + 0x25, 0x1F, 0x20, 0x60, 0x00, 0x84, 0x7C, 0x00, // 0x7C840060201F25 + 0x27, 0x1F, 0x40, 0x68, 0x00, 0x84, 0x7C, 0x00, // 0x7C840068401F27 + 0x91, 0x1F, 0xA0, 0x68, 0x00, 0x84, 0x7C, 0x00, // 0x7C840068A01F91 + 0x92, 0x1F, 0xA0, 0x68, 0x00, 0x88, 0x7C, 0x00, // 0x7C880068A01F92 + 0x93, 0x1F, 0xA0, 0x68, 0x00, 0x8C, 0x7C, 0x00, // 0x7C8C0068A01F93 + 0x94, 0x1F, 0xA0, 0x68, 0x00, 0x90, 0x7C, 0x00, // 0x7C900068A01F94 + 0x95, 0x1F, 0xA0, 0x68, 0x00, 0x94, 0x7C, 0x00, // 0x7C940068A01F95 + 0x96, 0x1F, 0xA0, 0x68, 0x00, 0x98, 0x7C, 0x00, // 0x7C980068A01F96 + 0x97, 0x1F, 0xA0, 0x68, 0x00, 0x9C, 0x7C, 0x00, // 0x7C9C0068A01F97 + 0x2A, 0x1F, 0x00, 0x60, 0x00, 0xA0, 0x7C, 0x00, // 0x7CA00060001F2A + 0x2C, 0x1F, 0x20, 0x60, 0x00, 0xA0, 0x7C, 0x00, // 0x7CA00060201F2C + 0x2E, 0x1F, 0x40, 0x68, 0x00, 0xA0, 0x7C, 0x00, // 0x7CA00068401F2E + 0x98, 0x1F, 0xA0, 0x68, 0x00, 0xA0, 0x7C, 0x00, // 0x7CA00068A01F98 + 0x2B, 0x1F, 0x00, 0x60, 0x00, 0xA4, 0x7C, 0x00, // 0x7CA40060001F2B + 0x2D, 0x1F, 0x20, 0x60, 0x00, 0xA4, 0x7C, 0x00, // 0x7CA40060201F2D + 0x2F, 0x1F, 0x40, 0x68, 0x00, 0xA4, 0x7C, 0x00, // 0x7CA40068401F2F + 0x99, 0x1F, 0xA0, 0x68, 0x00, 0xA4, 0x7C, 0x00, // 0x7CA40068A01F99 + 0x9A, 0x1F, 0xA0, 0x68, 0x00, 0xA8, 0x7C, 0x00, // 0x7CA80068A01F9A + 0x9B, 0x1F, 0xA0, 0x68, 0x00, 0xAC, 0x7C, 0x00, // 0x7CAC0068A01F9B + 0x9C, 0x1F, 0xA0, 0x68, 0x00, 0xB0, 0x7C, 0x00, // 0x7CB00068A01F9C + 0x9D, 0x1F, 0xA0, 0x68, 0x00, 0xB4, 0x7C, 0x00, // 0x7CB40068A01F9D + 0x9E, 0x1F, 0xA0, 0x68, 0x00, 0xB8, 0x7C, 0x00, // 0x7CB80068A01F9E + 0x9F, 0x1F, 0xA0, 0x68, 0x00, 0xBC, 0x7C, 0x00, // 0x7CBC0068A01F9F + 0x32, 0x1F, 0x00, 0x60, 0x00, 0xC0, 0x7C, 0x00, // 0x7CC00060001F32 + 0x34, 0x1F, 0x20, 0x60, 0x00, 0xC0, 0x7C, 0x00, // 0x7CC00060201F34 + 0x36, 0x1F, 0x40, 0x68, 0x00, 0xC0, 0x7C, 0x00, // 0x7CC00068401F36 + 0x33, 0x1F, 0x00, 0x60, 0x00, 0xC4, 0x7C, 0x00, // 0x7CC40060001F33 + 0x35, 0x1F, 0x20, 0x60, 0x00, 0xC4, 0x7C, 0x00, // 0x7CC40060201F35 + 0x37, 0x1F, 0x40, 0x68, 0x00, 0xC4, 0x7C, 0x00, // 0x7CC40068401F37 + 0x3A, 0x1F, 0x00, 0x60, 0x00, 0xE0, 0x7C, 0x00, // 0x7CE00060001F3A + 0x3C, 0x1F, 0x20, 0x60, 0x00, 0xE0, 0x7C, 0x00, // 0x7CE00060201F3C + 0x3E, 0x1F, 0x40, 0x68, 0x00, 0xE0, 0x7C, 0x00, // 0x7CE00068401F3E + 0x3B, 0x1F, 0x00, 0x60, 0x00, 0xE4, 0x7C, 0x00, // 0x7CE40060001F3B + 0x3D, 0x1F, 0x20, 0x60, 0x00, 0xE4, 0x7C, 0x00, // 0x7CE40060201F3D + 0x3F, 0x1F, 0x40, 0x68, 0x00, 0xE4, 0x7C, 0x00, // 0x7CE40068401F3F + 0x42, 0x1F, 0x00, 0x60, 0x00, 0x00, 0x7D, 0x00, // 0x7D000060001F42 + 0x44, 0x1F, 0x20, 0x60, 0x00, 0x00, 0x7D, 0x00, // 0x7D000060201F44 + 0x43, 0x1F, 0x00, 0x60, 0x00, 0x04, 0x7D, 0x00, // 0x7D040060001F43 + 0x45, 0x1F, 0x20, 0x60, 0x00, 0x04, 0x7D, 0x00, // 0x7D040060201F45 + 0x4A, 0x1F, 0x00, 0x60, 0x00, 0x20, 0x7D, 0x00, // 0x7D200060001F4A + 0x4C, 0x1F, 0x20, 0x60, 0x00, 0x20, 0x7D, 0x00, // 0x7D200060201F4C + 0x4B, 0x1F, 0x00, 0x60, 0x00, 0x24, 0x7D, 0x00, // 0x7D240060001F4B + 0x4D, 0x1F, 0x20, 0x60, 0x00, 0x24, 0x7D, 0x00, // 0x7D240060201F4D + 0x52, 0x1F, 0x00, 0x60, 0x00, 0x40, 0x7D, 0x00, // 0x7D400060001F52 + 0x54, 0x1F, 0x20, 0x60, 0x00, 0x40, 0x7D, 0x00, // 0x7D400060201F54 + 0x56, 0x1F, 0x40, 0x68, 0x00, 0x40, 0x7D, 0x00, // 0x7D400068401F56 + 0x53, 0x1F, 0x00, 0x60, 0x00, 0x44, 0x7D, 0x00, // 0x7D440060001F53 + 0x55, 0x1F, 0x20, 0x60, 0x00, 0x44, 0x7D, 0x00, // 0x7D440060201F55 + 0x57, 0x1F, 0x40, 0x68, 0x00, 0x44, 0x7D, 0x00, // 0x7D440068401F57 + 0x5B, 0x1F, 0x00, 0x60, 0x00, 0x64, 0x7D, 0x00, // 0x7D640060001F5B + 0x5D, 0x1F, 0x20, 0x60, 0x00, 0x64, 0x7D, 0x00, // 0x7D640060201F5D + 0x5F, 0x1F, 0x40, 0x68, 0x00, 0x64, 0x7D, 0x00, // 0x7D640068401F5F + 0x62, 0x1F, 0x00, 0x60, 0x00, 0x80, 0x7D, 0x00, // 0x7D800060001F62 + 0x64, 0x1F, 0x20, 0x60, 0x00, 0x80, 0x7D, 0x00, // 0x7D800060201F64 + 0x66, 0x1F, 0x40, 0x68, 0x00, 0x80, 0x7D, 0x00, // 0x7D800068401F66 + 0xA0, 0x1F, 0xA0, 0x68, 0x00, 0x80, 0x7D, 0x00, // 0x7D800068A01FA0 + 0x63, 0x1F, 0x00, 0x60, 0x00, 0x84, 0x7D, 0x00, // 0x7D840060001F63 + 0x65, 0x1F, 0x20, 0x60, 0x00, 0x84, 0x7D, 0x00, // 0x7D840060201F65 + 0x67, 0x1F, 0x40, 0x68, 0x00, 0x84, 0x7D, 0x00, // 0x7D840068401F67 + 0xA1, 0x1F, 0xA0, 0x68, 0x00, 0x84, 0x7D, 0x00, // 0x7D840068A01FA1 + 0xA2, 0x1F, 0xA0, 0x68, 0x00, 0x88, 0x7D, 0x00, // 0x7D880068A01FA2 + 0xA3, 0x1F, 0xA0, 0x68, 0x00, 0x8C, 0x7D, 0x00, // 0x7D8C0068A01FA3 + 0xA4, 0x1F, 0xA0, 0x68, 0x00, 0x90, 0x7D, 0x00, // 0x7D900068A01FA4 + 0xA5, 0x1F, 0xA0, 0x68, 0x00, 0x94, 0x7D, 0x00, // 0x7D940068A01FA5 + 0xA6, 0x1F, 0xA0, 0x68, 0x00, 0x98, 0x7D, 0x00, // 0x7D980068A01FA6 + 0xA7, 0x1F, 0xA0, 0x68, 0x00, 0x9C, 0x7D, 0x00, // 0x7D9C0068A01FA7 + 0x6A, 0x1F, 0x00, 0x60, 0x00, 0xA0, 0x7D, 0x00, // 0x7DA00060001F6A + 0x6C, 0x1F, 0x20, 0x60, 0x00, 0xA0, 0x7D, 0x00, // 0x7DA00060201F6C + 0x6E, 0x1F, 0x40, 0x68, 0x00, 0xA0, 0x7D, 0x00, // 0x7DA00068401F6E + 0xA8, 0x1F, 0xA0, 0x68, 0x00, 0xA0, 0x7D, 0x00, // 0x7DA00068A01FA8 + 0x6B, 0x1F, 0x00, 0x60, 0x00, 0xA4, 0x7D, 0x00, // 0x7DA40060001F6B + 0x6D, 0x1F, 0x20, 0x60, 0x00, 0xA4, 0x7D, 0x00, // 0x7DA40060201F6D + 0x6F, 0x1F, 0x40, 0x68, 0x00, 0xA4, 0x7D, 0x00, // 0x7DA40068401F6F + 0xA9, 0x1F, 0xA0, 0x68, 0x00, 0xA4, 0x7D, 0x00, // 0x7DA40068A01FA9 + 0xAA, 0x1F, 0xA0, 0x68, 0x00, 0xA8, 0x7D, 0x00, // 0x7DA80068A01FAA + 0xAB, 0x1F, 0xA0, 0x68, 0x00, 0xAC, 0x7D, 0x00, // 0x7DAC0068A01FAB + 0xAC, 0x1F, 0xA0, 0x68, 0x00, 0xB0, 0x7D, 0x00, // 0x7DB00068A01FAC + 0xAD, 0x1F, 0xA0, 0x68, 0x00, 0xB4, 0x7D, 0x00, // 0x7DB40068A01FAD + 0xAE, 0x1F, 0xA0, 0x68, 0x00, 0xB8, 0x7D, 0x00, // 0x7DB80068A01FAE + 0xAF, 0x1F, 0xA0, 0x68, 0x00, 0xBC, 0x7D, 0x00, // 0x7DBC0068A01FAF + 0xB2, 0x1F, 0xA0, 0x68, 0x00, 0xC0, 0x7D, 0x00, // 0x7DC00068A01FB2 + 0xC2, 0x1F, 0xA0, 0x68, 0x00, 0xD0, 0x7D, 0x00, // 0x7DD00068A01FC2 + 0xF2, 0x1F, 0xA0, 0x68, 0x00, 0xF0, 0x7D, 0x00, // 0x7DF00068A01FF2 + 0xB7, 0x1F, 0xA0, 0x68, 0x00, 0xD8, 0x7E, 0x00, // 0x7ED80068A01FB7 + 0xCD, 0x1F, 0x00, 0x60, 0x00, 0xFC, 0x7E, 0x00, // 0x7EFC0060001FCD + 0xCE, 0x1F, 0x20, 0x60, 0x00, 0xFC, 0x7E, 0x00, // 0x7EFC0060201FCE + 0xCF, 0x1F, 0x40, 0x68, 0x00, 0xFC, 0x7E, 0x00, // 0x7EFC0068401FCF + 0xC7, 0x1F, 0xA0, 0x68, 0x00, 0x18, 0x7F, 0x00, // 0x7F180068A01FC7 + 0xF7, 0x1F, 0xA0, 0x68, 0x00, 0xD8, 0x7F, 0x00, // 0x7FD80068A01FF7 + 0xDD, 0x1F, 0x00, 0x60, 0x00, 0xF8, 0x7F, 0x00, // 0x7FF80060001FDD + 0xDE, 0x1F, 0x20, 0x60, 0x00, 0xF8, 0x7F, 0x00, // 0x7FF80060201FDE + 0xDF, 0x1F, 0x40, 0x68, 0x00, 0xF8, 0x7F, 0x00, // 0x7FF80068401FDF + 0x9A, 0x21, 0x00, 0x67, 0x00, 0x40, 0x86, 0x00, // 0x8640006700219A + 0x9B, 0x21, 0x00, 0x67, 0x00, 0x48, 0x86, 0x00, // 0x8648006700219B + 0xAE, 0x21, 0x00, 0x67, 0x00, 0x50, 0x86, 0x00, // 0x865000670021AE + 0xCD, 0x21, 0x00, 0x67, 0x00, 0x40, 0x87, 0x00, // 0x874000670021CD + 0xCF, 0x21, 0x00, 0x67, 0x00, 0x48, 0x87, 0x00, // 0x874800670021CF + 0xCE, 0x21, 0x00, 0x67, 0x00, 0x50, 0x87, 0x00, // 0x875000670021CE + 0x04, 0x22, 0x00, 0x67, 0x00, 0x0C, 0x88, 0x00, // 0x880C0067002204 + 0x09, 0x22, 0x00, 0x67, 0x00, 0x20, 0x88, 0x00, // 0x88200067002209 + 0x0C, 0x22, 0x00, 0x67, 0x00, 0x2C, 0x88, 0x00, // 0x882C006700220C + 0x24, 0x22, 0x00, 0x67, 0x00, 0x8C, 0x88, 0x00, // 0x888C0067002224 + 0x26, 0x22, 0x00, 0x67, 0x00, 0x94, 0x88, 0x00, // 0x88940067002226 + 0x41, 0x22, 0x00, 0x67, 0x00, 0xF0, 0x88, 0x00, // 0x88F00067002241 + 0x44, 0x22, 0x00, 0x67, 0x00, 0x0C, 0x89, 0x00, // 0x890C0067002244 + 0x47, 0x22, 0x00, 0x67, 0x00, 0x14, 0x89, 0x00, // 0x89140067002247 + 0x49, 0x22, 0x00, 0x67, 0x00, 0x20, 0x89, 0x00, // 0x89200067002249 + 0x6D, 0x22, 0x00, 0x67, 0x00, 0x34, 0x89, 0x00, // 0x8934006700226D + 0x62, 0x22, 0x00, 0x67, 0x00, 0x84, 0x89, 0x00, // 0x89840067002262 + 0x70, 0x22, 0x00, 0x67, 0x00, 0x90, 0x89, 0x00, // 0x89900067002270 + 0x71, 0x22, 0x00, 0x67, 0x00, 0x94, 0x89, 0x00, // 0x89940067002271 + 0x74, 0x22, 0x00, 0x67, 0x00, 0xC8, 0x89, 0x00, // 0x89C80067002274 + 0x75, 0x22, 0x00, 0x67, 0x00, 0xCC, 0x89, 0x00, // 0x89CC0067002275 + 0x78, 0x22, 0x00, 0x67, 0x00, 0xD8, 0x89, 0x00, // 0x89D80067002278 + 0x79, 0x22, 0x00, 0x67, 0x00, 0xDC, 0x89, 0x00, // 0x89DC0067002279 + 0x80, 0x22, 0x00, 0x67, 0x00, 0xE8, 0x89, 0x00, // 0x89E80067002280 + 0x81, 0x22, 0x00, 0x67, 0x00, 0xEC, 0x89, 0x00, // 0x89EC0067002281 + 0xE0, 0x22, 0x00, 0x67, 0x00, 0xF0, 0x89, 0x00, // 0x89F000670022E0 + 0xE1, 0x22, 0x00, 0x67, 0x00, 0xF4, 0x89, 0x00, // 0x89F400670022E1 + 0x84, 0x22, 0x00, 0x67, 0x00, 0x08, 0x8A, 0x00, // 0x8A080067002284 + 0x85, 0x22, 0x00, 0x67, 0x00, 0x0C, 0x8A, 0x00, // 0x8A0C0067002285 + 0x88, 0x22, 0x00, 0x67, 0x00, 0x18, 0x8A, 0x00, // 0x8A180067002288 + 0x89, 0x22, 0x00, 0x67, 0x00, 0x1C, 0x8A, 0x00, // 0x8A1C0067002289 + 0xE2, 0x22, 0x00, 0x67, 0x00, 0x44, 0x8A, 0x00, // 0x8A4400670022E2 + 0xE3, 0x22, 0x00, 0x67, 0x00, 0x48, 0x8A, 0x00, // 0x8A4800670022E3 + 0xAC, 0x22, 0x00, 0x67, 0x00, 0x88, 0x8A, 0x00, // 0x8A8800670022AC + 0xAD, 0x22, 0x00, 0x67, 0x00, 0xA0, 0x8A, 0x00, // 0x8AA000670022AD + 0xAE, 0x22, 0x00, 0x67, 0x00, 0xA4, 0x8A, 0x00, // 0x8AA400670022AE + 0xAF, 0x22, 0x00, 0x67, 0x00, 0xAC, 0x8A, 0x00, // 0x8AAC00670022AF + 0xEA, 0x22, 0x00, 0x67, 0x00, 0xC8, 0x8A, 0x00, // 0x8AC800670022EA + 0xEB, 0x22, 0x00, 0x67, 0x00, 0xCC, 0x8A, 0x00, // 0x8ACC00670022EB + 0xEC, 0x22, 0x00, 0x67, 0x00, 0xD0, 0x8A, 0x00, // 0x8AD000670022EC + 0xED, 0x22, 0x00, 0x67, 0x00, 0xD4, 0x8A, 0x00, // 0x8AD400670022ED + 0x94, 0x30, 0x20, 0x13, 0x06, 0x18, 0xC1, 0x00, // 0xC1180613203094 + 0x4C, 0x30, 0x20, 0x13, 0x06, 0x2C, 0xC1, 0x00, // 0xC12C061320304C + 0x4E, 0x30, 0x20, 0x13, 0x06, 0x34, 0xC1, 0x00, // 0xC134061320304E + 0x50, 0x30, 0x20, 0x13, 0x06, 0x3C, 0xC1, 0x00, // 0xC13C0613203050 + 0x52, 0x30, 0x20, 0x13, 0x06, 0x44, 0xC1, 0x00, // 0xC1440613203052 + 0x54, 0x30, 0x20, 0x13, 0x06, 0x4C, 0xC1, 0x00, // 0xC14C0613203054 + 0x56, 0x30, 0x20, 0x13, 0x06, 0x54, 0xC1, 0x00, // 0xC1540613203056 + 0x58, 0x30, 0x20, 0x13, 0x06, 0x5C, 0xC1, 0x00, // 0xC15C0613203058 + 0x5A, 0x30, 0x20, 0x13, 0x06, 0x64, 0xC1, 0x00, // 0xC164061320305A + 0x5C, 0x30, 0x20, 0x13, 0x06, 0x6C, 0xC1, 0x00, // 0xC16C061320305C + 0x5E, 0x30, 0x20, 0x13, 0x06, 0x74, 0xC1, 0x00, // 0xC174061320305E + 0x60, 0x30, 0x20, 0x13, 0x06, 0x7C, 0xC1, 0x00, // 0xC17C0613203060 + 0x62, 0x30, 0x20, 0x13, 0x06, 0x84, 0xC1, 0x00, // 0xC1840613203062 + 0x65, 0x30, 0x20, 0x13, 0x06, 0x90, 0xC1, 0x00, // 0xC1900613203065 + 0x67, 0x30, 0x20, 0x13, 0x06, 0x98, 0xC1, 0x00, // 0xC1980613203067 + 0x69, 0x30, 0x20, 0x13, 0x06, 0xA0, 0xC1, 0x00, // 0xC1A00613203069 + 0x70, 0x30, 0x20, 0x13, 0x06, 0xBC, 0xC1, 0x00, // 0xC1BC0613203070 + 0x71, 0x30, 0x40, 0x13, 0x06, 0xBC, 0xC1, 0x00, // 0xC1BC0613403071 + 0x73, 0x30, 0x20, 0x13, 0x06, 0xC8, 0xC1, 0x00, // 0xC1C80613203073 + 0x74, 0x30, 0x40, 0x13, 0x06, 0xC8, 0xC1, 0x00, // 0xC1C80613403074 + 0x76, 0x30, 0x20, 0x13, 0x06, 0xD4, 0xC1, 0x00, // 0xC1D40613203076 + 0x77, 0x30, 0x40, 0x13, 0x06, 0xD4, 0xC1, 0x00, // 0xC1D40613403077 + 0x79, 0x30, 0x20, 0x13, 0x06, 0xE0, 0xC1, 0x00, // 0xC1E00613203079 + 0x7A, 0x30, 0x40, 0x13, 0x06, 0xE0, 0xC1, 0x00, // 0xC1E0061340307A + 0x7C, 0x30, 0x20, 0x13, 0x06, 0xEC, 0xC1, 0x00, // 0xC1EC061320307C + 0x7D, 0x30, 0x40, 0x13, 0x06, 0xEC, 0xC1, 0x00, // 0xC1EC061340307D + 0x9E, 0x30, 0x20, 0x13, 0x06, 0x74, 0xC2, 0x00, // 0xC274061320309E + 0xF4, 0x30, 0x20, 0x13, 0x06, 0x98, 0xC2, 0x00, // 0xC29806132030F4 + 0xAC, 0x30, 0x20, 0x13, 0x06, 0xAC, 0xC2, 0x00, // 0xC2AC06132030AC + 0xAE, 0x30, 0x20, 0x13, 0x06, 0xB4, 0xC2, 0x00, // 0xC2B406132030AE + 0xB0, 0x30, 0x20, 0x13, 0x06, 0xBC, 0xC2, 0x00, // 0xC2BC06132030B0 + 0xB2, 0x30, 0x20, 0x13, 0x06, 0xC4, 0xC2, 0x00, // 0xC2C406132030B2 + 0xB4, 0x30, 0x20, 0x13, 0x06, 0xCC, 0xC2, 0x00, // 0xC2CC06132030B4 + 0xB6, 0x30, 0x20, 0x13, 0x06, 0xD4, 0xC2, 0x00, // 0xC2D406132030B6 + 0xB8, 0x30, 0x20, 0x13, 0x06, 0xDC, 0xC2, 0x00, // 0xC2DC06132030B8 + 0xBA, 0x30, 0x20, 0x13, 0x06, 0xE4, 0xC2, 0x00, // 0xC2E406132030BA + 0xBC, 0x30, 0x20, 0x13, 0x06, 0xEC, 0xC2, 0x00, // 0xC2EC06132030BC + 0xBE, 0x30, 0x20, 0x13, 0x06, 0xF4, 0xC2, 0x00, // 0xC2F406132030BE + 0xC0, 0x30, 0x20, 0x13, 0x06, 0xFC, 0xC2, 0x00, // 0xC2FC06132030C0 + 0xC2, 0x30, 0x20, 0x13, 0x06, 0x04, 0xC3, 0x00, // 0xC30406132030C2 + 0xC5, 0x30, 0x20, 0x13, 0x06, 0x10, 0xC3, 0x00, // 0xC31006132030C5 + 0xC7, 0x30, 0x20, 0x13, 0x06, 0x18, 0xC3, 0x00, // 0xC31806132030C7 + 0xC9, 0x30, 0x20, 0x13, 0x06, 0x20, 0xC3, 0x00, // 0xC32006132030C9 + 0xD0, 0x30, 0x20, 0x13, 0x06, 0x3C, 0xC3, 0x00, // 0xC33C06132030D0 + 0xD1, 0x30, 0x40, 0x13, 0x06, 0x3C, 0xC3, 0x00, // 0xC33C06134030D1 + 0xD3, 0x30, 0x20, 0x13, 0x06, 0x48, 0xC3, 0x00, // 0xC34806132030D3 + 0xD4, 0x30, 0x40, 0x13, 0x06, 0x48, 0xC3, 0x00, // 0xC34806134030D4 + 0xD6, 0x30, 0x20, 0x13, 0x06, 0x54, 0xC3, 0x00, // 0xC35406132030D6 + 0xD7, 0x30, 0x40, 0x13, 0x06, 0x54, 0xC3, 0x00, // 0xC35406134030D7 + 0xD9, 0x30, 0x20, 0x13, 0x06, 0x60, 0xC3, 0x00, // 0xC36006132030D9 + 0xDA, 0x30, 0x40, 0x13, 0x06, 0x60, 0xC3, 0x00, // 0xC36006134030DA + 0xDC, 0x30, 0x20, 0x13, 0x06, 0x6C, 0xC3, 0x00, // 0xC36C06132030DC + 0xDD, 0x30, 0x40, 0x13, 0x06, 0x6C, 0xC3, 0x00, // 0xC36C06134030DD + 0xF7, 0x30, 0x20, 0x13, 0x06, 0xBC, 0xC3, 0x00, // 0xC3BC06132030F7 + 0xF8, 0x30, 0x20, 0x13, 0x06, 0xC0, 0xC3, 0x00, // 0xC3C006132030F8 + 0xF9, 0x30, 0x20, 0x13, 0x06, 0xC4, 0xC3, 0x00, // 0xC3C406132030F9 + 0xFA, 0x30, 0x20, 0x13, 0x06, 0xC8, 0xC3, 0x00, // 0xC3C806132030FA + 0xFE, 0x30, 0x20, 0x13, 0x06, 0xF4, 0xC3, 0x00, // 0xC3F406132030FE + 0xC9, 0x05, 0xE1, 0x60, 0x00, 0x48, 0x17, 0x04, // 0x417480060E105C9 + 0xE4, 0x05, 0xE1, 0x60, 0x00, 0x68, 0x17, 0x04, // 0x417680060E105E4 + 0x9A, 0x10, 0x41, 0x17, 0x22, 0x64, 0x42, 0x04, // 0x44264221741109A + 0x9C, 0x10, 0x41, 0x17, 0x22, 0x6C, 0x42, 0x04, // 0x4426C221741109C + 0xAB, 0x10, 0x41, 0x17, 0x22, 0x94, 0x42, 0x04, // 0x4429422174110AB + 0x2E, 0x11, 0xE1, 0x24, 0x22, 0xC4, 0x44, 0x04, // 0x444C42224E1112E + 0x2F, 0x11, 0xE1, 0x24, 0x22, 0xC8, 0x44, 0x04, // 0x444C82224E1112F + 0x4B, 0x13, 0xC1, 0x67, 0x22, 0x1C, 0x4D, 0x04, // 0x44D1C2267C1134B + 0x4C, 0x13, 0xE1, 0x6A, 0x22, 0x1C, 0x4D, 0x04, // 0x44D1C226AE1134C + 0x83, 0x13, 0x21, 0x79, 0x22, 0x08, 0x4E, 0x04, // 0x44E082279211383 + 0x85, 0x13, 0x61, 0x77, 0x22, 0x10, 0x4E, 0x04, // 0x44E102277611385 + 0x8E, 0x13, 0x41, 0x78, 0x22, 0x2C, 0x4E, 0x04, // 0x44E2C227841138E + 0x91, 0x13, 0x21, 0x79, 0x22, 0x40, 0x4E, 0x04, // 0x44E402279211391 + 0xC7, 0x13, 0x01, 0x77, 0x22, 0x08, 0x4F, 0x04, // 0x44F0822770113C7 + 0xC5, 0x13, 0x41, 0x78, 0x22, 0x08, 0x4F, 0x04, // 0x44F0822784113C5 + 0xC8, 0x13, 0x21, 0x79, 0x22, 0x08, 0x4F, 0x04, // 0x44F0822792113C8 + 0xBC, 0x14, 0x01, 0x96, 0x22, 0xE4, 0x52, 0x04, // 0x452E422960114BC + 0xBB, 0x14, 0x41, 0x97, 0x22, 0xE4, 0x52, 0x04, // 0x452E422974114BB + 0xBE, 0x14, 0xA1, 0x97, 0x22, 0xE4, 0x52, 0x04, // 0x452E42297A114BE + 0xBA, 0x15, 0xE1, 0xB5, 0x22, 0xE0, 0x56, 0x04, // 0x456E022B5E115BA + 0xBB, 0x15, 0xE1, 0xB5, 0x22, 0xE4, 0x56, 0x04, // 0x456E422B5E115BB + 0x38, 0x19, 0x01, 0x26, 0x23, 0xD4, 0x64, 0x04, // 0x464D42326011938 + 0x21, 0x61, 0xC1, 0x23, 0x2C, 0x78, 0x84, 0x05, // 0x584782C23C16121 + 0x23, 0x61, 0xE1, 0x23, 0x2C, 0x78, 0x84, 0x05, // 0x584782C23E16123 + 0x25, 0x61, 0x01, 0x24, 0x2C, 0x78, 0x84, 0x05, // 0x584782C24016125 + 0x22, 0x61, 0x21, 0x25, 0x2C, 0x78, 0x84, 0x05, // 0x584782C25216122 + 0x26, 0x61, 0xE1, 0x23, 0x2C, 0x84, 0x84, 0x05, // 0x584842C23E16126 + 0x28, 0x61, 0x01, 0x24, 0x2C, 0x84, 0x84, 0x05, // 0x584842C24016128 + 0x27, 0x61, 0xE1, 0x23, 0x2C, 0x88, 0x84, 0x05, // 0x584882C23E16127 + 0x24, 0x61, 0xE1, 0x23, 0x2C, 0xA4, 0x84, 0x05, // 0x584A42C23E16124 + 0x69, 0x6D, 0xE1, 0xAC, 0x2D, 0x8C, 0xB5, 0x05, // 0x5B58C2DACE16D69 + 0x68, 0x6D, 0xE1, 0xAC, 0x2D, 0x9C, 0xB5, 0x05, // 0x5B59C2DACE16D68 + 0x6A, 0x6D, 0xE1, 0xAC, 0x2D, 0xA4, 0xB5, 0x05, // 0x5B5A42DACE16D6A + }; + } +} diff --git a/src/SixLabors.Fonts/Unicode/Resources/ScriptDirectionData.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/ScriptDirectionData.Generated.cs new file mode 100644 index 000000000..b6be5b0f7 --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/Resources/ScriptDirectionData.Generated.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +namespace SixLabors.Fonts.Unicode.Resources +{ + /// + /// The direction each script is written in when it is set horizontally. + /// + internal static class ScriptDirectionData + { + /// + /// Gets the direction the given script is written in. A script the standard writes + /// left to right is not listed here and answers with that direction. + /// + /// The script to look up. + /// The direction the script is written in. + public static ScriptHorizontalDirection GetDirection(ScriptClass script) + { + switch (script) + { + case ScriptClass.Adlam: + case ScriptClass.Arabic: + case ScriptClass.Avestan: + case ScriptClass.Chorasmian: + case ScriptClass.Cypriot: + case ScriptClass.Elymaic: + case ScriptClass.Garay: + case ScriptClass.HanifiRohingya: + case ScriptClass.Hatran: + case ScriptClass.Hebrew: + case ScriptClass.ImperialAramaic: + case ScriptClass.InscriptionalPahlavi: + case ScriptClass.InscriptionalParthian: + case ScriptClass.Kharoshthi: + case ScriptClass.Lydian: + case ScriptClass.Mandaic: + case ScriptClass.Manichaean: + case ScriptClass.MendeKikakui: + case ScriptClass.MeroiticCursive: + case ScriptClass.MeroiticHieroglyphs: + case ScriptClass.Nabataean: + case ScriptClass.Nko: + case ScriptClass.OldNorthArabian: + case ScriptClass.OldSogdian: + case ScriptClass.OldSouthArabian: + case ScriptClass.OldTurkic: + case ScriptClass.OldUyghur: + case ScriptClass.Palmyrene: + case ScriptClass.Phoenician: + case ScriptClass.PsalterPahlavi: + case ScriptClass.Samaritan: + case ScriptClass.Sidetic: + case ScriptClass.Sogdian: + case ScriptClass.Syriac: + case ScriptClass.Thaana: + case ScriptClass.Yezidi: + return ScriptHorizontalDirection.RightToLeft; + + case ScriptClass.OldHungarian: + case ScriptClass.OldItalic: + case ScriptClass.Runic: + case ScriptClass.Tifinagh: + return ScriptHorizontalDirection.Either; + + default: + return ScriptHorizontalDirection.LeftToRight; + } + } + } +} diff --git a/src/SixLabors.Fonts/Unicode/Resources/ScriptExtensionData.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/ScriptExtensionData.Generated.cs new file mode 100644 index 000000000..820aef253 --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/Resources/ScriptExtensionData.Generated.cs @@ -0,0 +1,762 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +namespace SixLabors.Fonts.Unicode.Resources +{ + /// + /// Unicode Script_Extensions membership for characters whose permitted scripts + /// differ from their Script property. + /// + internal static class ScriptExtensionData + { + /// + /// Determines whether the code point may be shaped as the requested script. + /// + /// The code point to test. + /// The candidate script. + /// + /// when the script is listed for the code point; + /// otherwise, . + /// + public static bool Contains(CodePoint codePoint, ScriptClass script) + { + switch (codePoint.Value) + { + case 0xB7: + return script is ScriptClass.Avestan or ScriptClass.Carian or ScriptClass.Coptic + or ScriptClass.Duployan or ScriptClass.Elbasan or ScriptClass.Georgian or ScriptClass.Glagolitic + or ScriptClass.GunjalaGondi or ScriptClass.Gothic or ScriptClass.Greek or ScriptClass.Han + or ScriptClass.Latin or ScriptClass.Lydian or ScriptClass.Mahajani or ScriptClass.OldPermic + or ScriptClass.Shavian; + + case 0x2BC: + return script is ScriptClass.Bengali or ScriptClass.Cyrillic or ScriptClass.Devanagari + or ScriptClass.Latin or ScriptClass.Lisu or ScriptClass.Thai or ScriptClass.Toto; + + case 0x2C7: + return script is ScriptClass.Bopomofo or ScriptClass.Latin; + + case >= 0x2C9 and <= 0x2CB: + return script is ScriptClass.Bopomofo or ScriptClass.Latin; + + case 0x2CD: + return script is ScriptClass.Latin or ScriptClass.Lisu; + + case 0x2D7: + return script is ScriptClass.Latin or ScriptClass.Thai; + + case 0x2D9: + return script is ScriptClass.Bopomofo or ScriptClass.Latin; + + case 0x300: + return script is ScriptClass.Cherokee or ScriptClass.Coptic or ScriptClass.Cyrillic + or ScriptClass.Greek or ScriptClass.Latin or ScriptClass.OldPermic or ScriptClass.TaiLe + or ScriptClass.Sunuwar; + + case 0x301: + return script is ScriptClass.Cherokee or ScriptClass.Cyrillic or ScriptClass.Greek + or ScriptClass.Latin or ScriptClass.Osage or ScriptClass.TaiLe or ScriptClass.Sunuwar + or ScriptClass.Todhri; + + case 0x302: + return script is ScriptClass.Cherokee or ScriptClass.Cyrillic or ScriptClass.Latin + or ScriptClass.Tifinagh; + + case 0x303: + return script is ScriptClass.Glagolitic or ScriptClass.Latin or ScriptClass.Syriac + or ScriptClass.Thai or ScriptClass.Sunuwar; + + case 0x304: + return script is ScriptClass.CaucasianAlbanian or ScriptClass.Cherokee or ScriptClass.Coptic + or ScriptClass.Cyrillic or ScriptClass.Gothic or ScriptClass.Greek or ScriptClass.Latin + or ScriptClass.Osage or ScriptClass.Syriac or ScriptClass.Tifinagh or ScriptClass.Todhri; + + case 0x305: + return script is ScriptClass.Coptic or ScriptClass.Elbasan or ScriptClass.Glagolitic + or ScriptClass.Gothic or ScriptClass.Katakana or ScriptClass.Latin; + + case 0x306: + return script is ScriptClass.Cyrillic or ScriptClass.Greek or ScriptClass.Latin + or ScriptClass.OldPermic or ScriptClass.Tifinagh; + + case 0x307: + return script is ScriptClass.Coptic or ScriptClass.Duployan or ScriptClass.Hebrew + or ScriptClass.Latin or ScriptClass.OldPermic or ScriptClass.Syriac or ScriptClass.TaiLe + or ScriptClass.Tifinagh or ScriptClass.Todhri; + + case 0x308: + return script is ScriptClass.Armenian or ScriptClass.Cyrillic or ScriptClass.Duployan + or ScriptClass.Gothic or ScriptClass.Greek or ScriptClass.Hebrew or ScriptClass.Latin + or ScriptClass.OldPermic or ScriptClass.Syriac or ScriptClass.TaiLe or ScriptClass.Tifinagh; + + case 0x309: + return script is ScriptClass.Latin or ScriptClass.Tifinagh; + + case 0x30A: + return script is ScriptClass.Duployan or ScriptClass.Latin or ScriptClass.Syriac; + + case 0x30B: + return script is ScriptClass.Cherokee or ScriptClass.Cyrillic or ScriptClass.Latin + or ScriptClass.Osage; + + case 0x30C: + return script is ScriptClass.Cherokee or ScriptClass.Latin or ScriptClass.TaiLe; + + case 0x30D: + return script is ScriptClass.Latin or ScriptClass.Sunuwar; + + case 0x30E: + return script is ScriptClass.Ethiopic or ScriptClass.Latin; + + case 0x310: + return script is ScriptClass.Latin or ScriptClass.Sunuwar; + + case 0x311: + return script is ScriptClass.Cyrillic or ScriptClass.Latin or ScriptClass.Todhri; + + case 0x313: + return script is ScriptClass.Greek or ScriptClass.Latin or ScriptClass.OldPermic + or ScriptClass.Todhri; + + case 0x323: + return script is ScriptClass.Cherokee or ScriptClass.Duployan or ScriptClass.Katakana + or ScriptClass.Latin or ScriptClass.Syriac or ScriptClass.Tifinagh; + + case 0x324: + return script is ScriptClass.Cherokee or ScriptClass.Duployan or ScriptClass.Latin + or ScriptClass.Syriac; + + case 0x325: + return script is ScriptClass.Latin or ScriptClass.Syriac; + + case 0x32D: + return script is ScriptClass.Latin or ScriptClass.Syriac or ScriptClass.Sunuwar; + + case 0x32E: + return script is ScriptClass.Latin or ScriptClass.Syriac; + + case 0x330: + return script is ScriptClass.Cherokee or ScriptClass.Latin or ScriptClass.Syriac; + + case 0x331: + return script is ScriptClass.CaucasianAlbanian or ScriptClass.Cherokee or ScriptClass.Gothic + or ScriptClass.Latin or ScriptClass.Syriac or ScriptClass.Thai or ScriptClass.Sunuwar; + + case 0x342: + return script is ScriptClass.Greek; + + case 0x345: + return script is ScriptClass.Greek; + + case 0x358: + return script is ScriptClass.Latin or ScriptClass.Osage; + + case 0x35E: + return script is ScriptClass.CaucasianAlbanian or ScriptClass.Latin or ScriptClass.Todhri; + + case >= 0x363 and <= 0x36F: + return script is ScriptClass.Latin; + + case 0x374: + return script is ScriptClass.Coptic or ScriptClass.Greek; + + case 0x375: + return script is ScriptClass.Coptic or ScriptClass.Greek; + + case 0x483: + return script is ScriptClass.Cyrillic or ScriptClass.OldPermic; + + case 0x484: + return script is ScriptClass.Cyrillic or ScriptClass.Glagolitic; + + case >= 0x485 and <= 0x486: + return script is ScriptClass.Cyrillic or ScriptClass.Latin; + + case 0x487: + return script is ScriptClass.Cyrillic or ScriptClass.Glagolitic; + + case 0x589: + return script is ScriptClass.Armenian or ScriptClass.Georgian or ScriptClass.Glagolitic; + + case 0x60C: + return script is ScriptClass.Arabic or ScriptClass.Nko or ScriptClass.HanifiRohingya + or ScriptClass.Syriac or ScriptClass.Thaana or ScriptClass.Yezidi or ScriptClass.Garay; + + case 0x61B: + return script is ScriptClass.Arabic or ScriptClass.Nko or ScriptClass.HanifiRohingya + or ScriptClass.Syriac or ScriptClass.Thaana or ScriptClass.Yezidi or ScriptClass.Garay; + + case 0x61C: + return script is ScriptClass.Arabic or ScriptClass.Syriac or ScriptClass.Thaana; + + case 0x61F: + return script is ScriptClass.Adlam or ScriptClass.Arabic or ScriptClass.Nko + or ScriptClass.HanifiRohingya or ScriptClass.Syriac or ScriptClass.Thaana or ScriptClass.Yezidi + or ScriptClass.Garay; + + case 0x640: + return script is ScriptClass.Adlam or ScriptClass.Arabic or ScriptClass.Mandaic + or ScriptClass.Manichaean or ScriptClass.OldUyghur or ScriptClass.PsalterPahlavi + or ScriptClass.HanifiRohingya or ScriptClass.Sogdian or ScriptClass.Syriac; + + case >= 0x64B and <= 0x655: + return script is ScriptClass.Arabic or ScriptClass.Syriac; + + case >= 0x660 and <= 0x669: + return script is ScriptClass.Arabic or ScriptClass.Thaana or ScriptClass.Yezidi; + + case 0x670: + return script is ScriptClass.Arabic or ScriptClass.Syriac; + + case 0x6D4: + return script is ScriptClass.Arabic or ScriptClass.HanifiRohingya; + + case 0x951: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Grantha + or ScriptClass.Gujarati or ScriptClass.Gurmukhi or ScriptClass.Kannada or ScriptClass.Latin + or ScriptClass.Malayalam or ScriptClass.Nandinagari or ScriptClass.Newa or ScriptClass.Oriya + or ScriptClass.Sharada or ScriptClass.Tamil or ScriptClass.Telugu or ScriptClass.Tirhuta; + + case 0x952: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Grantha + or ScriptClass.Gujarati or ScriptClass.Gurmukhi or ScriptClass.Kannada or ScriptClass.Latin + or ScriptClass.Malayalam or ScriptClass.Newa or ScriptClass.Oriya or ScriptClass.Tamil + or ScriptClass.Telugu or ScriptClass.Tirhuta; + + case 0x964: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Dogra + or ScriptClass.GunjalaGondi or ScriptClass.MasaramGondi or ScriptClass.Grantha + or ScriptClass.Gujarati or ScriptClass.Gurmukhi or ScriptClass.Kannada or ScriptClass.Mahajani + or ScriptClass.Malayalam or ScriptClass.Nandinagari or ScriptClass.Oriya + or ScriptClass.Khudawadi or ScriptClass.Sinhala or ScriptClass.SylotiNagri or ScriptClass.Takri + or ScriptClass.Tamil or ScriptClass.Telugu or ScriptClass.Tirhuta or ScriptClass.OlOnal; + + case 0x965: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Dogra + or ScriptClass.GunjalaGondi or ScriptClass.MasaramGondi or ScriptClass.Grantha + or ScriptClass.Gujarati or ScriptClass.Gurmukhi or ScriptClass.Kannada or ScriptClass.Limbu + or ScriptClass.Mahajani or ScriptClass.Malayalam or ScriptClass.Nandinagari or ScriptClass.Oriya + or ScriptClass.Khudawadi or ScriptClass.Sinhala or ScriptClass.SylotiNagri or ScriptClass.Takri + or ScriptClass.Tamil or ScriptClass.Telugu or ScriptClass.Tirhuta or ScriptClass.GurungKhema + or ScriptClass.OlOnal; + + case >= 0x966 and <= 0x96F: + return script is ScriptClass.Devanagari or ScriptClass.Dogra or ScriptClass.Kaithi + or ScriptClass.Mahajani; + + case >= 0x9E6 and <= 0x9EF: + return script is ScriptClass.Bengali or ScriptClass.Chakma or ScriptClass.SylotiNagri; + + case >= 0xA66 and <= 0xA6F: + return script is ScriptClass.Gurmukhi or ScriptClass.Multani; + + case >= 0xAE6 and <= 0xAEF: + return script is ScriptClass.Gujarati or ScriptClass.Khojki; + + case >= 0xBE6 and <= 0xBEF: + return script is ScriptClass.Grantha or ScriptClass.Tamil; + + case >= 0xBF0 and <= 0xBF2: + return script is ScriptClass.Grantha or ScriptClass.Tamil; + + case 0xBF3: + return script is ScriptClass.Grantha or ScriptClass.Tamil; + + case >= 0xCE6 and <= 0xCEF: + return script is ScriptClass.Kannada or ScriptClass.Nandinagari or ScriptClass.TuluTigalari; + + case >= 0x1040 and <= 0x1049: + return script is ScriptClass.Chakma or ScriptClass.Myanmar or ScriptClass.TaiLe; + + case 0x10FB: + return script is ScriptClass.Georgian or ScriptClass.Glagolitic or ScriptClass.Latin; + + case >= 0x16EB and <= 0x16ED: + return script is ScriptClass.Runic; + + case >= 0x1735 and <= 0x1736: + return script is ScriptClass.Buhid or ScriptClass.Hanunoo or ScriptClass.Tagbanwa + or ScriptClass.Tagalog; + + case >= 0x1802 and <= 0x1803: + return script is ScriptClass.Mongolian or ScriptClass.PhagsPa; + + case 0x1805: + return script is ScriptClass.Mongolian or ScriptClass.PhagsPa; + + case 0x1CD0: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Grantha + or ScriptClass.Kannada; + + case 0x1CD1: + return script is ScriptClass.Devanagari; + + case 0x1CD2: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Grantha + or ScriptClass.Kannada; + + case 0x1CD3: + return script is ScriptClass.Devanagari or ScriptClass.Grantha or ScriptClass.Kannada; + + case 0x1CD4: + return script is ScriptClass.Devanagari; + + case 0x1CD5: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Newa + or ScriptClass.Telugu or ScriptClass.Tirhuta; + + case 0x1CD6: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Telugu; + + case 0x1CD7: + return script is ScriptClass.Devanagari or ScriptClass.Newa or ScriptClass.Sharada; + + case 0x1CD8: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Newa + or ScriptClass.Telugu; + + case 0x1CD9: + return script is ScriptClass.Devanagari or ScriptClass.Sharada; + + case 0x1CDA: + return script is ScriptClass.Devanagari or ScriptClass.Kannada or ScriptClass.Malayalam + or ScriptClass.Oriya or ScriptClass.Tamil or ScriptClass.Telugu; + + case 0x1CDB: + return script is ScriptClass.Devanagari; + + case >= 0x1CDC and <= 0x1CDD: + return script is ScriptClass.Devanagari or ScriptClass.Sharada; + + case >= 0x1CDE and <= 0x1CDF: + return script is ScriptClass.Devanagari; + + case 0x1CE0: + return script is ScriptClass.Devanagari or ScriptClass.Sharada; + + case 0x1CE1: + return script is ScriptClass.Bengali or ScriptClass.Devanagari; + + case 0x1CE2: + return script is ScriptClass.Devanagari or ScriptClass.Newa or ScriptClass.Tirhuta; + + case >= 0x1CE3 and <= 0x1CE8: + return script is ScriptClass.Devanagari; + + case 0x1CE9: + return script is ScriptClass.Devanagari or ScriptClass.Nandinagari or ScriptClass.Newa; + + case 0x1CEA: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Sharada; + + case 0x1CEB: + return script is ScriptClass.Devanagari or ScriptClass.Newa; + + case 0x1CEC: + return script is ScriptClass.Devanagari; + + case 0x1CED: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Newa + or ScriptClass.Sharada; + + case >= 0x1CEE and <= 0x1CF1: + return script is ScriptClass.Devanagari; + + case 0x1CF2: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.Grantha + or ScriptClass.Kannada or ScriptClass.Malayalam or ScriptClass.Nandinagari or ScriptClass.Oriya + or ScriptClass.Sinhala or ScriptClass.Telugu or ScriptClass.Tirhuta or ScriptClass.TuluTigalari; + + case 0x1CF3: + return script is ScriptClass.Devanagari or ScriptClass.Grantha; + + case 0x1CF4: + return script is ScriptClass.Devanagari or ScriptClass.Grantha or ScriptClass.Kannada + or ScriptClass.TuluTigalari; + + case >= 0x1CF5 and <= 0x1CF6: + return script is ScriptClass.Bengali or ScriptClass.Devanagari; + + case 0x1CF7: + return script is ScriptClass.Bengali; + + case >= 0x1CF8 and <= 0x1CF9: + return script is ScriptClass.Devanagari or ScriptClass.Grantha; + + case 0x1CFA: + return script is ScriptClass.Nandinagari; + + case >= 0x1DC0 and <= 0x1DC1: + return script is ScriptClass.Greek; + + case 0x1DF8: + return script is ScriptClass.Cyrillic or ScriptClass.Latin or ScriptClass.Syriac; + + case 0x1DFA: + return script is ScriptClass.Syriac; + + case 0x202F: + return script is ScriptClass.Latin or ScriptClass.Mongolian or ScriptClass.PhagsPa; + + case 0x204F: + return script is ScriptClass.Adlam or ScriptClass.Arabic; + + case 0x205A: + return script is ScriptClass.Carian or ScriptClass.Georgian or ScriptClass.Glagolitic + or ScriptClass.OldHungarian or ScriptClass.Lycian or ScriptClass.OldTurkic; + + case 0x205D: + return script is ScriptClass.Carian or ScriptClass.Greek or ScriptClass.OldHungarian + or ScriptClass.MeroiticHieroglyphs; + + case 0x20F0: + return script is ScriptClass.Devanagari or ScriptClass.Grantha or ScriptClass.Latin; + + case 0x2E17: + return script is ScriptClass.Coptic or ScriptClass.Latin; + + case 0x2E30: + return script is ScriptClass.Avestan or ScriptClass.OldTurkic; + + case 0x2E31: + return script is ScriptClass.Avestan or ScriptClass.Carian or ScriptClass.Georgian + or ScriptClass.OldHungarian or ScriptClass.Kaithi or ScriptClass.Lydian or ScriptClass.Samaritan; + + case 0x2E3C: + return script is ScriptClass.Duployan; + + case 0x2E41: + return script is ScriptClass.Adlam or ScriptClass.Arabic or ScriptClass.OldHungarian; + + case 0x2E43: + return script is ScriptClass.Cyrillic or ScriptClass.Glagolitic; + + case >= 0x2FF0 and <= 0x2FFF: + return script is ScriptClass.Han or ScriptClass.Tangut; + + case 0x3001: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Mongolian or ScriptClass.Yi; + + case 0x3002: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Mongolian or ScriptClass.PhagsPa + or ScriptClass.Yi; + + case 0x3003: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana; + + case 0x3006: + return script is ScriptClass.Han; + + case 0x3008: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Mongolian or ScriptClass.Tibetan + or ScriptClass.Yi; + + case 0x3009: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Mongolian or ScriptClass.Tibetan + or ScriptClass.Yi; + + case 0x300A: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Lisu or ScriptClass.Mongolian + or ScriptClass.Tibetan or ScriptClass.Yi; + + case 0x300B: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Lisu or ScriptClass.Mongolian + or ScriptClass.Tibetan or ScriptClass.Yi; + + case 0x300C: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x300D: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x300E: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x300F: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x3010: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x3011: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x3013: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana; + + case 0x3014: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x3015: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x3016: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x3017: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x3018: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x3019: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x301A: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x301B: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x301C: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana; + + case 0x301D: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana; + + case >= 0x301E and <= 0x301F: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana; + + case >= 0x302A and <= 0x302D: + return script is ScriptClass.Bopomofo or ScriptClass.Han; + + case 0x3030: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana; + + case >= 0x3031 and <= 0x3035: + return script is ScriptClass.Hiragana or ScriptClass.Katakana; + + case 0x3037: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana; + + case 0x303C: + return script is ScriptClass.Han or ScriptClass.Hiragana or ScriptClass.Katakana; + + case 0x303D: + return script is ScriptClass.Han or ScriptClass.Hiragana or ScriptClass.Katakana; + + case >= 0x303E and <= 0x303F: + return script is ScriptClass.Han; + + case >= 0x3099 and <= 0x309A: + return script is ScriptClass.Hiragana or ScriptClass.Katakana; + + case >= 0x309B and <= 0x309C: + return script is ScriptClass.Hiragana or ScriptClass.Katakana; + + case 0x30A0: + return script is ScriptClass.Hiragana or ScriptClass.Katakana; + + case 0x30FB: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0x30FC: + return script is ScriptClass.Hiragana or ScriptClass.Katakana; + + case >= 0x3190 and <= 0x3191: + return script is ScriptClass.Han; + + case >= 0x3192 and <= 0x3195: + return script is ScriptClass.Han; + + case >= 0x3196 and <= 0x319F: + return script is ScriptClass.Han; + + case >= 0x31C0 and <= 0x31E5: + return script is ScriptClass.Han; + + case 0x31EF: + return script is ScriptClass.Han or ScriptClass.Tangut; + + case >= 0x3220 and <= 0x3229: + return script is ScriptClass.Han; + + case >= 0x322A and <= 0x3247: + return script is ScriptClass.Han; + + case >= 0x3280 and <= 0x3289: + return script is ScriptClass.Han; + + case >= 0x328A and <= 0x32B0: + return script is ScriptClass.Han; + + case >= 0x32C0 and <= 0x32CB: + return script is ScriptClass.Han; + + case 0x32FF: + return script is ScriptClass.Han; + + case >= 0x3358 and <= 0x3370: + return script is ScriptClass.Han; + + case >= 0x337B and <= 0x337F: + return script is ScriptClass.Han; + + case >= 0x33E0 and <= 0x33FE: + return script is ScriptClass.Han; + + case 0xA66F: + return script is ScriptClass.Cyrillic or ScriptClass.Glagolitic; + + case >= 0xA700 and <= 0xA707: + return script is ScriptClass.Han or ScriptClass.Latin; + + case >= 0xA830 and <= 0xA832: + return script is ScriptClass.Devanagari or ScriptClass.Dogra or ScriptClass.Gujarati + or ScriptClass.Gurmukhi or ScriptClass.Khojki or ScriptClass.Kannada or ScriptClass.Kaithi + or ScriptClass.Mahajani or ScriptClass.Malayalam or ScriptClass.Modi or ScriptClass.Nandinagari + or ScriptClass.Sharada or ScriptClass.Khudawadi or ScriptClass.Takri or ScriptClass.Tirhuta + or ScriptClass.TuluTigalari; + + case >= 0xA833 and <= 0xA835: + return script is ScriptClass.Devanagari or ScriptClass.Dogra or ScriptClass.Gujarati + or ScriptClass.Gurmukhi or ScriptClass.Khojki or ScriptClass.Kannada or ScriptClass.Kaithi + or ScriptClass.Mahajani or ScriptClass.Modi or ScriptClass.Nandinagari or ScriptClass.Sharada + or ScriptClass.Khudawadi or ScriptClass.Takri or ScriptClass.Tirhuta or ScriptClass.TuluTigalari; + + case >= 0xA836 and <= 0xA837: + return script is ScriptClass.Devanagari or ScriptClass.Dogra or ScriptClass.Gujarati + or ScriptClass.Gurmukhi or ScriptClass.Khojki or ScriptClass.Kaithi or ScriptClass.Mahajani + or ScriptClass.Modi or ScriptClass.Khudawadi or ScriptClass.Takri or ScriptClass.Tirhuta; + + case 0xA838: + return script is ScriptClass.Devanagari or ScriptClass.Dogra or ScriptClass.Gujarati + or ScriptClass.Gurmukhi or ScriptClass.Khojki or ScriptClass.Kaithi or ScriptClass.Mahajani + or ScriptClass.Modi or ScriptClass.Sharada or ScriptClass.Khudawadi or ScriptClass.Takri + or ScriptClass.Tirhuta; + + case 0xA839: + return script is ScriptClass.Devanagari or ScriptClass.Dogra or ScriptClass.Gujarati + or ScriptClass.Gurmukhi or ScriptClass.Khojki or ScriptClass.Kaithi or ScriptClass.Mahajani + or ScriptClass.Modi or ScriptClass.Khudawadi or ScriptClass.Takri or ScriptClass.Tirhuta; + + case 0xA8F1: + return script is ScriptClass.Bengali or ScriptClass.Devanagari or ScriptClass.TuluTigalari; + + case 0xA8F3: + return script is ScriptClass.Devanagari or ScriptClass.Tamil; + + case 0xA92E: + return script is ScriptClass.KayahLi or ScriptClass.Latin or ScriptClass.Myanmar; + + case 0xA9CF: + return script is ScriptClass.Buginese or ScriptClass.Javanese; + + case 0xFD3E: + return script is ScriptClass.Arabic or ScriptClass.Nko; + + case 0xFD3F: + return script is ScriptClass.Arabic or ScriptClass.Nko; + + case 0xFDF2: + return script is ScriptClass.Arabic or ScriptClass.Thaana; + + case 0xFDFD: + return script is ScriptClass.Arabic or ScriptClass.Thaana; + + case >= 0xFE45 and <= 0xFE46: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana; + + case 0xFF61: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0xFF62: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0xFF63: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case >= 0xFF64 and <= 0xFF65: + return script is ScriptClass.Bopomofo or ScriptClass.Hangul or ScriptClass.Han + or ScriptClass.Hiragana or ScriptClass.Katakana or ScriptClass.Yi; + + case 0xFF70: + return script is ScriptClass.Hiragana or ScriptClass.Katakana; + + case >= 0xFF9E and <= 0xFF9F: + return script is ScriptClass.Hiragana or ScriptClass.Katakana; + + case >= 0x10100 and <= 0x10101: + return script is ScriptClass.CyproMinoan or ScriptClass.Cypriot or ScriptClass.LinearB; + + case 0x10102: + return script is ScriptClass.Cypriot or ScriptClass.LinearB; + + case >= 0x10107 and <= 0x10133: + return script is ScriptClass.Cypriot or ScriptClass.LinearA or ScriptClass.LinearB; + + case >= 0x10137 and <= 0x1013F: + return script is ScriptClass.Cypriot or ScriptClass.LinearB; + + case 0x102E0: + return script is ScriptClass.Arabic or ScriptClass.Coptic; + + case >= 0x102E1 and <= 0x102FB: + return script is ScriptClass.Arabic or ScriptClass.Coptic; + + case 0x10AF2: + return script is ScriptClass.Manichaean or ScriptClass.OldUyghur; + + case 0x11301: + return script is ScriptClass.Grantha or ScriptClass.Tamil; + + case 0x11303: + return script is ScriptClass.Grantha or ScriptClass.Tamil; + + case >= 0x1133B and <= 0x1133C: + return script is ScriptClass.Grantha or ScriptClass.Tamil; + + case >= 0x11FD0 and <= 0x11FD1: + return script is ScriptClass.Grantha or ScriptClass.Tamil; + + case 0x11FD3: + return script is ScriptClass.Grantha or ScriptClass.Tamil; + + case >= 0x1BCA0 and <= 0x1BCA3: + return script is ScriptClass.Duployan; + + case >= 0x1D360 and <= 0x1D371: + return script is ScriptClass.Han; + + case >= 0x1F250 and <= 0x1F251: + return script is ScriptClass.Han; + + default: + return false; + } + } + } +} diff --git a/src/SixLabors.Fonts/Unicode/Resources/UniversalShapingData.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/UniversalShapingData.Generated.cs index fce249dc3..6c1aa791a 100644 --- a/src/SixLabors.Fonts/Unicode/Resources/UniversalShapingData.Generated.cs +++ b/src/SixLabors.Fonts/Unicode/Resources/UniversalShapingData.Generated.cs @@ -12,165 +12,278 @@ internal static class UniversalShapingData public static string[] Categories => new string[] { "O", - "IND", - "S", - "GB", "B", - "FMPst", + "N", + "GB", "CGJ", - "VMAbv", - "VMPst", - "VAbv", - "VPst", - "CMBlw", - "VPre", - "VBlw", + "SUB", "H", - "VMBlw", - "FMAbv", - "CMAbv", - "MBlw", - "CS", + "HN", + "ZWNJ", + "WJ", "R", + "CS", + "IS", + "Sk", + "G", + "J", + "SB", + "SE", "HVM", - "FMBlw", - "SUB", - "MPst", - "MPre", + "HM", + "HR", + "RK", "FAbv", - "FPst", "FBlw", + "FPst", "MAbv", + "MBlw", + "MPst", + "MPre", + "CMAbv", + "CMBlw", + "VAbv", + "VBlw", + "VPst", + "VPre", + "VMAbv", + "VMBlw", + "VMPst", + "VMPre", "SMAbv", "SMBlw", - "RK", - "VMPre", - "ZWNJ", - "ZWJ", - "WJ", - "VS", - "N", - "HN" + "FMAbv", + "FMBlw", + "FMPst" }; - public static Dictionary Decompositions => new() + public static ReadOnlySpan PostBaseCategories => new bool[] { - { 0x9CB, new int[] { 0x9C7,0x9BE } }, - { 0x9CC, new int[] { 0x9C7,0x9D7 } }, - { 0xB48, new int[] { 0xB47,0xB56 } }, - { 0xB4B, new int[] { 0xB47,0xB3E } }, - { 0xB4C, new int[] { 0xB47,0xB57 } }, - { 0xBCA, new int[] { 0xBC6,0xBBE } }, - { 0xBCB, new int[] { 0xBC7,0xBBE } }, - { 0xBCC, new int[] { 0xBC6,0xBD7 } }, - { 0xC48, new int[] { 0xC46,0xC56 } }, - { 0xCC0, new int[] { 0xCBF,0xCD5 } }, - { 0xCC7, new int[] { 0xCC6,0xCD5 } }, - { 0xCC8, new int[] { 0xCC6,0xCD6 } }, - { 0xCCA, new int[] { 0xCC6,0xCC2 } }, - { 0xCCB, new int[] { 0xCC6,0xCC2,0xCD5 } }, - { 0xD4A, new int[] { 0xD46,0xD3E } }, - { 0xD4B, new int[] { 0xD47,0xD3E } }, - { 0xD4C, new int[] { 0xD46,0xD57 } }, - { 0xDDA, new int[] { 0xDD9,0xDCA } }, - { 0xDDC, new int[] { 0xDD9,0xDCF } }, - { 0xDDD, new int[] { 0xDD9,0xDCF,0xDCA } }, - { 0xDDE, new int[] { 0xDD9,0xDDF } }, - { 0xF73, new int[] { 0xF71,0xF72 } }, - { 0xF75, new int[] { 0xF71,0xF74 } }, - { 0xF76, new int[] { 0xFB2,0xF80 } }, - { 0xF78, new int[] { 0xFB3,0xF80 } }, - { 0xF81, new int[] { 0xF71,0xF80 } }, - { 0x1B3B, new int[] { 0x1B3A,0x1B35 } }, - { 0x1B3D, new int[] { 0x1B3C,0x1B35 } }, - { 0x1B40, new int[] { 0x1B3E,0x1B35 } }, - { 0x1B41, new int[] { 0x1B3F,0x1B35 } }, - { 0x1B43, new int[] { 0x1B42,0x1B35 } }, - { 0x1112E, new int[] { 0x11131,0x11127 } }, - { 0x1112F, new int[] { 0x11132,0x11127 } }, - { 0x1134B, new int[] { 0x11347,0x1133E } }, - { 0x1134C, new int[] { 0x11347,0x11357 } }, - { 0x113C5, new int[] { 0x113C2,0x113C2 } }, - { 0x113C7, new int[] { 0x113C2,0x113B8 } }, - { 0x113C8, new int[] { 0x113C2,0x113C9 } }, - { 0x114BB, new int[] { 0x114B9,0x114BA } }, - { 0x114BC, new int[] { 0x114B9,0x114B0 } }, - { 0x114BE, new int[] { 0x114B9,0x114BD } }, - { 0x115BA, new int[] { 0x115B8,0x115AF } }, - { 0x115BB, new int[] { 0x115B9,0x115AF } }, - { 0x11938, new int[] { 0x11935,0x11930 } }, - { 0x16121, new int[] { 0x1611E,0x1611E } }, - { 0x16122, new int[] { 0x1611E,0x16129 } }, - { 0x16123, new int[] { 0x1611E,0x1611F } }, - { 0x16124, new int[] { 0x16129,0x1611F } }, - { 0x16125, new int[] { 0x1611E,0x16120 } }, - { 0x16126, new int[] { 0x1611E,0x1611E,0x1611F } }, - { 0x16127, new int[] { 0x1611E,0x16129,0x1611F } }, - { 0x16128, new int[] { 0x1611E,0x1611E,0x16120 } }, - { 0x16D68, new int[] { 0x16D67,0x16D67 } }, - { 0x16D69, new int[] { 0x16D63,0x16D67 } }, - { 0x16D6A, new int[] { 0x16D63,0x16D67,0x16D67 } } + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + false, + false, + true, + true, + true }; - public static int[][] StateTable => new int[57][] + public static int[][] StateTable => new int[177][] { - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 2,2,3,4,4,0,0,5,6,7,8,9,10,11,12,13,0,14,15,0,16,0,0,17,10,18,19,20,21,22,0,0,0,23,0,0,2,0,24,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,25,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,27,0,0,0,0,0,26,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,31,32,33,34,35,36,37,38,39,40,0,0,0,41,42,34,43,44,45,46,47,0,0,48,49,0,0,0,39,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0,0,0,19,20,21,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,20,21,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,7,8,0,0,11,0,13,0,0,0,0,0,0,0,0,0,0,19,20,21,0,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,0,8,0,0,0,0,13,0,0,0,0,0,0,0,0,0,0,19,20,21,0,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,7,8,9,10,11,12,13,0,0,15,0,0,0,0,17,10,18,19,20,21,22,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,7,8,0,10,11,0,13,0,0,0,0,0,0,0,0,0,0,19,20,21,0,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,0,8,0,0,11,0,13,0,0,0,0,0,0,0,0,0,0,19,20,21,0,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0,0,0,19,20,21,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,7,8,9,10,11,12,13,0,14,15,0,0,0,0,17,10,18,19,20,21,22,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,7,8,0,10,11,0,13,0,0,0,0,0,0,0,0,10,0,19,20,21,0,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,4,4,0,0,5,6,7,8,9,10,11,12,13,0,14,15,0,0,0,0,17,10,18,19,20,21,22,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,7,8,50,10,11,12,13,0,50,15,0,0,0,0,17,10,18,19,20,21,22,0,0,0,23,0,0,0,51,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,7,8,0,10,11,0,13,0,0,15,0,0,0,0,0,10,0,19,20,21,22,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,20,21,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,21,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,7,8,0,10,11,0,13,0,0,15,0,0,0,0,0,10,0,19,20,21,0,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0,0,0,19,20,21,0,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,0,53 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,27,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,0,0,0,0,0,0,37,38,0,0,0,0,0,41,0,0,0,44,45,46,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,0,30,0,0,0,0,0,0,0,38,0,0,0,0,0,41,0,0,0,44,45,46,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,31,32,0,0,35,0,37,38,0,0,0,0,0,41,0,0,0,44,45,46,0,0,0,0,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,0,32,0,0,0,0,37,38,0,0,0,0,0,41,0,0,0,44,45,46,0,0,0,0,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,31,32,33,34,35,36,37,38,0,40,0,0,0,41,42,34,43,44,45,46,47,0,0,48,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,31,32,0,34,35,0,37,38,0,0,0,0,0,41,0,0,0,44,45,46,0,0,0,0,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,0,32,0,0,35,0,37,38,0,0,0,0,0,41,0,0,0,44,45,46,0,0,0,0,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,0,30,0,0,0,0,0,0,37,38,0,0,0,0,0,41,0,0,0,44,45,46,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,31,32,33,34,35,36,37,38,39,40,0,0,0,41,42,34,43,44,45,46,47,0,0,48,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,31,32,0,34,35,0,37,38,0,0,0,0,0,41,0,34,0,44,45,46,0,0,0,0,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,31,32,54,34,35,36,37,38,54,40,0,0,0,41,42,34,43,44,45,46,47,0,0,48,49,0,0,0,55,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,31,32,0,34,35,0,37,38,0,40,0,0,0,41,0,34,0,44,45,46,47,0,0,0,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,41,0,0,0,44,45,46,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,41,0,0,0,0,45,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,41,0,0,0,0,45,46,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,31,32,0,34,35,0,37,38,0,40,0,0,0,41,0,34,0,44,45,46,0,0,0,0,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,0,0,0,0,0,0,37,38,0,0,0,0,0,41,0,0,0,44,45,46,0,0,0,0,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,7,8,50,10,11,12,13,0,0,15,0,0,0,0,17,10,18,19,20,21,22,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,5,6,7,8,50,10,11,12,13,0,50,15,0,0,0,0,17,10,18,19,20,21,22,0,0,0,23,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,56,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,31,32,54,34,35,36,37,38,0,40,0,0,0,41,42,34,43,44,45,46,47,0,0,48,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,28,0,29,30,31,32,54,34,35,36,37,38,54,40,0,0,0,41,42,34,43,44,45,46,47,0,0,48,49,0,0,0,0,0,0 }, - new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,0,53 } + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 2,3,4,5,6,7,8,9,6,6,10,11,12,13,14,6,15,6,16,6,6,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39 }, + new int[] { 0,0,0,0,0,40,41,0,42,0,0,0,43,44,0,0,0,0,45,0,0,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,46 }, + new int[] { 0,0,0,0,0,68,69,0,42,0,0,0,70,71,0,0,0,0,72,0,0,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,94,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,95,96,0,42,0,0,0,97,98,0,0,0,0,99,0,0,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,64,65,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,121,122,0,42,0,0,0,123,124,0,0,0,0,125,0,0,126,127,128,129,130,131,132,133,121,134,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,121,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,146,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,81,0,81,0,121,122,147,42,0,0,0,123,124,0,0,0,0,125,0,0,126,127,128,129,130,131,132,133,148,149,135,136,137,138,139,140,141,142,150,151,143,144,126 }, + new int[] { 0,81,0,81,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,121,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,121,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,152,0,153,0,153,154,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,40,41,0,42,0,0,0,43,44,155,0,156,0,45,0,0,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,46 }, + new int[] { 0,121,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,157,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,131,132,0,0,0,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,157,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,132,0,0,0,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,157,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,157,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,130,131,132,0,0,0,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,121,122,0,42,0,0,0,123,124,0,0,0,0,125,0,0,126,127,128,129,130,131,132,133,148,149,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,121,122,0,42,0,0,0,123,124,0,0,0,0,125,0,0,126,127,128,129,130,131,132,133,0,149,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,135,136,137,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,0,136,137,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,0,0,137,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,139,140,141,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,0,140,141,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,0,0,141,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,158,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,151,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,144,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,144,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,40,41,0,42,0,0,0,43,44,0,0,0,0,45,0,0,46,47,48,49,50,51,52,53,40,159,56,57,58,59,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,40,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,63,0,0,0,47,48,49,0,0,0,0,0,0,0,0,0,0,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,40,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,40,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,40,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,0,0,0,0,47,48,49,0,0,0,0,0,0,0,0,0,0,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,47,48,49,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,49,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,49,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,161,0,42,0,0,0,0,160,0,0,0,0,63,0,0,0,47,48,49,0,51,52,0,0,0,56,57,58,59,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,161,0,42,0,0,0,0,160,0,0,0,0,63,0,0,0,47,48,49,0,0,52,0,0,0,56,57,58,59,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,161,0,42,0,0,0,0,160,0,0,0,0,63,0,0,0,47,48,49,0,0,0,0,0,0,56,57,58,59,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,161,0,42,0,0,0,0,160,0,0,0,0,63,0,0,0,47,48,49,50,51,52,0,0,0,56,57,58,59,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,40,41,0,42,0,0,0,43,44,0,0,0,0,45,0,0,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,40,41,0,42,0,0,0,43,44,0,0,0,0,45,0,0,46,47,48,49,50,51,52,53,0,55,56,57,58,59,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,63,0,0,0,47,48,49,0,0,0,0,0,0,56,57,58,0,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,63,0,0,0,47,48,49,0,0,0,0,0,0,0,57,58,0,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,63,0,0,0,47,48,49,0,0,0,0,0,0,0,0,58,0,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,63,0,0,0,47,48,49,0,0,0,0,0,0,56,57,58,59,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,0,0,0,0,47,48,49,0,0,0,0,0,0,0,0,0,0,60,61,62,0,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,0,0,0,0,47,48,49,0,0,0,0,0,0,0,0,0,0,0,61,62,0,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,0,0,0,0,47,48,49,0,0,0,0,0,0,0,0,0,0,0,0,62,0,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,0,0,0,0,47,48,49,0,0,0,0,0,0,0,0,0,0,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,162,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,67,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,67,0 }, + new int[] { 0,0,0,0,0,68,69,0,42,0,0,0,70,71,0,0,0,0,72,0,0,73,74,75,76,77,78,79,80,68,163,83,84,85,86,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,68,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,90,0,0,0,74,75,76,0,0,0,0,0,0,0,0,0,0,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,68,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,68,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,68,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,0,0,0,0,74,75,76,0,0,0,0,0,0,0,0,0,0,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,74,75,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,75,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,165,0,42,0,0,0,0,164,0,0,0,0,90,0,0,0,74,75,76,0,78,79,0,0,0,83,84,85,86,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,165,0,42,0,0,0,0,164,0,0,0,0,90,0,0,0,74,75,76,0,0,79,0,0,0,83,84,85,86,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,165,0,42,0,0,0,0,164,0,0,0,0,90,0,0,0,74,75,76,0,0,0,0,0,0,83,84,85,86,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,165,0,42,0,0,0,0,164,0,0,0,0,90,0,0,0,74,75,76,77,78,79,0,0,0,83,84,85,86,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,68,69,0,42,0,0,0,70,71,0,0,0,0,72,0,0,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,68,69,0,42,0,0,0,70,71,0,0,0,0,72,0,0,73,74,75,76,77,78,79,80,0,82,83,84,85,86,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,90,0,0,0,74,75,76,0,0,0,0,0,0,83,84,85,0,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,90,0,0,0,74,75,76,0,0,0,0,0,0,0,84,85,0,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,90,0,0,0,74,75,76,0,0,0,0,0,0,0,0,85,0,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,90,0,0,0,74,75,76,0,0,0,0,0,0,83,84,85,86,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,0,0,0,0,74,75,76,0,0,0,0,0,0,0,0,0,0,87,88,89,0,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,0,0,0,0,74,75,76,0,0,0,0,0,0,0,0,0,0,0,88,89,0,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,0,0,0,0,74,75,76,0,0,0,0,0,0,0,0,0,0,0,0,89,0,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,0,0,0,0,74,75,76,0,0,0,0,0,0,0,0,0,0,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,92,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,166,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,95,96,0,42,0,0,0,97,98,0,0,0,0,99,0,0,100,101,102,103,104,105,106,107,95,167,110,111,112,113,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,95,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,117,0,0,0,101,102,103,0,0,0,0,0,0,0,0,0,0,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,95,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,95,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,95,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,0,0,0,0,101,102,103,0,0,0,0,0,0,0,0,0,0,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,101,102,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,169,0,42,0,0,0,0,168,0,0,0,0,117,0,0,0,101,102,103,0,105,106,0,0,0,110,111,112,113,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,169,0,42,0,0,0,0,168,0,0,0,0,117,0,0,0,101,102,103,0,0,106,0,0,0,110,111,112,113,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,169,0,42,0,0,0,0,168,0,0,0,0,117,0,0,0,101,102,103,0,0,0,0,0,0,110,111,112,113,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,169,0,42,0,0,0,0,168,0,0,0,0,117,0,0,0,101,102,103,104,105,106,0,0,0,110,111,112,113,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,95,96,0,42,0,0,0,97,98,0,0,0,0,99,0,0,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,95,96,0,42,0,0,0,97,98,0,0,0,0,99,0,0,100,101,102,103,104,105,106,107,0,109,110,111,112,113,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,117,0,0,0,101,102,103,0,0,0,0,0,0,110,111,112,0,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,117,0,0,0,101,102,103,0,0,0,0,0,0,0,111,112,0,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,117,0,0,0,101,102,103,0,0,0,0,0,0,0,0,112,0,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,117,0,0,0,101,102,103,0,0,0,0,0,0,110,111,112,113,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,0,0,0,0,101,102,103,0,0,0,0,0,0,0,0,0,0,114,115,116,0,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,0,0,0,0,101,102,103,0,0,0,0,0,0,0,0,0,0,0,115,116,0,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,0,0,0,0,101,102,103,0,0,0,0,0,0,0,0,0,0,0,0,116,0,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,0,0,0,0,101,102,103,0,0,0,0,0,0,0,0,0,0,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118,119,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,119,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,121,122,0,42,0,0,0,123,124,0,0,0,0,125,0,0,126,127,128,129,130,131,132,133,121,134,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,121,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,121,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,121,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,121,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,157,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,131,132,0,0,0,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,157,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,132,0,0,0,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,157,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,157,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,130,131,132,0,0,0,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,121,122,0,42,0,0,0,123,124,0,0,0,0,125,0,0,126,127,128,129,130,131,132,133,0,134,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,135,136,137,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,0,136,137,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,0,0,137,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,139,140,141,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,0,140,141,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,0,0,141,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,144,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,144,0 }, + new int[] { 0,170,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,147,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,146,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,121,122,0,42,0,0,0,123,124,0,0,0,0,125,0,0,126,127,128,129,130,131,132,133,148,149,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,121,122,0,42,0,0,0,123,124,0,0,0,0,125,0,0,126,127,128,129,130,131,132,133,0,149,135,136,137,138,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,150,158,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,151,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,171,152,152,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,152,0,153,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,152,0,153,0,153,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,152,0,153,0,153,154,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,155,0,156,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,142,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,139,140,141,142,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,158,0,0,0 }, + new int[] { 0,0,0,0,0,40,41,0,42,0,0,0,43,44,0,0,0,0,45,0,0,46,47,48,49,50,51,52,53,0,159,56,57,58,59,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,172,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,63,0,0,0,47,48,49,0,0,0,0,0,0,0,0,0,0,60,61,62,63,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,162,0,0,0 }, + new int[] { 0,0,0,0,0,68,69,0,42,0,0,0,70,71,0,0,0,0,72,0,0,73,74,75,76,77,78,79,80,0,163,83,84,85,86,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,173,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,90,0,0,0,74,75,76,0,0,0,0,0,0,0,0,0,0,87,88,89,90,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,94,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,95,96,0,42,0,0,0,97,98,0,0,0,0,99,0,0,100,101,102,103,104,105,106,107,0,167,110,111,112,113,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,174,0,0,0,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,117,0,0,0,101,102,103,0,0,0,0,0,0,0,0,0,0,114,115,116,117,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,145,0,0,0,0,0,0,0,0,127,128,129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,144,126 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,152,0,175,0,175,176,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,160,0,0,0,0,0,0,0,0,47,48,49,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,67,46 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,164,0,0,0,0,0,0,0,0,74,75,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,168,0,0,0,0,0,0,0,0,101,102,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118,119,120 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,152,0,175,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, + new int[] { 0,0,0,0,0,0,0,0,42,0,0,0,0,0,0,152,0,175,0,175,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } }; public static bool[] AcceptingStates => new bool[] @@ -187,7 +300,6 @@ internal static class UniversalShapingData true, true, true, - false, true, true, true, @@ -231,48 +343,213 @@ internal static class UniversalShapingData true, true, true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, true }; - public static string[][] Tags => new string[57][] + public static string[][] Tags => new string[177][] { Array.Empty(), new string[] { "broken_cluster" }, - new string[] { "independent_cluster" }, + new string[] { "symbol_cluster","non_cluster" }, + new string[] { "standard_cluster","non_cluster" }, + new string[] { "numeral_cluster","non_cluster" }, + new string[] { "standard_cluster","symbol_cluster","non_cluster" }, + new string[] { "non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "hieroglyph_cluster","non_cluster" }, + new string[] { "symbol_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "broken_cluster","non_cluster" }, + new string[] { "non_cluster","broken_cluster" }, + new string[] { "symbol_cluster" }, new string[] { "symbol_cluster" }, - new string[] { "standard_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, Array.Empty(), - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "broken_cluster" }, - new string[] { "numeral_cluster" }, - new string[] { "independent_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, new string[] { "symbol_cluster" }, new string[] { "symbol_cluster" }, new string[] { "standard_cluster" }, new string[] { "standard_cluster" }, + new string[] { "virama_terminated_cluster" }, + new string[] { "sakot_terminated_cluster" }, + new string[] { "standard_cluster" }, + new string[] { "virama_terminated_cluster" }, + new string[] { "standard_cluster" }, new string[] { "standard_cluster" }, new string[] { "standard_cluster" }, new string[] { "standard_cluster" }, new string[] { "standard_cluster" }, new string[] { "standard_cluster" }, new string[] { "standard_cluster" }, - new string[] { "virama_terminated_cluster" }, new string[] { "standard_cluster" }, new string[] { "standard_cluster" }, new string[] { "standard_cluster" }, @@ -284,15 +561,91 @@ internal static class UniversalShapingData new string[] { "standard_cluster" }, new string[] { "standard_cluster" }, new string[] { "standard_cluster" }, - new string[] { "virama_terminated_cluster" }, new string[] { "standard_cluster" }, + new string[] { "standard_cluster" }, + new string[] { "number_joiner_terminated_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "virama_terminated_cluster","symbol_cluster" }, + new string[] { "sakot_terminated_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "virama_terminated_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, new string[] { "broken_cluster" }, new string[] { "broken_cluster" }, - new string[] { "numeral_cluster" }, - new string[] { "number_joiner_terminated_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "hieroglyph_cluster" }, + new string[] { "hieroglyph_cluster" }, + new string[] { "hieroglyph_cluster" }, + new string[] { "hieroglyph_cluster" }, + Array.Empty(), + new string[] { "broken_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, + new string[] { "symbol_cluster" }, new string[] { "standard_cluster" }, + new string[] { "sakot_terminated_cluster" }, + new string[] { "standard_cluster" }, + new string[] { "numeral_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "sakot_terminated_cluster","symbol_cluster" }, + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "broken_cluster" }, + new string[] { "hieroglyph_cluster" }, + new string[] { "symbol_cluster" }, new string[] { "standard_cluster" }, - new string[] { "numeral_cluster" } + new string[] { "standard_cluster","symbol_cluster" }, + new string[] { "hieroglyph_cluster" }, + new string[] { "hieroglyph_cluster" } }; } } diff --git a/src/SixLabors.Fonts/Unicode/Resources/UniversalShapingTrie.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/UniversalShapingTrie.Generated.cs index e75f25c50..78b17d884 100644 --- a/src/SixLabors.Fonts/Unicode/Resources/UniversalShapingTrie.Generated.cs +++ b/src/SixLabors.Fonts/Unicode/Resources/UniversalShapingTrie.Generated.cs @@ -10,525 +10,431 @@ internal static class UniversalShapingTrie { public static ReadOnlySpan Data => new byte[] { - 0, 0, 2, 0, 0, 0, 0, 0, 96, 202, 0, 0, 54, 3, 0, 0, 62, 3, 0, 0, 70, 3, 0, 0, 78, 3, 0, 0, 94, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 118, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, - 78, 3, 0, 0, 86, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 123, 3, 0, 0, 131, 3, 0, 0, 139, 3, 0, 0, 147, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 155, 3, 0, 0, 163, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 165, 3, 0, 0, 173, 3, 0, 0, 179, 3, 0, 0, 187, 3, 0, 0, 195, 3, 0, 0, - 203, 3, 0, 0, 209, 3, 0, 0, 217, 3, 0, 0, 217, 3, 0, 0, 225, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 229, 3, 0, 0, 237, 3, 0, 0, 245, 3, 0, 0, 253, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 249, 3, 0, 0, 1, 4, 0, 0, 54, 3, 0, 0, 9, 4, 0, 0, 131, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 17, 4, 0, 0, 19, 4, 0, 0, 27, 4, 0, 0, 35, 4, 0, 0, 43, 4, 0, 0, 49, 4, 0, 0, 57, 4, 0, 0, 65, 4, 0, 0, 73, 4, 0, 0, 79, 4, 0, 0, 87, 4, 0, 0, 95, 4, 0, 0, 103, 4, 0, 0, 109, 4, 0, 0, 117, 4, 0, 0, 125, 4, 0, 0, 133, 4, 0, 0, 139, 4, 0, 0, 147, 4, 0, 0, 155, 4, 0, 0, 163, 4, 0, 0, 171, 4, 0, 0, 179, 4, 0, 0, 186, 4, 0, 0, 194, 4, 0, 0, - 200, 4, 0, 0, 208, 4, 0, 0, 216, 4, 0, 0, 224, 4, 0, 0, 230, 4, 0, 0, 238, 4, 0, 0, 246, 4, 0, 0, 254, 4, 0, 0, 3, 5, 0, 0, 11, 5, 0, 0, 19, 5, 0, 0, 27, 5, 0, 0, 34, 5, 0, 0, 42, 5, 0, 0, 50, 5, 0, 0, 58, 5, 0, 0, 63, 5, 0, 0, 71, 5, 0, 0, 78, 3, 0, 0, 79, 5, 0, 0, 86, 5, 0, 0, 94, 5, 0, 0, 78, 3, 0, 0, 102, 5, 0, 0, 110, 5, 0, 0, - 118, 5, 0, 0, 123, 5, 0, 0, 131, 5, 0, 0, 138, 5, 0, 0, 146, 5, 0, 0, 78, 3, 0, 0, 154, 5, 0, 0, 160, 5, 0, 0, 168, 5, 0, 0, 176, 5, 0, 0, 184, 5, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 192, 5, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 200, 5, 0, 0, 204, 5, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 211, 5, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 217, 5, 0, 0, 225, 5, 0, 0, 233, 5, 0, 0, 241, 5, 0, 0, 249, 5, 0, 0, 154, 5, 0, 0, 1, 6, 0, 0, 9, 6, 0, 0, 17, 6, 0, 0, 25, 6, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 59, 5, 0, 0, 33, 6, 0, 0, 41, 6, 0, 0, 46, 6, 0, 0, 154, 5, 0, 0, 54, 6, 0, 0, 60, 6, 0, 0, 68, 6, 0, 0, 76, 6, 0, 0, 154, 5, 0, 0, 84, 6, 0, 0, 92, 6, 0, 0, 100, 6, 0, 0, 108, 6, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 116, 6, 0, 0, 119, 6, 0, 0, 127, 6, 0, 0, 135, 6, 0, 0, 143, 6, 0, 0, 151, 6, 0, 0, - 154, 5, 0, 0, 158, 6, 0, 0, 154, 5, 0, 0, 166, 6, 0, 0, 174, 6, 0, 0, 164, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 182, 6, 0, 0, 190, 6, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 197, 6, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 204, 6, 0, 0, 212, 6, 0, 0, 220, 6, 0, 0, 228, 6, 0, 0, 236, 6, 0, 0, 68, 6, 0, 0, 244, 6, 0, 0, 248, 6, 0, 0, 0, 7, 0, 0, 8, 7, 0, 0, 15, 7, 0, 0, 54, 3, 0, 0, 21, 7, 0, 0, 29, 7, 0, 0, 34, 7, 0, 0, 40, 7, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 48, 7, 0, 0, 56, 7, 0, 0, 68, 6, 0, 0, 59, 7, 0, 0, 67, 7, 0, 0, 74, 7, 0, 0, 79, 7, 0, 0, 67, 6, 0, 0, 68, 6, 0, 0, 87, 7, 0, 0, 71, 7, 0, 0, 54, 3, 0, 0, 90, 7, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 87, 7, 0, 0, 68, 6, 0, 0, - 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 97, 7, 0, 0, 105, 7, 0, 0, 109, 7, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 117, 7, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 123, 7, 0, 0, 74, 7, 0, 0, 68, 6, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, - 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 68, 6, 0, 0, 121, 7, 0, 0, 130, 7, 0, 0, - 134, 7, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 142, 7, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 73, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 150, 7, 0, 0, 158, 7, 0, 0, 166, 7, 0, 0, 78, 3, 0, 0, - 174, 7, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 40, 7, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 181, 7, 0, 0, 75, 7, 0, 0, 189, 7, 0, 0, 197, 7, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 192, 5, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 205, 7, 0, 0, - 54, 3, 0, 0, 68, 6, 0, 0, 212, 7, 0, 0, 220, 7, 0, 0, 228, 7, 0, 0, 234, 7, 0, 0, 68, 6, 0, 0, 228, 7, 0, 0, 242, 7, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, - 68, 6, 0, 0, 68, 6, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 75, 7, 0, 0, 68, 6, 0, 0, 250, 7, 0, 0, 164, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 255, 7, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 3, 8, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 167, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 11, 8, 0, 0, 19, 8, 0, 0, 154, 5, 0, 0, - 27, 8, 0, 0, 35, 8, 0, 0, 38, 8, 0, 0, 45, 8, 0, 0, 53, 8, 0, 0, 154, 5, 0, 0, 61, 8, 0, 0, 68, 8, 0, 0, 54, 3, 0, 0, 76, 8, 0, 0, 80, 8, 0, 0, 88, 8, 0, 0, 96, 8, 0, 0, 154, 5, 0, 0, 104, 8, 0, 0, 112, 8, 0, 0, 120, 8, 0, 0, 154, 5, 0, 0, 128, 8, 0, 0, 136, 8, 0, 0, 144, 8, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 154, 5, 0, 0, 152, 8, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 160, 8, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 121, 7, 0, 0, 54, 3, 0, 0, 240, 6, 0, 0, 54, 3, 0, 0, 166, 8, 0, 0, 90, 7, 0, 0, 174, 8, 0, 0, 73, 3, 0, 0, 181, 8, 0, 0, 189, 8, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 197, 8, 0, 0, 70, 3, 0, 0, 54, 3, 0, 0, 205, 8, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 213, 8, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 88, 13, 0, 0, 88, 13, 0, 0, 120, 13, 0, 0, 184, 13, 0, 0, 56, 13, 0, 0, 56, 13, 0, 0, 56, 13, 0, 0, 56, 13, 0, 0, 56, 13, 0, 0, 56, 13, 0, 0, 56, 13, 0, 0, 56, 13, 0, 0, 56, 13, 0, 0, 236, 13, 0, 0, 44, 14, 0, 0, 56, 13, 0, 0, 56, 13, 0, 0, - 56, 13, 0, 0, 108, 14, 0, 0, 56, 13, 0, 0, 56, 13, 0, 0, 148, 14, 0, 0, 204, 14, 0, 0, 12, 15, 0, 0, 68, 15, 0, 0, 100, 15, 0, 0, 56, 13, 0, 0, 148, 15, 0, 0, 212, 15, 0, 0, 56, 13, 0, 0, 56, 13, 0, 0, 228, 15, 0, 0, 96, 8, 0, 0, 160, 8, 0, 0, 224, 8, 0, 0, 32, 9, 0, 0, 96, 9, 0, 0, 139, 9, 0, 0, 203, 9, 0, 0, 203, 9, 0, 0, 216, 9, 0, 0, 161, 1, 0, 0, - 161, 1, 0, 0, 161, 1, 0, 0, 16, 10, 0, 0, 80, 10, 0, 0, 160, 1, 0, 0, 161, 1, 0, 0, 46, 2, 0, 0, 144, 10, 0, 0, 161, 1, 0, 0, 161, 1, 0, 0, 161, 1, 0, 0, 46, 2, 0, 0, 208, 10, 0, 0, 240, 10, 0, 0, 161, 1, 0, 0, 22, 11, 0, 0, 86, 11, 0, 0, 150, 11, 0, 0, 214, 11, 0, 0, 21, 12, 0, 0, 85, 12, 0, 0, 149, 12, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 221, 8, 0, 0, 224, 8, 0, 0, 54, 3, 0, 0, 232, 8, 0, 0, 239, 8, 0, 0, 246, 8, 0, 0, 75, 7, 0, 0, 254, 8, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 200, 3, 0, 0, 54, 3, 0, 0, 73, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 6, 9, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 10, 9, 0, 0, 16, 9, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 200, 3, 0, 0, 200, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 24, 9, 0, 0, 31, 9, 0, 0, 39, 9, 0, 0, 200, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 46, 9, 0, 0, 50, 9, 0, 0, 54, 3, 0, 0, 56, 9, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 64, 9, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 72, 9, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 79, 9, 0, 0, 54, 3, 0, 0, 226, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 87, 9, 0, 0, 89, 9, 0, 0, 97, 9, 0, 0, 104, 9, 0, 0, 112, 9, 0, 0, 116, 9, 0, 0, 124, 9, 0, 0, 54, 3, 0, 0, 132, 9, 0, 0, 139, 9, 0, 0, 147, 9, 0, 0, 151, 9, 0, 0, 112, 9, 0, 0, 159, 9, 0, 0, 167, 9, 0, 0, 175, 9, 0, 0, 183, 9, 0, 0, 188, 9, 0, 0, 196, 9, 0, 0, 78, 3, 0, 0, 204, 9, 0, 0, 212, 9, 0, 0, 216, 9, 0, 0, 224, 9, 0, 0, 232, 9, 0, 0, - 238, 9, 0, 0, 246, 9, 0, 0, 254, 9, 0, 0, 6, 10, 0, 0, 10, 10, 0, 0, 18, 10, 0, 0, 26, 10, 0, 0, 154, 5, 0, 0, 34, 10, 0, 0, 42, 10, 0, 0, 50, 10, 0, 0, 58, 5, 0, 0, 58, 10, 0, 0, 66, 10, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 154, 5, 0, 0, 74, 10, 0, 0, 82, 10, 0, 0, 78, 3, 0, 0, 154, 5, 0, 0, 90, 10, 0, 0, - 98, 10, 0, 0, 106, 10, 0, 0, 154, 5, 0, 0, 114, 10, 0, 0, 122, 10, 0, 0, 129, 10, 0, 0, 137, 10, 0, 0, 145, 10, 0, 0, 153, 10, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 154, 5, 0, 0, 161, 10, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 169, 10, 0, 0, 175, 10, 0, 0, 183, 10, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 191, 10, 0, 0, 195, 10, 0, 0, 203, 10, 0, 0, 211, 10, 0, 0, 215, 10, 0, 0, 223, 10, 0, 0, 154, 5, 0, 0, 230, 10, 0, 0, 221, 8, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 246, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 238, 10, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 246, 10, 0, 0, 254, 10, 0, 0, 3, 11, 0, 0, 11, 11, 0, 0, 16, 11, 0, 0, - 21, 11, 0, 0, 27, 11, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 35, 11, 0, 0, 39, 11, 0, 0, 47, 11, 0, 0, 55, 11, 0, 0, 61, 11, 0, 0, 17, 6, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 69, 11, 0, 0, 77, 11, 0, 0, 82, 11, 0, 0, 90, 11, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 54, 3, 0, 0, 233, 8, 0, 0, 98, 11, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 106, 11, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 112, 11, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 120, 11, 0, 0, 128, 11, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 135, 11, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 241, 10, 0, 0, 54, 3, 0, 0, 139, 11, 0, 0, 147, 11, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 155, 11, 0, 0, 161, 11, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 168, 11, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 196, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 175, 11, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, - 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 183, 11, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 191, 11, 0, 0, 242, 7, 0, 0, 121, 7, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 75, 7, 0, 0, - 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 198, 11, 0, 0, 78, 3, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 181, 7, 0, 0, 68, 6, 0, 0, 206, 11, 0, 0, 68, 6, 0, 0, 213, 11, 0, 0, 221, 11, 0, 0, 227, 11, 0, 0, 68, 6, 0, 0, 71, 7, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 235, 11, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 7, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, - 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 54, 3, 0, 0, 238, 11, 0, 0, 54, 3, 0, 0, 245, 11, 0, 0, 252, 11, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 1, 12, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 222, 8, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 200, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 164, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 6, 12, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 152, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, 54, 3, 0, 0, - 78, 3, 0, 0, 54, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 68, 6, 0, 0, 14, 12, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 40, 7, 0, 0, 19, 12, 0, 0, 23, 12, 0, 0, 181, 7, 0, 0, 31, 12, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 36, 12, 0, 0, 78, 3, 0, 0, - 43, 12, 0, 0, 51, 12, 0, 0, 79, 7, 0, 0, 57, 12, 0, 0, 88, 7, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 67, 7, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, - 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 65, 12, 0, 0, 68, 12, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 76, 12, 0, 0, 84, 12, 0, 0, 14, 12, 0, 0, - 68, 6, 0, 0, 92, 12, 0, 0, 68, 6, 0, 0, 100, 12, 0, 0, 105, 12, 0, 0, 244, 6, 0, 0, 78, 3, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 113, 12, 0, 0, 121, 12, 0, 0, 129, 12, 0, 0, 68, 6, 0, 0, 136, 12, 0, 0, 144, 12, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, - 68, 6, 0, 0, 68, 6, 0, 0, 152, 12, 0, 0, 68, 6, 0, 0, 68, 6, 0, 0, 157, 12, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, - 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 78, 3, 0, 0, 53, 3, 1, 0, 53, 3, 1, 0, 53, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, - 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 16, 14, 0, 0, 0, 0, 0, 208, 165, 0, 0, 70, 3, 0, 0, 78, 3, 0, 0, 86, 3, 0, 0, 94, 3, 0, 0, 110, 3, 0, 0, 118, 3, 0, 0, 123, 3, 0, 0, 131, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, + 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 136, 3, 0, 0, 144, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, + 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 152, 3, 0, 0, 160, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 102, 3, 0, 0, 110, 3, 0, 0, 166, 3, 0, 0, 174, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 0, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 70, 3, 0, 0, + 182, 3, 0, 0, 109, 5, 0, 0, 49, 6, 0, 0, 232, 6, 0, 0, 190, 3, 0, 0, 117, 5, 0, 0, 57, 6, 0, 0, 198, 3, 0, 0, 206, 3, 0, 0, 201, 5, 0, 0, 65, 6, 0, 0, 214, 3, 0, 0, 222, 3, 0, 0, 125, 5, 0, 0, 73, 6, 0, 0, 209, 5, 0, 0, 230, 3, 0, 0, 133, 5, 0, 0, 81, 6, 0, 0, 240, 6, 0, 0, 238, 3, 0, 0, 136, 7, 0, 0, 89, 6, 0, 0, 209, 8, 0, 0, 246, 3, 0, 0, + 141, 5, 0, 0, 97, 6, 0, 0, 248, 6, 0, 0, 254, 3, 0, 0, 149, 5, 0, 0, 105, 6, 0, 0, 6, 4, 0, 0, 14, 4, 0, 0, 157, 5, 0, 0, 113, 6, 0, 0, 248, 6, 0, 0, 22, 4, 0, 0, 88, 8, 0, 0, 121, 6, 0, 0, 144, 7, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 8, 9, 0, 0, 217, 5, 0, 0, + 90, 7, 0, 0, 29, 4, 0, 0, 37, 4, 0, 0, 141, 8, 0, 0, 184, 8, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 45, 4, 0, 0, 0, 7, 0, 0, 152, 7, 0, 0, 160, 7, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 70, 3, 0, 0, 70, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 153, 6, 0, 0, 161, 6, 0, 0, 8, 7, 0, 0, 16, 7, 0, 0, 40, 7, 0, 0, 24, 7, 0, 0, 53, 4, 0, 0, 82, 3, 0, 0, 16, 9, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 24, 9, 0, 0, 202, 9, 0, 0, + 218, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 93, 7, 0, 0, 61, 4, 0, 0, 167, 3, 0, 0, 49, 8, 0, 0, 40, 7, 0, 0, 168, 7, 0, 0, 174, 7, 0, 0, 102, 3, 0, 0, 182, 7, 0, 0, 40, 7, 0, 0, 32, 7, 0, 0, 69, 4, 0, 0, 217, 8, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 77, 4, 0, 0, 225, 5, 0, 0, 129, 6, 0, 0, 242, 9, 0, 0, 85, 4, 0, 0, 165, 5, 0, 0, + 40, 7, 0, 0, 233, 5, 0, 0, 40, 7, 0, 0, 92, 4, 0, 0, 96, 8, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 161, 8, 0, 0, 65, 8, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 186, 8, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 72, 8, 0, 0, 28, 10, 0, 0, 102, 3, 0, 0, 194, 8, 0, 0, 202, 8, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 169, 8, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 149, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 21, 10, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 21, 8, 0, 0, 40, 7, 0, 0, 32, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 22, 10, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 70, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 100, 4, 0, 0, 169, 6, 0, 0, 40, 7, 0, 0, + 103, 4, 0, 0, 111, 4, 0, 0, 190, 7, 0, 0, 119, 4, 0, 0, 127, 4, 0, 0, 40, 7, 0, 0, 57, 8, 0, 0, 177, 6, 0, 0, 102, 3, 0, 0, 135, 4, 0, 0, 241, 5, 0, 0, 137, 6, 0, 0, 198, 7, 0, 0, 40, 7, 0, 0, 206, 7, 0, 0, 149, 8, 0, 0, 80, 8, 0, 0, 40, 7, 0, 0, 214, 7, 0, 0, 157, 8, 0, 0, 101, 5, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 185, 6, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 210, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 49, 10, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 70, 3, 0, 0, 102, 3, 0, 0, + 57, 10, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 184, 13, 0, 0, 236, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 32, 14, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, + 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 96, 14, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 13, 0, 0, 152, 14, 0, 0, 7, 11, 0, 0, 226, 9, 0, 0, 34, 10, 0, 0, 98, 10, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 13, 12, 0, 0, 48, 12, 0, 0, 81, 12, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 157, 10, 0, 0, 199, 10, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 53, 11, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 93, 11, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 134, 12, 0, 0, 140, 1, 0, 0, 149, 11, 0, 0, 205, 11, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, + 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 140, 1, 0, 0, 198, 12, 0, 0, 214, 12, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 143, 4, 0, 0, + 249, 5, 0, 0, 225, 8, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 90, 7, 0, 0, 57, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 120, 9, 0, 0, 34, 10, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 65, 9, 0, 0, + 166, 3, 0, 0, 73, 9, 0, 0, 83, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 79, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 21, 8, 0, 0, 87, 9, 0, 0, 21, 8, 0, 0, 95, 9, 0, 0, 38, 10, 0, 0, 46, 10, 0, 0, 102, 3, 0, 0, 151, 4, 0, 0, 222, 7, 0, 0, 145, 6, 0, 0, + 193, 6, 0, 0, 159, 4, 0, 0, 1, 6, 0, 0, 230, 7, 0, 0, 102, 3, 0, 0, 167, 4, 0, 0, 201, 6, 0, 0, 237, 7, 0, 0, 9, 6, 0, 0, 159, 4, 0, 0, 241, 7, 0, 0, 175, 4, 0, 0, 233, 8, 0, 0, 44, 7, 0, 0, 183, 4, 0, 0, 52, 7, 0, 0, 102, 3, 0, 0, 60, 7, 0, 0, 68, 7, 0, 0, 169, 3, 0, 0, 17, 6, 0, 0, 191, 4, 0, 0, 171, 5, 0, 0, 199, 4, 0, 0, 76, 7, 0, 0, + 84, 7, 0, 0, 179, 5, 0, 0, 207, 4, 0, 0, 177, 8, 0, 0, 40, 7, 0, 0, 249, 7, 0, 0, 215, 4, 0, 0, 133, 8, 0, 0, 92, 7, 0, 0, 223, 4, 0, 0, 231, 4, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 239, 4, 0, 0, 25, 6, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 247, 4, 0, 0, 1, 8, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, + 255, 4, 0, 0, 241, 8, 0, 0, 248, 8, 0, 0, 104, 8, 0, 0, 209, 6, 0, 0, 112, 8, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 7, 5, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 99, 7, 0, 0, 15, 5, 0, 0, 33, 6, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 105, 7, 0, 0, + 23, 5, 0, 0, 187, 5, 0, 0, 9, 8, 0, 0, 31, 5, 0, 0, 224, 6, 0, 0, 40, 7, 0, 0, 39, 5, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 17, 8, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 111, 7, 0, 0, 47, 5, 0, 0, 195, 5, 0, 0, 120, 8, 0, 0, 125, 8, 0, 0, 55, 5, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 118, 7, 0, 0, 25, 8, 0, 0, 63, 5, 0, 0, 125, 7, 0, 0, 71, 5, 0, 0, 82, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 33, 8, 0, 0, 79, 5, 0, 0, 41, 8, 0, 0, 41, 6, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 128, 7, 0, 0, 87, 5, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 82, 3, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 103, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 95, 5, 0, 0, 217, 6, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 111, 9, 0, 0, 234, 9, 0, 0, 226, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 151, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 40, 9, 0, 0, 44, 9, 0, 0, 49, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, + 40, 7, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 119, 9, 0, 0, 125, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 133, 9, 0, 0, 138, 9, 0, 0, 70, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 40, 7, 0, 0, 146, 9, 0, 0, 154, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 21, 8, 0, 0, 162, 9, 0, 0, 40, 7, 0, 0, 170, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 21, 8, 0, 0, 178, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 21, 8, 0, 0, 186, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 40, 7, 0, 0, 40, 7, 0, 0, 194, 9, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, + 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 254, 9, 0, 0, 6, 10, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, + 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, + 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, + 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 250, 9, 0, 0, 14, 10, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 64, 10, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, + 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 102, 3, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, + 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 80, 10, 0, 0, 80, 10, 0, 0, 80, 10, 0, 0, 80, 10, 0, 0, 80, 10, 0, 0, 80, 10, 0, 0, 80, 10, 0, 0, 84, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, + 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, + 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, + 72, 10, 0, 0, 72, 10, 0, 0, 72, 10, 0, 0, 69, 3, 1, 0, 69, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 11, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 14, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, - 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, - 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 4, 0, 0, 0, - 10, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 14, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 7, 0, 0, 0, 17, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, - 18, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, - 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, - 7, 0, 0, 0, 17, 0, 0, 0, 7, 0, 0, 0, 17, 0, 0, 0, 17, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, - 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, - 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 7, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 7, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 1, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, - 9, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, - 19, 0, 0, 0, 19, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, - 12, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 14, 0, 0, 0, 20, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, - 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 17, 0, 0, 0, 7, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 18, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 22, 0, 0, 0, 2, 0, 0, 0, 22, 0, 0, 0, 2, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, - 7, 0, 0, 0, 13, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, - 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, - 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 22, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, - 10, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 12, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 15, 0, 0, 0, 8, 0, 0, 0, 14, 0, 0, 0, 9, 0, 0, 0, 24, 0, 0, 0, 25, 0, 0, 0, 18, 0, 0, 0, 18, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 18, 0, 0, 0, 18, 0, 0, 0, 18, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 18, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 15, 0, 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, - 10, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, - 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 10, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 16, 0, 0, 0, 26, 0, 0, 0, 17, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, - 9, 0, 0, 0, 14, 0, 0, 0, 16, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 27, 0, 0, 0, 15, 0, 0, 0, 27, 0, 0, 0, 27, 0, 0, 0, - 27, 0, 0, 0, 27, 0, 0, 0, 27, 0, 0, 0, 27, 0, 0, 0, 28, 0, 0, 0, 9, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, - 13, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 25, 0, 0, 0, 18, 0, 0, 0, 23, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 29, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, - 13, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 9, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 26, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 17, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, - 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 14, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 30, 0, 0, 0, 31, 0, 0, 0, - 30, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 26, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 14, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 17, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 26, 0, 0, 0, - 26, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 16, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, - 15, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 8, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 34, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 14, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 7, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, - 10, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 24, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, - 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 14, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, - 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 13, 0, 0, 0, - 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 27, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, - 7, 0, 0, 0, 26, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 17, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 9, 0, 0, 0, 18, 0, 0, 0, 18, 0, 0, 0, 18, 0, 0, 0, 14, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, - 9, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 24, 0, 0, 0, 25, 0, 0, 0, 18, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 26, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 26, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, - 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 4, 0, 0, 0, 7, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 12, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, - 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 15, 0, 0, 0, 7, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 11, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, - 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 14, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, - 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 14, 0, 0, 0, 11, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 12, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 14, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 11, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, - 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 14, 0, 0, 0, 4, 0, 0, 0, 20, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 22, 0, 0, 0, 11, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, - 1, 0, 0, 0, 12, 0, 0, 0, 7, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 14, 0, 0, 0, 17, 0, 0, 0, 17, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 7, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 7, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 11, 0, 0, 0, - 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, - 8, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 11, 0, 0, 0, - 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, - 8, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 14, 0, 0, 0, 20, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 15, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 35, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 29, 0, 0, 0, 3, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 35, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 11, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 35, 0, 0, 0, + 35, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, + 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 34, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 31, 0, 0, 0, 35, 0, 0, 0, 36, 0, 0, 0, 37, 0, 0, 0, 12, 0, 0, 0, 31, 0, 0, 0, 27, 0, 0, 0, 28, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 1, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 33, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 41, 0, 0, 0, 22, 0, 0, 0, 29, 0, 0, 0, + 41, 0, 0, 0, 35, 0, 0, 0, 41, 0, 0, 0, 31, 0, 0, 0, 12, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 31, 0, 0, 0, 31, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 24, 0, 0, 0, 36, 0, 0, 0, 24, 0, 0, 0, 24, 0, 0, 0, 24, 0, 0, 0, 24, 0, 0, 0, 24, 0, 0, 0, 24, 0, 0, 0, 23, 0, 0, 0, 35, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 13, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 31, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, + 35, 0, 0, 0, 31, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 22, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 22, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, + 34, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, 41, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 6, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 22, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 35, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 37, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 11, 0, 0, 0, 11, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 6, 0, 0, 0, + 1, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 30, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 35, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 31, 0, 0, 0, 35, 0, 0, 0, 6, 0, 0, 0, 29, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 35, 0, 0, 0, 30, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 6, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 30, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 34, 0, 0, 0, 31, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, + 6, 0, 0, 0, 30, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, + 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 31, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 6, 0, 0, 0, 30, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 35, 0, 0, 0, + 37, 0, 0, 0, 6, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, + 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 33, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 42, 0, 0, 0, 32, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 11, 0, 0, 0, 5, 0, 0, 0, + 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, + 23, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 29, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 6, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, + 5, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 32, 0, 0, 0, 34, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 35, 0, 0, 0, 35, 0, 0, 0, 30, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 0, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 10, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, 27, 0, 0, 0, 35, 0, 0, 0, 26, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 34, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 30, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 30, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, + 31, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 12, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 29, 0, 0, 0, 35, 0, 0, 0, 29, 0, 0, 0, 29, 0, 0, 0, 29, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 29, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, + 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 29, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, 21, 0, 0, 0, 21, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 29, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 31, 0, 0, 0, 26, 0, 0, 0, 27, 0, 0, 0, 26, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 12, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, + 33, 0, 0, 0, 6, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 30, 0, 0, 0, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 10, 0, 0, 0, 27, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 31, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 6, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, + 33, 0, 0, 0, 33, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 31, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, + 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 6, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, + 31, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, + 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, 24, 0, 0, 0, 33, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 34, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 12, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 34, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 26, 0, 0, 0, 26, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 28, 0, 0, 0, 26, 0, 0, 0, 5, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, + 25, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 36, 0, 0, 0, + 1, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 37, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 34, 0, 0, 0, + 33, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 27, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 27, 0, 0, 0, 28, 0, 0, 0, 25, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 32, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 35, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, + 33, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 0, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 33, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 34, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 35, 0, 0, 0, 11, 0, 0, 0, 11, 0, 0, 0, 37, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 3, 0, 0, 0, + 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 37, 0, 0, 0, 35, 0, 0, 0, 37, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 28, 0, 0, 0, 25, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 10, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 14, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 11, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, + 5, 0, 0, 0, 5, 0, 0, 0, 11, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, + 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 22, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 22, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, + 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 43, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, + 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, + 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, - 12, 0, 0, 0, 9, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 14, 0, 0, 0, 11, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 14, 0, 0, 0, 11, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 13, 0, 0, 0, - 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, - 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 14, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 14, 0, 0, 0, 11, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 25, 0, 0, 0, 29, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, - 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 12, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, - 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 14, 0, 0, 0, 11, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 10, 0, 0, 0, 14, 0, 0, 0, 20, 0, 0, 0, - 24, 0, 0, 0, 20, 0, 0, 0, 18, 0, 0, 0, 11, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, - 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 14, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 22, 0, 0, 0, 13, 0, 0, 0, 7, 0, 0, 0, - 7, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 19, 0, 0, 0, 18, 0, 0, 0, 18, 0, 0, 0, 18, 0, 0, 0, 18, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 20, 0, 0, 0, 20, 0, 0, 0, 20, 0, 0, 0, 20, 0, 0, 0, 20, 0, 0, 0, 20, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, - 28, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, 28, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 17, 0, 0, 0, 14, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, - 10, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, - 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 14, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, - 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 13, 0, 0, 0, 12, 0, 0, 0, 13, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, - 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 11, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 14, 0, 0, 0, 20, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, - 10, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 7, 0, 0, 0, 7, 0, 0, 0, 20, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 12, 0, 0, 0, 12, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 14, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 25, 0, 0, 0, 25, 0, 0, 0, 24, 0, 0, 0, 7, 0, 0, 0, 18, 0, 0, 0, 13, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, - 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, - 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 30, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, + 35, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 35, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 29, 0, 0, 0, + 29, 0, 0, 0, 29, 0, 0, 0, 29, 0, 0, 0, 29, 0, 0, 0, 29, 0, 0, 0, 29, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 29, 0, 0, 0, 29, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 30, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, + 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, + 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 39, 0, 0, 0, + 39, 0, 0, 0, 39, 0, 0, 0, 39, 0, 0, 0, 39, 0, 0, 0, 39, 0, 0, 0, 39, 0, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, + 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, + 14, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 16, 0, 0, 0, 17, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 20, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 19, 0, 0, 0, + 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, + 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, 14, 0, 0, 0, + 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 17, 0, 0, 0, 16, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, + 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, + 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, + 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; } } diff --git a/src/SixLabors.Fonts/Unicode/Resources/VowelConstraintData.Generated.cs b/src/SixLabors.Fonts/Unicode/Resources/VowelConstraintData.Generated.cs new file mode 100644 index 000000000..a744efe93 --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/Resources/VowelConstraintData.Generated.cs @@ -0,0 +1,224 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Unicode.Resources; + +/// +/// The character sequences that spell one vowel but read as another. A font is +/// not asked to render such a sequence: a dotted circle is placed before its +/// final character so the sequence cannot be mistaken for the vowel it +/// imitates. Generated from IndicShapingInvalidCluster.txt. +/// +internal static class VowelConstraintData +{ + /// + /// The two character sequences as packed keys, in ascending order so a + /// lookup is a binary search. + /// + private static readonly ulong[] PairData = + [ + 0x380130A009BEUL, + 0x3801316009C3UL, + 0x3801318009E2UL, + 0x442200A11038UL, + 0x44220161103EUL, + 0x442201E11042UL, + 0x7C0120A0093AUL, + 0x7C0120A0093BUL, + 0x7C0120A0093EUL, + 0x7C0120A00945UL, + 0x7C0120A00946UL, + 0x7C0120A00949UL, + 0x7C0120A0094AUL, + 0x7C0120A0094BUL, + 0x7C0120A0094CUL, + 0x7C0120A0094FUL, + 0x7C0120A00956UL, + 0x7C0120A00957UL, + 0x7C0120C0093AUL, + 0x7C0120C00945UL, + 0x7C0120C00946UL, + 0x7C0120C00947UL, + 0x7C0120C00948UL, + 0x7C0121200941UL, + 0x7C0121E00945UL, + 0x7C0121E00946UL, + 0x7C0121E00947UL, + 0xBC0150A00ABEUL, + 0xBC0150A00AC5UL, + 0xBC0150A00AC7UL, + 0xBC0150A00AC8UL, + 0xBC0150A00AC9UL, + 0xBC0150A00ACBUL, + 0xBC0150A00ACCUL, + 0xBC0158A00ABEUL, + 0xC00140A00A3EUL, + 0xC00140A00A48UL, + 0xC00140A00A4CUL, + 0xC0014E400A3FUL, + 0xC0014E400A40UL, + 0xC0014E400A47UL, + 0xC0014E600A41UL, + 0xC0014E600A42UL, + 0xC0014E600A4BUL, + 0x108224001122CUL, + 0x1082240011231UL, + 0x1082240011233UL, + 0x1082240C1122CUL, + 0x1082245811230UL, + 0x1082245811231UL, + 0x108224801122EUL, + 0x1100191200CBEUL, + 0x1100191600CBEUL, + 0x1100192400CCCUL, + 0x16401A0E00D57UL, + 0x16401A1200D57UL, + 0x16401A1C00D46UL, + 0x16401A2400D3EUL, + 0x16401A2400D57UL, + 0x16822C0011639UL, + 0x16822C001163AUL, + 0x16822C0211639UL, + 0x16822C021163AUL, + 0x1A40160A00B3EUL, + 0x1A40161E00B57UL, + 0x1A40162600B57UL, + 0x20022560112E0UL, + 0x20022560112E5UL, + 0x20022560112E6UL, + 0x20022560112E7UL, + 0x20022560112E8UL, + 0x20401B0A00DCFUL, + 0x20401B0A00DD0UL, + 0x20401B0A00DD1UL, + 0x20401B1600DDFUL, + 0x20401B1A00DD8UL, + 0x20401B1E00DDFUL, + 0x20401B2200DCAUL, + 0x20401B2200DD9UL, + 0x20401B2200DDAUL, + 0x20401B2200DDCUL, + 0x20401B2200DDDUL, + 0x20401B2200DDEUL, + 0x20401B2800DDFUL, + 0x22822D00116ADUL, + 0x22822D00116B4UL, + 0x22822D00116B5UL, + 0x22822D0C116B2UL, + 0x2340170A00BC2UL, + 0x2400182400C4CUL, + 0x2400182400C55UL, + 0x2400187E00C55UL, + 0x2400188C00C55UL, + 0x2400189400C55UL, + 0x25822902114B0UL, + 0x25822916114BAUL, + 0x2582291A114BAUL, + 0x25822954114B5UL, + 0x25822954114B6UL, + ]; + + /// + /// The first two characters of each three character sequence, packed and + /// ordered as the pairs are. + /// + private static readonly ulong[] TripleData = + [ + 0x7C012600094DUL, + ]; + + /// + /// The final character of each three character sequence, positioned as its + /// packed key is. + /// + private static readonly int[] TripleFinalData = + [ + 0x0907, + ]; + + /// + /// Determines whether any sequence is written in the given script. Text in + /// any other script carries no constrained sequence and is left alone. + /// + /// The script the text is written in. + /// when the script carries sequences. + public static bool IsConstrainedScript(ScriptClass script) + => script switch + { + ScriptClass.Bengali => true, + ScriptClass.Brahmi => true, + ScriptClass.Devanagari => true, + ScriptClass.Gujarati => true, + ScriptClass.Gurmukhi => true, + ScriptClass.Kannada => true, + ScriptClass.Khojki => true, + ScriptClass.Khudawadi => true, + ScriptClass.Malayalam => true, + ScriptClass.Modi => true, + ScriptClass.Oriya => true, + ScriptClass.Sinhala => true, + ScriptClass.Takri => true, + ScriptClass.Tamil => true, + ScriptClass.Telugu => true, + ScriptClass.Tirhuta => true, + _ => false, + }; + + /// + /// Determines whether the two characters spell a constrained sequence. + /// + /// The script the text is written in. + /// The character that begins the sequence. + /// The character that follows it. + /// when the two are constrained. + public static bool IsConstrainedPair(ScriptClass script, int first, int second) + => Array.BinarySearch(PairData, Key(script, first, second)) >= 0; + + /// + /// Determines whether the three characters spell a constrained sequence. + /// + /// The script the text is written in. + /// The character that begins the sequence. + /// The character that follows it. + /// The character that ends it. + /// when the three are constrained. + public static bool IsConstrainedTriple(ScriptClass script, int first, int second, int third) + { + ulong key = Key(script, first, second); + int index = Array.BinarySearch(TripleData, key); + if (index < 0) + { + return false; + } + + // Sequences sharing their first two characters sit together, so the + // run around the found position holds every candidate final character. + ReadOnlySpan keys = TripleData; + ReadOnlySpan finals = TripleFinalData; + int start = index; + while (start > 0 && keys[start - 1] == key) + { + start--; + } + + for (int i = start; i < keys.Length && keys[i] == key; i++) + { + if (finals[i] == third) + { + return true; + } + } + + return false; + } + + /// + /// Packs a script and two characters into one ordered key. + /// + /// The script the text is written in. + /// The character that begins the sequence. + /// The character that follows it. + /// The packed key. + private static ulong Key(ScriptClass script, int first, int second) + => ((ulong)script << 42) | ((ulong)(uint)first << 21) | (uint)second; +} diff --git a/src/SixLabors.Fonts/Unicode/ScriptClass.cs b/src/SixLabors.Fonts/Unicode/ScriptClass.cs index c4697bc0b..75f5628a8 100644 --- a/src/SixLabors.Fonts/Unicode/ScriptClass.cs +++ b/src/SixLabors.Fonts/Unicode/ScriptClass.cs @@ -4,13 +4,14 @@ namespace SixLabors.Fonts.Unicode; /// -/// Unicode Script property values. +/// Script values used when shaping text. /// /// /// /// and are Unicode script values. /// is an OpenType fallback tag rather than a Unicode Script -/// property value. +/// property value. explicitly selects shaping for the +/// legacy Zawgyi encoding and is not a Unicode Script property value. /// public enum ScriptClass { @@ -897,5 +898,10 @@ public enum ScriptClass /// /// OpenType default script tag. Shortcode: DFLT. /// - Default = 999 + Default = 999, + + /// + /// Myanmar encoded using the legacy Zawgyi convention. Shortcode: Qaag. + /// + MyanmarZawgyi = 1000 } diff --git a/src/SixLabors.Fonts/Unicode/ScriptHorizontalDirection.cs b/src/SixLabors.Fonts/Unicode/ScriptHorizontalDirection.cs new file mode 100644 index 000000000..22e6cb900 --- /dev/null +++ b/src/SixLabors.Fonts/Unicode/ScriptHorizontalDirection.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.Fonts.Unicode; + +/// +/// The direction a script is written in when it is set horizontally. +/// +internal enum ScriptHorizontalDirection +{ + /// + /// The script is written from left to right. + /// + LeftToRight, + + /// + /// The script is written from right to left. + /// + RightToLeft, + + /// + /// The script is written either way, so a run of it is left in the order it + /// arrived rather than being turned around. + /// + Either +} diff --git a/src/SixLabors.Fonts/Unicode/SpanGraphemeEnumerator.cs b/src/SixLabors.Fonts/Unicode/SpanGraphemeEnumerator.cs index 40e9c20c7..7c57483b4 100644 --- a/src/SixLabors.Fonts/Unicode/SpanGraphemeEnumerator.cs +++ b/src/SixLabors.Fonts/Unicode/SpanGraphemeEnumerator.cs @@ -18,6 +18,7 @@ public ref struct SpanGraphemeEnumerator { private ReadOnlySpan source; private readonly TerminalWidthOptions terminalWidthOptions; + private readonly bool countOnly; private int sourceOffset; /// @@ -38,8 +39,30 @@ public SpanGraphemeEnumerator(ReadOnlySpan source, TerminalWidthOptions te { this.source = source; this.terminalWidthOptions = terminalWidthOptions; + this.countOnly = false; this.sourceOffset = 0; this.Current = default; + this.CurrentSpan = default; + } + + /// + /// Initializes a new instance of the struct + /// that walks cluster boundaries without producing cluster metadata. + /// + /// The buffer to read from. + /// + /// Whether enumeration only advances over boundaries. When set, + /// stays default and no width, emoji, or flag metadata is + /// computed, leaving a pure boundary walk for callers that only need a count. + /// + internal SpanGraphemeEnumerator(ReadOnlySpan source, bool countOnly) + { + this.source = source; + this.terminalWidthOptions = TerminalWidthOptions.Default; + this.countOnly = countOnly; + this.sourceOffset = 0; + this.Current = default; + this.CurrentSpan = default; } /// @@ -47,6 +70,12 @@ public SpanGraphemeEnumerator(ReadOnlySpan source, TerminalWidthOptions te /// public GraphemeCluster Current { get; private set; } + /// + /// Gets the UTF-16 span of the grapheme at the current position without + /// requiring its terminal-width metadata to be produced. + /// + internal ReadOnlySpan CurrentSpan { get; private set; } + /// /// Returns an enumerator that iterates through the collection. /// @@ -62,11 +91,49 @@ public SpanGraphemeEnumerator(ReadOnlySpan source, TerminalWidthOptions te /// public bool MoveNext() { + if (this.source.IsEmpty) + { + return false; + } + + // A printable ASCII value followed by another ASCII value, or by the end + // of the input, always forms a single-scalar cluster: no ASCII value + // extends, joins, or prepends, and any scalar that could attach to the + // cluster is necessarily non-ASCII. Such a cluster's metadata is fixed + // apart from the emoji flag carried by the keycap bases, so the boundary + // machine below never needs to run for it. The leading test is the + // printable range, deliberately narrower than the ASCII check used for + // the follower: ASCII controls carry real boundary rules (a carriage + // return joins a following line feed into one cluster) and must reach + // the machine. + char first = this.source[0]; + if (first is >= ' ' and <= '~' && (this.source.Length == 1 || UnicodeUtility.IsAsciiCodePoint(this.source[1]))) + { + ReadOnlySpan asciiGrapheme = this.source[..1]; + this.CurrentSpan = asciiGrapheme; + if (!this.countOnly) + { + CodePoint codePoint = new(first); + GraphemeClusterFlags flags = GraphemeClusterFlags.IsSingleCodePoint; + if ((CodePoint.GetEmojiProperties(codePoint) & EmojiProperties.Emoji) != 0) + { + flags |= GraphemeClusterFlags.ContainsEmoji; + } + + this.Current = new GraphemeCluster(asciiGrapheme, this.sourceOffset, 1, 1, flags, codePoint); + } + + this.source = this.source[1..]; + this.sourceOffset++; + return true; + } + // GB9c is a stateful rule: whether the next consonant can join depends on // the InCB classes already consumed into the current cluster. Keep that state // outside Processor so Processor remains a simple UTF-16/code-point reader. IndicConjunctState indicConjunctState = default; TerminalWidthState terminalWidthState = new(this.terminalWidthOptions); + bool boundariesOnly = this.countOnly; int utf16Offset = this.sourceOffset; // Accept the current scalar into the cluster and advance to the next scalar. @@ -74,7 +141,11 @@ public bool MoveNext() void ConsumeCurrentAndAdvance(ref Processor p) { indicConjunctState.Consume(p.CurrentCodePoint); - terminalWidthState.Consume(p.CurrentCodePoint, p.CurrentType); + if (!boundariesOnly) + { + terminalWidthState.Consume(p.CurrentCodePoint, p.CurrentType); + } + p.MoveNext(); } @@ -253,15 +324,19 @@ or GraphemeClusterClass.CarriageReturn Return: - terminalWidthState.Complete(); ReadOnlySpan grapheme = this.source[..processor.CharsConsumed]; - this.Current = new GraphemeCluster( - grapheme, - utf16Offset, - terminalWidthState.CodePointCount, - terminalWidthState.TerminalCellWidth, - terminalWidthState.Flags, - terminalWidthState.FirstCodePoint); + this.CurrentSpan = grapheme; + if (!boundariesOnly) + { + terminalWidthState.Complete(); + this.Current = new GraphemeCluster( + grapheme, + utf16Offset, + terminalWidthState.CodePointCount, + terminalWidthState.TerminalCellWidth, + terminalWidthState.Flags, + terminalWidthState.FirstCodePoint); + } this.source = this.source[processor.CharsConsumed..]; this.sourceOffset += processor.CharsConsumed; diff --git a/src/SixLabors.Fonts/Unicode/UnicodeData.cs b/src/SixLabors.Fonts/Unicode/UnicodeData.cs index 3d42765b7..48a99185c 100644 --- a/src/SixLabors.Fonts/Unicode/UnicodeData.cs +++ b/src/SixLabors.Fonts/Unicode/UnicodeData.cs @@ -9,22 +9,23 @@ namespace SixLabors.Fonts.Unicode; internal static class UnicodeData { - private static readonly Lazy LazyBidiTrie = new(() => GetBidiTrie(), true); - private static readonly Lazy LazyBidiMirrorTrie = new(() => GetBidiMirrorTrie(), true); - private static readonly Lazy LazyEastAsianWidthTrie = new(() => GetEastAsianWidthTrie(), true); - private static readonly Lazy LazyEmojiTrie = new(() => GetEmojiTrie(), true); - private static readonly Lazy LazyGraphemeTrie = new(() => GetGraphemeTrie(), true); - private static readonly Lazy LazyLineBreakTrie = new(() => GetLineBreakTrie(), true); - private static readonly Lazy LazyWordBreakTrie = new(() => GetWordBreakTrie(), true); - private static readonly Lazy LazyScriptTrie = new(() => GetScriptTrie(), true); - private static readonly Lazy LazyCategoryTrie = new(() => GetCategoryTrie(), true); - private static readonly Lazy LazyArabicShapingTrie = new(() => GetArabicShapingTrie(), true); - private static readonly Lazy LazyIndicConjunctBreakTrie = new(() => GetIndicConjunctBreakTrie(), true); - private static readonly Lazy LazyIndicSyllabicCategoryTrie = new(() => GetIndicSyllabicCategoryTrie(), true); - private static readonly Lazy LazyIndicPositionalCategoryTrie = new(() => GetIndicPositionalCategoryTrie(), true); - private static readonly Lazy LazyVerticalOrientationTrie = new(() => GetVerticalOrientationTrie(), true); - private static readonly Lazy LazyUniversalShapingTrie = new(() => GetUniversalShapingTrie(), true); - private static readonly Lazy LazyIndicShapingTrie = new(() => GetIndicShapingTrie(), true); + private static readonly Lazy LazyBidiTrie = new(GetBidiTrie, true); + private static readonly Lazy LazyBidiMirrorTrie = new(GetBidiMirrorTrie, true); + private static readonly Lazy LazyEastAsianWidthTrie = new(GetEastAsianWidthTrie, true); + private static readonly Lazy LazyEmojiTrie = new(GetEmojiTrie, true); + private static readonly Lazy LazyGraphemeTrie = new(GetGraphemeTrie, true); + private static readonly Lazy LazyLineBreakTrie = new(GetLineBreakTrie, true); + private static readonly Lazy LazyWordBreakTrie = new(GetWordBreakTrie, true); + private static readonly Lazy LazyScriptTrie = new(GetScriptTrie, true); + private static readonly Lazy LazyCategoryTrie = new(GetCategoryTrie, true); + private static readonly Lazy LazyArabicShapingTrie = new(GetArabicShapingTrie, true); + private static readonly Lazy LazyIndicConjunctBreakTrie = new(GetIndicConjunctBreakTrie, true); + private static readonly Lazy LazyIndicSyllabicCategoryTrie = new(GetIndicSyllabicCategoryTrie, true); + private static readonly Lazy LazyIndicPositionalCategoryTrie = new(GetIndicPositionalCategoryTrie, true); + private static readonly Lazy LazyVerticalOrientationTrie = new(GetVerticalOrientationTrie, true); + private static readonly Lazy LazyUniversalShapingTrie = new(GetUniversalShapingTrie, true); + private static readonly Lazy LazyIndicShapingTrie = new(GetIndicShapingTrie, true); + private static readonly Lazy LazyCanonicalCombiningClassTrie = new(GetCanonicalCombiningClassTrie, true); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint GetBidiData(uint codePoint) => LazyBidiTrie.Value.Get(codePoint); @@ -74,6 +75,36 @@ internal static class UnicodeData [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetIndicShapingProperties(uint codePoint) => (int)LazyIndicShapingTrie.Value.Get(codePoint); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetCanonicalCombiningClass(uint codePoint) => (int)LazyCanonicalCombiningClassTrie.Value.Get(codePoint); + + /// + /// Gets the class that orders a mark against the marks around it: the + /// canonical combining class with the classes of several scripts renumbered so + /// that sorting by it leaves their marks in the order they are drawn. + /// + /// The code point to evaluate. + /// The ordering class. + public static int GetMarkOrderingClass(uint codePoint) + { + // A few characters order by where they are drawn rather than by their class, + // so they are resolved before the table is consulted. + if (MarkOrderingData.TryGetOverride(codePoint, out byte order)) + { + return order; + } + + return MarkOrderingData.Classes[GetCanonicalCombiningClass(codePoint)]; + } + + /// + /// Determines whether an Arabic mark modifies the combining mark that follows it. + /// + /// The code point to evaluate. + /// when the character is a modifier combining mark; otherwise, . + public static bool IsArabicModifierCombiningMark(uint codePoint) + => MarkOrderingData.IsArabicModifierCombiningMark(codePoint); + private static UnicodeTrie GetBidiTrie() => new(BidiTrie.Data); private static UnicodeTrie GetBidiMirrorTrie() => new(BidiMirrorTrie.Data); @@ -94,6 +125,8 @@ internal static class UnicodeData private static UnicodeTrie GetArabicShapingTrie() => new(ArabicShapingTrie.Data); + private static UnicodeTrie GetCanonicalCombiningClassTrie() => new(CanonicalCombiningClassTrie.Data); + private static UnicodeTrie GetIndicConjunctBreakTrie() => new(IndicConjunctBreakTrie.Data); private static UnicodeTrie GetIndicSyllabicCategoryTrie() => new(IndicSyllabicCategoryTrie.Data); diff --git a/src/SixLabors.Fonts/Unicode/UnicodeUtility.cs b/src/SixLabors.Fonts/Unicode/UnicodeUtility.cs index 95ff31c46..d2b5b46b5 100644 --- a/src/SixLabors.Fonts/Unicode/UnicodeUtility.cs +++ b/src/SixLabors.Fonts/Unicode/UnicodeUtility.cs @@ -147,169 +147,65 @@ public static uint GetVerticalMirror(uint value) /// public static bool IsDefaultIgnorableCodePoint(uint value) { - // SOFT HYPHEN - if (value == 0x00AD) - { - return true; - } - - // COMBINING GRAPHEME JOINER - if (value == 0x034F) - { - return true; - } - - // COMBINING GRAPHEME JOINER - if (value == 0x061C) - { - return true; - } - - // HANGUL CHOSEONG FILLER..HANGUL JUNGSEONG FILLER - if (IsInRangeInclusive(value, 0x115F, 0x1160)) - { - return true; - } - - // KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA - if (IsInRangeInclusive(value, 0x17B4, 0x17B5)) - { - return true; - } - - // MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE - if (IsInRangeInclusive(value, 0x180B, 0x180D)) - { - return true; - } - - // MONGOLIAN VOWEL SEPARATOR - if (value == 0x180E) - { - return true; - } - - // MONGOLIAN FREE VARIATION SELECTOR FOUR - if (value == 0x180F) - { - return true; - } - - // ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK - if (IsInRangeInclusive(value, 0x200B, 0x200F)) - { - return true; - } - - // LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE - if (IsInRangeInclusive(value, 0x202A, 0x202E)) - { - return true; - } - - // WORD JOINER..INVISIBLE PLUS - if (IsInRangeInclusive(value, 0x2060, 0x2064)) - { - return true; - } - - // - if (value == 0x2065) - { - return true; - } - - // LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES - if (IsInRangeInclusive(value, 0x2066, 0x206F)) - { - return true; - } - - // HANGUL FILLER - if (value == 0x3164) - { - return true; - } - - // VARIATION SELECTOR-1..VARIATION SELECTOR-16 - if (IsInRangeInclusive(value, 0xFE00, 0xFE0F)) - { - return true; + uint plane = value >> 16; + if (plane == 0) + { + uint page = value >> 8; + switch (page) + { + case 0x00: + // SOFT HYPHEN + return value == 0x00AD; + case 0x03: + // COMBINING GRAPHEME JOINER + return value == 0x034F; + case 0x06: + // ARABIC LETTER MARK + return value == 0x061C; + case 0x11: + // HANGUL CHOSEONG FILLER..HANGUL JUNGSEONG FILLER + return IsInRangeInclusive(value, 0x115F, 0x1160); + case 0x17: + // KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA + return IsInRangeInclusive(value, 0x17B4, 0x17B5); + case 0x18: + // MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN VOWEL SEPARATOR + return IsInRangeInclusive(value, 0x180B, 0x180E); + case 0x20: + // ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK, + // LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE, + // WORD JOINER..NOMINAL DIGIT SHAPES + return IsInRangeInclusive(value, 0x200B, 0x200F) + || IsInRangeInclusive(value, 0x202A, 0x202E) + || IsInRangeInclusive(value, 0x2060, 0x206F); + case 0x31: + // HANGUL FILLER + return value == 0x3164; + case 0xFE: + // VARIATION SELECTOR-1..VARIATION SELECTOR-16, ZERO WIDTH NO-BREAK SPACE + return IsInRangeInclusive(value, 0xFE00, 0xFE0F) || value == 0xFEFF; + case 0xFF: + // HALFWIDTH HANGUL FILLER, .. + return value == 0xFFA0 || IsInRangeInclusive(value, 0xFFF0, 0xFFF8); + default: + return false; + } + } + + switch (plane) + { + case 0x01: + // SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP, + // MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE + return IsInRangeInclusive(value, 0x1BCA0, 0x1BCA3) + || IsInRangeInclusive(value, 0x1D173, 0x1D17A); + case 0x0E: + // ..: LANGUAGE TAG, TAG SPACE..CANCEL TAG, + // VARIATION SELECTOR-17..VARIATION SELECTOR-256, and the reserved ranges. + return IsInRangeInclusive(value, 0xE0000, 0xE0FFF); + default: + return false; } - - // ZERO WIDTH NO-BREAK SPACE - if (value == 0xFEFF) - { - return true; - } - - // HALFWIDTH HANGUL FILLER - if (value == 0xFFA0) - { - return true; - } - - // .. - if (IsInRangeInclusive(value, 0xFFF0, 0xFFF8)) - { - return true; - } - - // SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP - if (IsInRangeInclusive(value, 0x1BCA0, 0x1BCA3)) - { - return true; - } - - // MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE - if (IsInRangeInclusive(value, 0x1D173, 0x1D17A)) - { - return true; - } - - // - if (value == 0xE0000) - { - return true; - } - - // LANGUAGE TAG - if (value == 0xE0001) - { - return true; - } - - // .. - if (IsInRangeInclusive(value, 0xE0002, 0xE001F)) - { - return true; - } - - // TAG SPACE..CANCEL TAG - if (IsInRangeInclusive(value, 0xE0020, 0xE007F)) - { - return true; - } - - // .. - if (IsInRangeInclusive(value, 0xE0080, 0xE00FF)) - { - return true; - } - - // VARIATION SELECTOR-17..VARIATION SELECTOR-256 - if (IsInRangeInclusive(value, 0xE0100, 0xE01EF)) - { - return true; - } - - // .. - if (IsInRangeInclusive(value, 0xE01F0, 0xE0FFF)) - { - return true; - } - - return false; } /// @@ -344,15 +240,6 @@ public static bool ShouldRenderWhiteSpaceOnly(in CodePoint codePoint) return false; } - /// - /// Gets a value indicating whether the specified code point should not be rendered. - /// - /// The code point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ShouldNotBeRendered(in CodePoint codePoint) - => CodePoint.IsNewLine(codePoint) || (IsDefaultIgnorableCodePoint((uint)codePoint.Value) && !ShouldRenderWhiteSpaceOnly(codePoint)); - /// /// Returns the Unicode plane (0 through 16, inclusive) which contains this code point. /// diff --git a/src/UnicodeTrieGenerator/Generator.ArabicFallback.cs b/src/UnicodeTrieGenerator/Generator.ArabicFallback.cs new file mode 100644 index 000000000..2a9c14e02 --- /dev/null +++ b/src/UnicodeTrieGenerator/Generator.ArabicFallback.cs @@ -0,0 +1,291 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; +using System.Text.RegularExpressions; + +namespace UnicodeTrieGenerator; + +/// +/// Contains code to generate Arabic presentation-form fallback data. +/// +public static partial class Generator +{ + /// + /// The number of presentation-form columns in each shaping row. + /// + private const int ArabicPresentationFormCount = 4; + + /// + /// Matches one row of the Arabic presentation-form table. + /// + [GeneratedRegex(@"\{\s*0x(?[0-9A-F]+)u,\s*0x(?[0-9A-F]+)u,\s*0x(?[0-9A-F]+)u,\s*0x(?[0-9A-F]+)u\s*\},\s*/\*\s*U\+(?[0-9A-F]+)\s*(?.*?)\s*\*/")] + private static partial Regex ArabicPresentationFormRegex(); + + /// + /// Matches one first-character set in an Arabic ligature table. + /// + [GeneratedRegex(@"\{\s*0x(?[0-9A-F]+)u,\s*\{(?.*?)\}\s*\},", RegexOptions.Singleline)] + private static partial Regex ArabicLigatureSetRegex(); + + /// + /// Matches one ligature and its component characters. + /// + [GeneratedRegex(@"\{\s*\{(?(?:\s*0x[0-9A-F]+u\s*,?)+)\},\s*0x(?[0-9A-F]+)u\s*\}\s*,?\s*/\*\s*(?.*?)\s*\*/")] + private static partial Regex ArabicLigatureRegex(); + + /// + /// Matches one hexadecimal character code in a ligature component list. + /// + [GeneratedRegex(@"0x(?[0-9A-F]+)u")] + private static partial Regex ArabicLigatureComponentRegex(); + + /// + /// Generates Arabic presentation-form and required-ligature fallback data. + /// + /// + /// The presentation forms and ligatures come from the compatibility decompositions in + /// UnicodeData.txt. The three private-use compatibility additions are not part of + /// the Unicode Character Database. + /// + private static void GenerateArabicFallbackData() + { + string source = File.ReadAllText(GetReferenceSourcePath("hb-ot-shaper-arabic-table.hh")); + List<(ushort CodePoint, ushort Initial, ushort Medial, ushort Final, ushort Isolated, string Name)> forms = ReadArabicPresentationForms(source); + List<(ushort First, ushort Second, ushort Third, ushort Ligature, string Name)> threeCharacterLigatures = ReadArabicLigatures(source, "ligature_3_table", 2); + List<(ushort First, ushort Second, ushort Third, ushort Ligature, string Name)> twoCharacterLigatures = ReadArabicLigatures(source, "ligature_table", 1); + List<(ushort First, ushort Second, ushort Third, ushort Ligature, string Name)> markLigatures = ReadArabicLigatures(source, "ligature_mark_table", 1); + + using FileStream fileStream = GetStreamWriter("ArabicFallbackData.Generated.cs"); + using StreamWriter writer = new(fileStream); + + writer.WriteLine("// Copyright (c) Six Labors."); + writer.WriteLine("// Licensed under the Six Labors Split License."); + writer.WriteLine(); + writer.WriteLine("// "); + writer.WriteLine("using System;"); + writer.WriteLine("using System.Buffers.Binary;"); + writer.WriteLine(); + writer.WriteLine("namespace SixLabors.Fonts.Unicode.Resources"); + writer.WriteLine("{"); + writer.WriteLine(" /// "); + writer.WriteLine(" /// Provides Arabic presentation forms and required ligatures for fonts without substitution features."); + writer.WriteLine(" /// "); + writer.WriteLine(" internal static class ArabicFallbackData"); + writer.WriteLine(" {"); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The number of presentation forms stored for each character."); + writer.WriteLine(" /// "); + writer.WriteLine($" public const int PresentationFormCount = {ArabicPresentationFormCount};"); + writer.WriteLine(); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The number of bytes in one character and its presentation forms."); + writer.WriteLine(" /// "); + writer.WriteLine(" private const int PresentationEntrySize = sizeof(ushort) * (PresentationFormCount + 1);"); + writer.WriteLine(); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The byte offset of the first presentation form in an entry."); + writer.WriteLine(" /// "); + writer.WriteLine(" private const int FirstPresentationFormOffset = sizeof(ushort);"); + writer.WriteLine(); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The number of bytes in one two-character ligature entry."); + writer.WriteLine(" /// "); + writer.WriteLine(" public const int TwoCharacterLigatureEntrySize = sizeof(ushort) * 3;"); + writer.WriteLine(); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The number of bytes in one three-character ligature entry."); + writer.WriteLine(" /// "); + writer.WriteLine(" public const int ThreeCharacterLigatureEntrySize = sizeof(ushort) * 4;"); + writer.WriteLine(); + writer.WriteLine(" /// "); + writer.WriteLine(" /// Gets the requested presentation form for an Arabic character."); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The Arabic character."); + writer.WriteLine(" /// The zero-based form index: initial, medial, final, or isolated."); + writer.WriteLine(" /// The presentation-form character, or zero when no such form exists."); + writer.WriteLine(" public static ushort GetPresentationForm(int codePoint, int formIndex)"); + writer.WriteLine(" {"); + writer.WriteLine(" ReadOnlySpan forms = PresentationForms;"); + writer.WriteLine(" int low = 0;"); + writer.WriteLine(" int high = (forms.Length / PresentationEntrySize) - 1;"); + writer.WriteLine(" while (low <= high)"); + writer.WriteLine(" {"); + writer.WriteLine(" int middle = low + ((high - low) >> 1);"); + writer.WriteLine(" int offset = middle * PresentationEntrySize;"); + writer.WriteLine(" ushort candidate = BinaryPrimitives.ReadUInt16LittleEndian(forms.Slice(offset, sizeof(ushort)));"); + writer.WriteLine(" if (codePoint < candidate)"); + writer.WriteLine(" {"); + writer.WriteLine(" high = middle - 1;"); + writer.WriteLine(" }"); + writer.WriteLine(" else if (codePoint > candidate)"); + writer.WriteLine(" {"); + writer.WriteLine(" low = middle + 1;"); + writer.WriteLine(" }"); + writer.WriteLine(" else"); + writer.WriteLine(" {"); + writer.WriteLine(" int formOffset = offset + FirstPresentationFormOffset + (formIndex * sizeof(ushort));"); + writer.WriteLine(" return BinaryPrimitives.ReadUInt16LittleEndian(forms.Slice(formOffset, sizeof(ushort)));"); + writer.WriteLine(" }"); + writer.WriteLine(" }"); + writer.WriteLine(); + writer.WriteLine(" return 0;"); + writer.WriteLine(" }"); + writer.WriteLine(); + + WriteArabicPresentationForms(writer, forms); + writer.WriteLine(); + WriteArabicLigatures(writer, "ThreeCharacterLigatures", "three-character", threeCharacterLigatures, true); + writer.WriteLine(); + WriteArabicLigatures(writer, "TwoCharacterLigatures", "two-character", twoCharacterLigatures, false); + writer.WriteLine(); + WriteArabicLigatures(writer, "MarkLigatures", "mark", markLigatures, false); + + writer.WriteLine(" }"); + writer.WriteLine("}"); + } + + /// + /// Reads the Arabic presentation-form rows. + /// + /// The source table text. + /// The presentation-form rows in character order. + private static List<(ushort CodePoint, ushort Initial, ushort Medial, ushort Final, ushort Isolated, string Name)> ReadArabicPresentationForms(string source) + { + List<(ushort CodePoint, ushort Initial, ushort Medial, ushort Final, ushort Isolated, string Name)> forms = []; + foreach (Match match in ArabicPresentationFormRegex().Matches(source)) + { + forms.Add(( + ParseHexUInt16(match.Groups["codePoint"].Value), + ParseHexUInt16(match.Groups["initial"].Value), + ParseHexUInt16(match.Groups["medial"].Value), + ParseHexUInt16(match.Groups["final"].Value), + ParseHexUInt16(match.Groups["isolated"].Value), + match.Groups["name"].Value.Trim())); + } + + if (forms.Count == 0) + { + throw new InvalidDataException("Found no Arabic presentation forms in the reference table."); + } + + forms.Sort(static (left, right) => left.CodePoint.CompareTo(right.CodePoint)); + return forms; + } + + /// + /// Reads one required-ligature table. + /// + /// The source table text. + /// The table name. + /// The number of characters following the first. + /// The ligature entries in source order. + private static List<(ushort First, ushort Second, ushort Third, ushort Ligature, string Name)> ReadArabicLigatures(string source, string tableName, int componentCount) + { + int tableNameIndex = source.IndexOf($"{tableName}[]", StringComparison.Ordinal); + int tableStart = tableNameIndex >= 0 ? source.IndexOf('{', tableNameIndex) : -1; + int tableEnd = tableStart >= 0 ? source.IndexOf("\n};", tableStart, StringComparison.Ordinal) : -1; + if (tableStart < 0 || tableEnd < 0) + { + throw new InvalidDataException($"Found no {tableName} in the reference table."); + } + + string table = source[tableStart..tableEnd]; + List<(ushort First, ushort Second, ushort Third, ushort Ligature, string Name)> ligatures = []; + foreach (Match setMatch in ArabicLigatureSetRegex().Matches(table)) + { + ushort first = ParseHexUInt16(setMatch.Groups["first"].Value); + foreach (Match ligatureMatch in ArabicLigatureRegex().Matches(setMatch.Groups["body"].Value)) + { + MatchCollection componentMatches = ArabicLigatureComponentRegex().Matches(ligatureMatch.Groups["components"].Value); + if (componentMatches.Count != componentCount) + { + throw new InvalidDataException($"A {tableName} entry has {componentMatches.Count} components instead of {componentCount}."); + } + + ushort second = ParseHexUInt16(componentMatches[0].Groups["value"].Value); + ushort third = componentCount == 2 ? ParseHexUInt16(componentMatches[1].Groups["value"].Value) : (ushort)0; + ligatures.Add(( + first, + second, + third, + ParseHexUInt16(ligatureMatch.Groups["ligature"].Value), + ligatureMatch.Groups["name"].Value.Trim())); + } + } + + if (ligatures.Count == 0) + { + throw new InvalidDataException($"Found no entries in {tableName}."); + } + + return ligatures; + } + + /// + /// Parses a two-byte hexadecimal character code. + /// + /// The hexadecimal digits. + /// The parsed character code. + private static ushort ParseHexUInt16(string value) + => ushort.Parse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + + /// + /// Writes the presentation-form rows as packed little-endian bytes. + /// + /// The output writer. + /// The rows to write. + private static void WriteArabicPresentationForms(StreamWriter writer, List<(ushort CodePoint, ushort Initial, ushort Medial, ushort Final, ushort Isolated, string Name)> forms) + { + writer.WriteLine(" /// "); + writer.WriteLine(" /// The Arabic character rows in initial, medial, final, and isolated order."); + writer.WriteLine(" /// "); + writer.WriteLine(" private static ReadOnlySpan PresentationForms =>"); + writer.WriteLine(" ["); + + foreach ((ushort codePoint, ushort initial, ushort medial, ushort final, ushort isolated, string name) in forms) + { + string label = name.Length == 0 ? $"U+{codePoint:X4}" : $"U+{codePoint:X4} {name}"; + writer.WriteLine( + $" {FormatUInt16Bytes(codePoint)}, {FormatUInt16Bytes(initial)}, {FormatUInt16Bytes(medial)}, {FormatUInt16Bytes(final)}, {FormatUInt16Bytes(isolated)}, // {label}"); + } + + writer.WriteLine(" ];"); + } + + /// + /// Writes a required-ligature table as packed little-endian bytes. + /// + /// The output writer. + /// The generated property name. + /// The human-readable table label. + /// The entries to write. + /// Whether each entry has a third character. + private static void WriteArabicLigatures(StreamWriter writer, string propertyName, string label, List<(ushort First, ushort Second, ushort Third, ushort Ligature, string Name)> ligatures, bool includeThird) + { + writer.WriteLine(" /// "); + writer.WriteLine($" /// The {label} required ligatures."); + writer.WriteLine(" /// "); + writer.WriteLine($" public static ReadOnlySpan {propertyName} =>"); + writer.WriteLine(" ["); + + foreach ((ushort first, ushort second, ushort third, ushort ligature, string name) in ligatures) + { + string components = includeThird + ? $"{FormatUInt16Bytes(first)}, {FormatUInt16Bytes(second)}, {FormatUInt16Bytes(third)}" + : $"{FormatUInt16Bytes(first)}, {FormatUInt16Bytes(second)}"; + + writer.WriteLine($" {components}, {FormatUInt16Bytes(ligature)}, // {name}"); + } + + writer.WriteLine(" ];"); + } + + /// + /// Formats a two-byte value as little-endian byte literals. + /// + /// The value to format. + /// The two byte literals. + private static string FormatUInt16Bytes(ushort value) + => $"0x{value & LowByteMask:X2}, 0x{value >> HighByteShift:X2}"; +} diff --git a/src/UnicodeTrieGenerator/Generator.ArabicLegacyEncoding.cs b/src/UnicodeTrieGenerator/Generator.ArabicLegacyEncoding.cs new file mode 100644 index 000000000..930eed793 --- /dev/null +++ b/src/UnicodeTrieGenerator/Generator.ArabicLegacyEncoding.cs @@ -0,0 +1,186 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; + +namespace UnicodeTrieGenerator; + +/// +/// Contains code to generate legacy Arabic character mappings. +/// +public static partial class Generator +{ + /// + /// The number of bytes in one generated mapping entry. + /// + private const int ArabicLegacyMappingEntrySize = sizeof(ushort) * 2; + + /// + /// The mask selecting the low byte of a two-byte character code. + /// + private const ushort LowByteMask = byte.MaxValue; + + /// + /// The shift selecting the high byte of a two-byte character code. + /// + private const int HighByteShift = 8; + + /// + /// Generates the legacy Simplified and Traditional Arabic character mappings. + /// + /// + /// The mappings are defined by + /// Microsoft's legacy Arabic font documentation. + /// The generator reads its machine-readable copies from tests/harfbuzz/src/ArabicPUASimplified.txt + /// and tests/harfbuzz/src/ArabicPUATraditional.txt. They are not derivable from the Unicode Character Database. + /// + private static void GenerateArabicLegacyEncodingData() + { + List<(ushort CodePoint, ushort MappedCodePoint, string Name)> simplified = ReadArabicLegacyMappings("ArabicPUASimplified.txt"); + List<(ushort CodePoint, ushort MappedCodePoint, string Name)> traditional = ReadArabicLegacyMappings("ArabicPUATraditional.txt"); + + using FileStream fileStream = GetStreamWriter("ArabicLegacyEncodingData.Generated.cs"); + using StreamWriter writer = new(fileStream); + + writer.WriteLine("// Copyright (c) Six Labors."); + writer.WriteLine("// Licensed under the Six Labors Split License."); + writer.WriteLine(); + writer.WriteLine("// "); + writer.WriteLine("using System;"); + writer.WriteLine("using System.Buffers.Binary;"); + writer.WriteLine(); + writer.WriteLine("namespace SixLabors.Fonts.Unicode.Resources"); + writer.WriteLine("{"); + writer.WriteLine(" /// "); + writer.WriteLine(" /// Maps Unicode characters to the private-use codes of legacy Arabic fonts."); + writer.WriteLine(" /// "); + writer.WriteLine(" internal static class ArabicLegacyEncodingData"); + writer.WriteLine(" {"); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The OS/2 version 0 font-page marker for Simplified Arabic."); + writer.WriteLine(" /// "); + writer.WriteLine(" public const ushort SimplifiedArabicFontPage = 0xB200;"); + writer.WriteLine(); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The OS/2 version 0 font-page marker for Traditional Arabic."); + writer.WriteLine(" /// "); + writer.WriteLine(" public const ushort TraditionalArabicFontPage = 0xB300;"); + writer.WriteLine(); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The number of bytes in one source and mapped-code pair."); + writer.WriteLine(" /// "); + writer.WriteLine($" public const int MappingEntrySize = {ArabicLegacyMappingEntrySize};"); + writer.WriteLine(); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The byte offset of the mapped code within an entry."); + writer.WriteLine(" /// "); + writer.WriteLine(" public const int MappedCodePointOffset = sizeof(ushort);"); + writer.WriteLine(); + writer.WriteLine(" /// "); + writer.WriteLine(" /// Maps a Unicode character to the private-use code used by the selected legacy font page."); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The legacy font-page marker."); + writer.WriteLine(" /// The Unicode character to map."); + writer.WriteLine(" /// The private-use code, or zero when the character has no mapping."); + writer.WriteLine(" public static ushort GetMappedCodePoint(ushort fontPage, int codePoint)"); + writer.WriteLine(" {"); + writer.WriteLine(" ReadOnlySpan mappings = GetMappings(fontPage);"); + writer.WriteLine(" int low = 0;"); + writer.WriteLine(" int high = (mappings.Length / MappingEntrySize) - 1;"); + writer.WriteLine(" while (low <= high)"); + writer.WriteLine(" {"); + writer.WriteLine(" int middle = low + ((high - low) >> 1);"); + writer.WriteLine(" int offset = middle * MappingEntrySize;"); + writer.WriteLine(" ushort candidate = BinaryPrimitives.ReadUInt16LittleEndian(mappings.Slice(offset, sizeof(ushort)));"); + writer.WriteLine(" if (codePoint < candidate)"); + writer.WriteLine(" {"); + writer.WriteLine(" high = middle - 1;"); + writer.WriteLine(" }"); + writer.WriteLine(" else if (codePoint > candidate)"); + writer.WriteLine(" {"); + writer.WriteLine(" low = middle + 1;"); + writer.WriteLine(" }"); + writer.WriteLine(" else"); + writer.WriteLine(" {"); + writer.WriteLine(" return BinaryPrimitives.ReadUInt16LittleEndian(mappings.Slice(offset + MappedCodePointOffset, sizeof(ushort)));"); + writer.WriteLine(" }"); + writer.WriteLine(" }"); + writer.WriteLine(); + writer.WriteLine(" return 0;"); + writer.WriteLine(" }"); + writer.WriteLine(); + writer.WriteLine(" /// "); + writer.WriteLine(" /// Gets the packed mappings for the selected legacy font page."); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The legacy font-page marker."); + writer.WriteLine(" /// The packed mappings, or an empty span for another font page."); + writer.WriteLine(" public static ReadOnlySpan GetMappings(ushort fontPage)"); + writer.WriteLine(" => fontPage switch"); + writer.WriteLine(" {"); + writer.WriteLine(" SimplifiedArabicFontPage => SimplifiedMappings,"); + writer.WriteLine(" TraditionalArabicFontPage => TraditionalMappings,"); + writer.WriteLine(" _ => []"); + writer.WriteLine(" };"); + writer.WriteLine(); + + WriteArabicLegacyMappings(writer, "SimplifiedMappings", simplified); + writer.WriteLine(); + WriteArabicLegacyMappings(writer, "TraditionalMappings", traditional); + + writer.WriteLine(" }"); + writer.WriteLine("}"); + } + + /// + /// Reads one legacy mapping file, keeping the final entry for a repeated Unicode character. + /// + /// The source file name. + /// The mappings ordered by Unicode character. + private static List<(ushort CodePoint, ushort MappedCodePoint, string Name)> ReadArabicLegacyMappings(string fileName) + { + Dictionary mappings = []; + foreach (string line in File.ReadLines(GetReferenceSourcePath(fileName))) + { + if (line.Length == 0 || line[0] == '#') + { + continue; + } + + string[] fields = line.Split('\t'); + ushort mappedCodePoint = ushort.Parse(fields[0].AsSpan(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + ushort codePoint = ushort.Parse(fields[1].AsSpan(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + string name = fields[2].AsSpan(1).Trim().ToString(); + mappings[codePoint] = (mappedCodePoint, name); + } + + return mappings + .OrderBy(static pair => pair.Key) + .Select(static pair => (pair.Key, pair.Value.MappedCodePoint, pair.Value.Name)) + .ToList(); + } + + /// + /// Writes one mapping set as a packed little-endian byte span. + /// + /// The output writer. + /// The generated property name. + /// The mappings to write. + private static void WriteArabicLegacyMappings(StreamWriter writer, string propertyName, List<(ushort CodePoint, ushort MappedCodePoint, string Name)> mappings) + { + string label = propertyName[..(propertyName.Length - "Mappings".Length)].ToLowerInvariant(); + + writer.WriteLine(" /// "); + writer.WriteLine($" /// The {label} Arabic mappings."); + writer.WriteLine(" /// "); + writer.WriteLine($" private static ReadOnlySpan {propertyName} =>"); + writer.WriteLine(" ["); + + foreach ((ushort codePoint, ushort mappedCodePoint, string name) in mappings) + { + writer.WriteLine( + $" 0x{codePoint & LowByteMask:X2}, 0x{codePoint >> HighByteShift:X2}, 0x{mappedCodePoint & LowByteMask:X2}, 0x{mappedCodePoint >> HighByteShift:X2}, // U+{codePoint:X4} {name}"); + } + + writer.WriteLine(" ];"); + } +} diff --git a/src/UnicodeTrieGenerator/Generator.AsciiBidiTables.cs b/src/UnicodeTrieGenerator/Generator.AsciiBidiTables.cs new file mode 100644 index 000000000..7d101a1f1 --- /dev/null +++ b/src/UnicodeTrieGenerator/Generator.AsciiBidiTables.cs @@ -0,0 +1,172 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; +using SixLabors.Fonts.Tests.Unicode; +using SixLabors.Fonts.Unicode; + +namespace UnicodeTrieGenerator; + +/// +/// Contains the generator for the ASCII bidi classification tables. +/// +public static partial class Generator +{ + private const string UnicodeOutputRelativePath = @"src\SixLabors.Fonts\Unicode"; + + /// + /// The number of values the ASCII tables cover. + /// + private const int AsciiTableLength = 128; + + /// + /// Generates the compile-time constant ASCII bidi tables consumed by the + /// bidirectional analysis fast path. Every byte derives from the Unicode + /// source data: the character types from the UnicodeData bidi category field, + /// the bracket types and pairings from BidiBrackets, and the pairing keys from + /// the canonical mapping the same rows define. A unit test asserts the + /// generated data matches the general classification path at runtime. + /// + public static void GenerateAsciiBidiTables() + { + byte[] characterTypes = new byte[AsciiTableLength]; + byte[] pairedBracketTypes = new byte[AsciiTableLength]; + byte[] pairedBracketValues = new byte[AsciiTableLength]; + + using (StreamReader sr = GetStreamReader("UnicodeData.txt")) + { + string? line; + while ((line = sr.ReadLine()) != null) + { + string[] parts = line.Split(';'); + if (parts.Length > 4) + { + int codePoint = ParseHexInt(parts[0]); + if (codePoint < AsciiTableLength) + { + _ = UnicodeTypeMaps.BidiCharacterTypeMap.TryGetValue(parts[4], out BidiCharacterType cls); + characterTypes[codePoint] = (byte)cls; + } + } + } + } + + Regex regex = BidiBracketRowRegex(); + using (StreamReader sr = GetStreamReader("BidiBrackets.txt")) + { + string? line; + while ((line = sr.ReadLine()) != null) + { + Match match = regex.Match(line); + if (match.Success) + { + int point = ParseHexInt(match.Groups[1].Value); + int otherPoint = ParseHexInt(match.Groups[2].Value); + if (point < AsciiTableLength) + { + _ = BidiPairedBracketTypeMap.TryGetValue(match.Groups[3].Value, out BidiPairedBracketType kind); + pairedBracketTypes[point] = (byte)kind; + + // The pairing key is the canonical form of the closing + // bracket: the paired point for an opening bracket and the + // point itself for a closing one. No ASCII value carries a + // canonical decomposition in UnicodeData, so the canonical + // form is the value itself. + int pairingKey = kind == BidiPairedBracketType.Open ? otherPoint : point; + pairedBracketValues[point] = checked((byte)pairingKey); + } + } + } + } + + StringBuilder sb = new(); + sb.AppendLine("// Copyright (c) Six Labors."); + sb.AppendLine("// Licensed under the Six Labors Split License."); + sb.AppendLine(); + sb.AppendLine("// "); + sb.AppendLine("namespace SixLabors.Fonts.Unicode;"); + sb.AppendLine(); + sb.AppendLine("/// "); + sb.AppendLine("/// Contains the generated ASCII bidi classification tables."); + sb.AppendLine("/// "); + sb.AppendLine("internal partial class BidiData"); + sb.AppendLine("{"); + + const string characterTypesDoc = """ + /// + /// Gets the bidi character type for every ASCII value as compile-time constant + /// data the runtime reads directly from metadata. ASCII contains no embedding, + /// override, or isolate initiators, so the fast path never needs the flag + /// bookkeeping the general path performs. Generated from the UnicodeData bidi + /// category field; a test asserts the general classification path agrees. + /// + """; + + const string pairedBracketTypesDoc = """ + /// + /// Gets the paired bracket type for every ASCII value as compile-time constant + /// data; the ASCII brackets are the parentheses, square brackets, and curly + /// braces. Generated from BidiBrackets; a test asserts the general + /// classification path agrees. + /// + """; + + const string pairedBracketValuesDoc = """ + /// + /// Gets the paired bracket value for every ASCII value as compile-time constant + /// data: the canonical pairing key the bracket-pair stage matches opening and + /// closing brackets by, zero for non-brackets. Generated from BidiBrackets; a + /// test asserts the general classification path agrees. + /// + """; + + AppendTable(sb, "AsciiCharacterTypes", characterTypes, characterTypesDoc); + sb.AppendLine(); + AppendTable(sb, "AsciiPairedBracketTypes", pairedBracketTypes, pairedBracketTypesDoc); + sb.AppendLine(); + AppendTable(sb, "AsciiPairedBracketValues", pairedBracketValues, pairedBracketValuesDoc); + + sb.AppendLine("}"); + + string path = GetFullPath(Path.Combine(UnicodeOutputRelativePath, "BidiData.AsciiTables.Generated.cs")); + File.WriteAllText(path, sb.ToString()); + } + + /// + /// Appends one constant-data span property: the supplied documentation block + /// followed by the byte values in rows of sixteen. + /// + /// The builder receiving the generated source. + /// The property name. + /// The table values. + /// The XML documentation block for the property. + private static void AppendTable(StringBuilder sb, string name, byte[] values, string documentation) + { + foreach (string docLine in documentation.Split('\n')) + { + sb.Append(" ").AppendLine(docLine.TrimEnd('\r')); + } + + sb.AppendLine(CultureInfo.InvariantCulture, $" public static ReadOnlySpan {name} =>"); + sb.AppendLine(" ["); + for (int row = 0; row < values.Length; row += 16) + { + sb.Append(" "); + for (int col = 0; col < 16; col++) + { + int index = row + col; + sb.Append(CultureInfo.InvariantCulture, $"0x{values[index]:X2}"); + if (index != values.Length - 1) + { + sb.Append(col == 15 ? "," : ", "); + } + } + + sb.AppendLine(); + } + + sb.AppendLine(" ];"); + } +} diff --git a/src/UnicodeTrieGenerator/Generator.IndicShapingEngine.cs b/src/UnicodeTrieGenerator/Generator.IndicShapingEngine.cs index f2e936fe9..4bb61c01e 100644 --- a/src/UnicodeTrieGenerator/Generator.IndicShapingEngine.cs +++ b/src/UnicodeTrieGenerator/Generator.IndicShapingEngine.cs @@ -37,7 +37,7 @@ public static partial class Generator { ISC.ConsonantSucceedingRepha, Categories.CM }, { ISC.ConsonantWithStacker, Categories.CS }, { ISC.GeminationMark, Categories.SM }, // https://github.com/harfbuzz/harfbuzz/issues/552 - { ISC.InvisibleStacker, Categories.Coeng }, // TODO: Use H once we add explicit Khmer shaper + { ISC.InvisibleStacker, Categories.H }, { ISC.Joiner, Categories.ZWJ }, { ISC.ModifyingLetter, Categories.X }, { ISC.NonJoiner, Categories.ZWNJ }, @@ -56,9 +56,12 @@ public static partial class Generator { ISC.VowelIndependent, Categories.V } }; - // Per-codepoint category overrides for Indic-style shaping. - // These values augment the base Unicode/Indic tables to match - // HarfBuzz behavior for Indic, Khmer, and Myanmar scripts. + /// + /// Gets the per-character category overrides applied to the shared shaping trie. + /// + /// + /// These values are transcribed from HarfBuzz 14.2.1, src/gen-indic-table.py, symbol category_overrides. They are not derivable from the Unicode Character Database. + /// private static readonly Dictionary IndicShapingOverrides = new() { // -------------------------------------------------------------------- @@ -123,8 +126,6 @@ public static partial class Generator // Characters that act like consonants. { 0x0A72, Categories.C }, { 0x0A73, Categories.C }, - { 0x1CF5, Categories.C }, - { 0x1CF6, Categories.C }, // TODO: These should only be allowed after a visarga. // For now, treat them like regular tone marks (A). @@ -190,23 +191,23 @@ public static partial class Generator // -------------------------------------------------------------------- // Khmer overrides // -------------------------------------------------------------------- - { 0x179A, Categories.Ra }, // Khmer Ra - { 0x17C6, Categories.N }, // TODO: Replace with Xgroup as per below once we support it. - - // { 0x17CC, Categories.Robatic }, - // { 0x17C9, Categories.Robatic }, - // { 0x17CA, Categories.Robatic }, - // { 0x17C6, Categories.Xgroup }, - // { 0x17CB, Categories.Xgroup }, - // { 0x17CD, Categories.Xgroup }, - // { 0x17CE, Categories.Xgroup }, - // { 0x17CF, Categories.Xgroup }, - // { 0x17D0, Categories.Xgroup }, - // { 0x17D1, Categories.Xgroup }, - // { 0x17C7, Categories.Ygroup }, - // { 0x17C8, Categories.Ygroup }, - // { 0x17DD, Categories.Ygroup }, - // { 0x17D3, Categories.Ygroup }, // Just guessing. Uniscribe does not categorize it. + { 0x179A, Categories.Ra }, + { 0x17CC, Categories.Robatic }, + { 0x17C9, Categories.Robatic }, + { 0x17CA, Categories.Robatic }, + { 0x17C6, Categories.Xgroup }, + { 0x17CB, Categories.Xgroup }, + { 0x17CD, Categories.Xgroup }, + { 0x17CE, Categories.Xgroup }, + { 0x17CF, Categories.Xgroup }, + { 0x17D0, Categories.Xgroup }, + { 0x17D1, Categories.Xgroup }, + { 0x17C7, Categories.Ygroup }, + { 0x17C8, Categories.Ygroup }, + { 0x17DD, Categories.Ygroup }, + + // The reference source labels this compatibility category as a guess based on Uniscribe behavior. + { 0x17D3, Categories.Ygroup }, // https://github.com/harfbuzz/harfbuzz/issues/2384 { 0x17D9, Categories.Placeholder }, @@ -353,7 +354,10 @@ private static int GetPosition(Codepoint codepoint, Categories category) position = Positions.Before_Sub; } - return (int)Math.Log((int)position, 2); + // The trie stores the position zero-based. Zero is the unassigned + // sentinel of the enum, so every named position sits one ahead of the + // value the shaper reads back out. + return (int)position - 1; } private static void GenerateIndicShapingDataTrie(Codepoint[] codePoints) @@ -420,6 +424,15 @@ private static void GenerateIndicShapingDataTrie(Codepoint[] codePoints) GenerateDataClass("IndicShaping", null, null, machine, true); } + /// + /// Converts dependent vowels in the Khmer and Myanmar blocks to the positional category their syllable machines consume. + /// + /// + /// This conversion is transcribed from HarfBuzz 14.2.1, src/gen-indic-table.py, symbols matra_categories and position_to_category. The conversion is not derivable from the Unicode Character Database. + /// + /// The character whose block and position determine the category. + /// The category mapped from its syllabic property. + /// The category stored in the shaping trie. private static Categories NormalizeCategoryForBlock(Codepoint codepoint, Categories category) { // HarfBuzz: matra_categories = ('M', 'MPst') @@ -430,10 +443,7 @@ private static Categories NormalizeCategoryForBlock(Codepoint codepoint, Categor { string block = codepoint.Block; - // TODO: Once we implement the Khmer shaper, enable Khmer here too. - // if (block.StartsWith("Khmer", StringComparison.Ordinal) || - // block.StartsWith("Myanmar", StringComparison.Ordinal)) - if (block.StartsWith("Myanmar", StringComparison.Ordinal)) + if (block.StartsWith("Khmer", StringComparison.Ordinal) || block.StartsWith("Myanmar", StringComparison.Ordinal)) { // Base positional category from IndicPositionalCategory.txt Positions basePos = PositionMap.GetValueOrDefault( @@ -456,6 +466,15 @@ private static Categories NormalizeCategoryForBlock(Codepoint codepoint, Categor private static void SetBlocks(Codepoint[] codePoints) { + // Blocks span the whole of Unicode, so every character of every range is + // looked up. Index the characters by code once and the whole file costs + // one lookup per character rather than a scan. + Dictionary byCode = new(codePoints.Length); + foreach (Codepoint codePoint in codePoints) + { + _ = byCode.TryAdd(codePoint.Code, codePoint); + } + Regex regex = IndicPropertyRowRegex(); using StreamReader sr = GetStreamReader("Blocks.txt"); @@ -464,29 +483,27 @@ private static void SetBlocks(Codepoint[] codePoints) { Match match = regex.Match(line); - if (match.Success) + if (!match.Success) { - string start = match.Groups[1].Value; - string end = match.Groups[2].Value; - string block = match.Groups[3].Value; + continue; + } - if (string.IsNullOrEmpty(end)) - { - end = start; - } + string start = match.Groups[1].Value; + string end = match.Groups[2].Value; + string block = match.Groups[3].Value; - int min = ParseHexInt(start); - int max = ParseHexInt(end); + if (string.IsNullOrEmpty(end)) + { + end = start; + } - for (int i = min; i <= max; i++) - { - // TODO: Make an enum of block values and create a trie. This is painfully slow. - Codepoint? codePoint = Array.Find(codePoints, x => x.Code == i); - if (codePoint is null) - { - continue; - } + int min = ParseHexInt(start); + int max = ParseHexInt(end); + for (int i = min; i <= max; i++) + { + if (byCode.TryGetValue(i, out Codepoint? codePoint)) + { codePoint.Block = block; } } diff --git a/src/UnicodeTrieGenerator/Generator.KhmerShapingEngine.cs b/src/UnicodeTrieGenerator/Generator.KhmerShapingEngine.cs new file mode 100644 index 000000000..0a4544f2c --- /dev/null +++ b/src/UnicodeTrieGenerator/Generator.KhmerShapingEngine.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using UnicodeTrieGenerator.StateAutomation; +using Categories = SixLabors.Fonts.Unicode.Resources.IndicShapingData.Categories; +using KhmerCategories = SixLabors.Fonts.Unicode.Resources.IndicShapingData.KhmerCategories; + +namespace UnicodeTrieGenerator; + +/// +/// Contains code to generate the Khmer syllable state machine. +/// +public static partial class Generator +{ + /// + /// Generates the Khmer syllable state machine from its grammar. + /// + /// + /// The grammar and category alphabet follow tests/harfbuzz/src/hb-ot-shaper-khmer-machine.rl, + /// symbol khmer_syllable_machine. They are not derivable from the Unicode Character Database. + /// + private static void GenerateKhmerShapingData() + { + KhmerCategories[] categories = Enum.GetValues(); + Dictionary symbols = new(categories.Length); + int id = 0; + + foreach (KhmerCategories category in categories) + { + symbols[category.ToString()] = id++; + } + + StateMachine machine = GetStateMachine("khmer", symbols); + + GenerateDataClass("KhmerShaping", null, null, machine, true); + GenerateKhmerCategoryMap(categories); + } + + /// + /// Generates the sparse Khmer category to state-machine symbol mapping. + /// + /// + /// The symbol order follows tests/harfbuzz/src/hb-ot-shaper-khmer-machine.rl, + /// symbol khmer_syllable_machine. It is not derivable from the Unicode Character Database. + /// + /// The state-machine categories in symbol order. + private static void GenerateKhmerCategoryMap(KhmerCategories[] categories) + { + Categories[] allCategories = Enum.GetValues(); + int maxCategory = 0; + foreach (Categories category in allCategories) + { + maxCategory = Math.Max(maxCategory, (int)category); + } + + byte[] categoryToSymbolIds = new byte[maxCategory + 1]; + for (int symbolId = 0; symbolId < categories.Length; symbolId++) + { + categoryToSymbolIds[(int)categories[symbolId]] = checked((byte)symbolId); + } + + using FileStream fileStream = GetStreamWriter("KhmerShapingCategoryMap.Generated.cs"); + using StreamWriter writer = new(fileStream); + + writer.WriteLine("// Copyright (c) Six Labors."); + writer.WriteLine("// Licensed under the Six Labors Split License."); + writer.WriteLine(); + writer.WriteLine("// "); + writer.WriteLine("using System;"); + writer.WriteLine(); + writer.WriteLine("namespace SixLabors.Fonts.Unicode.Resources"); + writer.WriteLine("{"); + writer.WriteLine(" internal static partial class KhmerShapingData"); + writer.WriteLine(" {"); + writer.WriteLine(" /// "); + writer.WriteLine(" /// Maps sparse Khmer shaping categories to the state machine alphabet."); + writer.WriteLine(" /// "); + writer.WriteLine(" /// "); + writer.WriteLine(" /// The symbol order follows tests/harfbuzz/src/hb-ot-shaper-khmer-machine.rl,"); + writer.WriteLine(" /// symbol khmer_syllable_machine. It is not derivable from the Unicode Character Database."); + writer.WriteLine(" /// "); + writer.WriteLine(" public static ReadOnlySpan CategoryToSymbolIds =>"); + writer.WriteLine(" ["); + + for (int category = 0; category < categoryToSymbolIds.Length; category++) + { + string label = Enum.GetName((Categories)category) ?? "Unassigned"; + writer.WriteLine($" {categoryToSymbolIds[category]}, // {label}"); + } + + writer.WriteLine(" ];"); + writer.WriteLine(" }"); + writer.WriteLine("}"); + } +} diff --git a/src/UnicodeTrieGenerator/Generator.MarkOrdering.cs b/src/UnicodeTrieGenerator/Generator.MarkOrdering.cs new file mode 100644 index 000000000..906cee911 --- /dev/null +++ b/src/UnicodeTrieGenerator/Generator.MarkOrdering.cs @@ -0,0 +1,495 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace UnicodeTrieGenerator; + +/// +/// Generates the classes that order a mark against the marks around it. +/// +/// +/// +/// These are the canonical combining classes with the classes of several scripts +/// renumbered, so that sorting by them leaves the marks of those scripts in the +/// order they are drawn. No Unicode data file derives the renumbering, so it is +/// read out of the reference implementation instead: the table +/// _hb_modified_combining_class in hb-unicode.cc, whose entries are +/// either plain numbers or the HB_MODIFIED_COMBINING_CLASS_CCC* macros +/// defined in hb-unicode.hh, together with the handful of characters that +/// modified_combining_class resolves ahead of the table. +/// +/// +/// Reading it rather than transcribing it means the pin is the single statement of +/// which reference version this library matches, and moving the pin regenerates the +/// table. +/// +/// +public static partial class Generator +{ + /// + /// The path of the pinned reference implementation, relative to the solution. + /// + private const string ReferenceSubmoduleRelativePath = @"tests\harfbuzz"; + + /// + /// The number of canonical combining classes, which the standard bounds to one + /// byte. + /// + private const int CombiningClassCount = 256; + + /// + /// Matches one HB_MODIFIED_COMBINING_CLASS_CCC* definition, including the + /// label the reference attaches to it. + /// + [GeneratedRegex(@"#define\s+HB_MODIFIED_COMBINING_CLASS_CCC(?\d+)\s+(?\d+)(?:[ \t]*/\*\s*(?