using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using AOT; using Unity.UIWidgets.foundation; using Unity.UIWidgets.ui; using UnityEngine; using Unity.UIWidgets.async2; using Unity.UIWidgets.services; using Rect = Unity.UIWidgets.ui.Rect; namespace Unity.UIWidgets.ui { public enum FontStyle { normal, italic, } public class FontWeight { public FontWeight(int index) { this.index = index; } public readonly int index; public static readonly FontWeight w100 = new FontWeight(0); public static readonly FontWeight w200 = new FontWeight(1); public static readonly FontWeight w300 = new FontWeight(2); public static readonly FontWeight w400 = new FontWeight(3); public static readonly FontWeight w500 = new FontWeight(4); public static readonly FontWeight w600 = new FontWeight(5); public static readonly FontWeight w700 = new FontWeight(6); public static readonly FontWeight w800 = new FontWeight(7); public static readonly FontWeight w900 = new FontWeight(8); public static readonly FontWeight normal = w400; public static readonly FontWeight bold = w700; public static readonly List values = new List { w100, w200, w300, w400, w500, w600, w700, w800, w900 }; public static FontWeight lerp(FontWeight a, FontWeight b, float t) { if (a == null && b == null) return null; return values[MathUtils.lerpNullableFloat(a?.index ?? normal.index, b?.index ?? normal.index, t).round().clamp(0, 8)]; } public static readonly Dictionary map = new Dictionary { {0, "FontWeight.w100"}, {1, "FontWeight.w200"}, {2, "FontWeight.w300"}, {3, "FontWeight.w400"}, {4, "FontWeight.w500"}, {5, "FontWeight.w600"}, {6, "FontWeight.w700"}, {7, "FontWeight.w800"}, {8, "FontWeight.w900"}, }; public override string ToString() { return map[index]; } } public class FontFeature : IEquatable { public FontFeature(string feature, int value = 1) { D.assert(feature != null); D.assert(feature.Length == 4); D.assert(value >= 0); this.feature = feature; this.value = value; } public FontFeature enable(string feature) { return new FontFeature(feature, 1); } public FontFeature disable(string feature) { return new FontFeature(feature, 0); } public FontFeature randomize() { return new FontFeature("rand", 1); } public static FontFeature stylisticSet(int value) { D.assert(value >= 1); D.assert(value <= 20); return new FontFeature($"ss{value.ToString().PadLeft(2, '0')}"); } public FontFeature slashedZero() { return new FontFeature("zero", 1); } public FontFeature oldstyleFigures() { return new FontFeature("onum", 1); } public FontFeature proportionalFigures() { return new FontFeature("pnum", 1); } public FontFeature tabularFigures() { return new FontFeature("tnum", 1); } public readonly string feature; public readonly int value; internal static readonly int _kEncodedSize = 8; internal void _encode(byte[] byteData) { D.assert(() => { foreach (var charUnit in feature.ToCharArray()) { if (charUnit <= 0x20 && charUnit >= 0x7F) { return false; } } return true; }); for (int i = 0; i < 4; i++) { byteData[i] = (byte) feature[i]; } byteData[4] = (byte) (value >> 0); byteData[5] = (byte) (value >> 8); byteData[6] = (byte) (value >> 16); byteData[7] = (byte) (value >> 24); } public override string ToString() { return $"FontFeature({feature}, {value})"; } public bool Equals(FontFeature other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return feature == other.feature && value == other.value; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((FontFeature) obj); } public override int GetHashCode() { unchecked { return ((feature != null ? feature.GetHashCode() : 0) * 397) ^ value; } } } public enum TextAlign { left, right, center, justify, start, end, } public enum TextBaseline { alphabetic, ideographic, } public class TextDecoration : IEquatable { public TextDecoration(int _mask) { this._mask = _mask; } public TextDecoration combine(List decorations) { int mask = 0; foreach (TextDecoration decoration in decorations) { mask |= decoration._mask; } return new TextDecoration(mask); } internal readonly int _mask; public bool contains(TextDecoration other) { return (_mask | other._mask) == _mask; } public static readonly TextDecoration none = new TextDecoration(0x0); public static readonly TextDecoration underline = new TextDecoration(0x1); public static readonly TextDecoration overline = new TextDecoration(0x2); public static readonly TextDecoration lineThrough = new TextDecoration(0x4); public override string ToString() { if (_mask == 0) { return "TextDecoration.none"; } List values = new List(); if ((_mask & underline._mask) != 0) values.Add("underline"); if ((_mask & overline._mask) != 0) values.Add("overline"); if ((_mask & lineThrough._mask) != 0) values.Add("lineThrough"); if (values.Count == 1) return $"TextDecoration.{values[0]}"; return $"TextDecoration.combine([{String.Join(", ", values)}])"; } public bool Equals(TextDecoration other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return _mask == other._mask; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((TextDecoration) obj); } public override int GetHashCode() { return _mask; } } public enum TextDecorationStyle { solid, doubleLine, dotted, dashed, wavy } public class TextHeightBehavior : IEquatable { public TextHeightBehavior( bool applyHeightToFirstAscent = true, bool applyHeightToLastDescent = true ) { this.applyHeightToFirstAscent = applyHeightToFirstAscent; this.applyHeightToLastDescent = applyHeightToLastDescent; } public static TextHeightBehavior fromEncoded(int encoded) { return new TextHeightBehavior((encoded & 0x1) == 0, (encoded & 0x2) == 0); } public readonly bool applyHeightToFirstAscent; public readonly bool applyHeightToLastDescent; public int encode() { return (applyHeightToFirstAscent ? 0 : 1 << 0) | (applyHeightToLastDescent ? 0 : 1 << 1); } public override string ToString() { return "TextHeightBehavior(\n" + $" applyHeightToFirstAscent: {applyHeightToFirstAscent}, \n" + $" applyHeightToLastDescent: {applyHeightToLastDescent} \n" + ")"; } public bool Equals(TextHeightBehavior other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return applyHeightToFirstAscent == other.applyHeightToFirstAscent && applyHeightToLastDescent == other.applyHeightToLastDescent; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((TextHeightBehavior) obj); } public override int GetHashCode() { unchecked { return (applyHeightToFirstAscent.GetHashCode() * 397) ^ applyHeightToLastDescent.GetHashCode(); } } } public struct Float32List { public IntPtr data; public int length; } public static partial class ui_ { static readonly object VoidObject = new object(); internal static int[] _encodeTextStyle( Color color, TextDecoration decoration, Color decorationColor, TextDecorationStyle? decorationStyle, float? decorationThickness, FontWeight fontWeight, FontStyle? fontStyle, TextBaseline? textBaseline, string fontFamily, List fontFamilyFallback, float? fontSize, float? letterSpacing, float? wordSpacing, float? height, Locale locale, Paint background, Paint foreground, List shadows, List fontFeatures ) { int[] result = new int[8]; if (color != null) { result[0] |= 1 << 1; result[1] = (int) color.value; } if (decoration != null) { result[0] |= 1 << 2; result[2] = decoration._mask; } if (decorationColor != null) { result[0] |= 1 << 3; result[3] = (int) decorationColor.value; } if (decorationStyle != null) { result[0] |= 1 << 4; result[4] = (int) decorationStyle; } if (fontWeight != null) { result[0] |= 1 << 5; result[5] = fontWeight.index; } if (fontStyle != null) { result[0] |= 1 << 6; result[6] = (int) fontStyle; } if (textBaseline != null) { result[0] |= 1 << 7; result[7] = (int) textBaseline; } if (decorationThickness != null) { result[0] |= 1 << 8; } if (fontFamily != null || (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty())) { result[0] |= 1 << 9; // Passed separately to native. } if (fontSize != null) { result[0] |= 1 << 10; // Passed separately to native. } if (letterSpacing != null) { result[0] |= 1 << 11; // Passed separately to native. } if (wordSpacing != null) { result[0] |= 1 << 12; // Passed separately to native. } if (height != null) { result[0] |= 1 << 13; // Passed separately to native. } if (locale != null) { result[0] |= 1 << 14; // Passed separately to native. } if (background != null) { result[0] |= 1 << 15; // Passed separately to native. } if (foreground != null) { result[0] |= 1 << 16; // Passed separately to native. } if (shadows != null) { result[0] |= 1 << 17; // Passed separately to native. } if (fontFeatures != null) { result[0] |= 1 << 18; // Passed separately to native. } return result; } internal static List _encodeParagraphStyle( TextAlign? textAlign, TextDirection? textDirection, int? maxLines, string fontFamily, float? fontSize, float? height, TextHeightBehavior textHeightBehavior, FontWeight fontWeight, FontStyle? fontStyle, StrutStyle strutStyle, string ellipsis, Locale locale ) { List result = new List(new int[7]); if (textAlign != null) { result[0] |= 1 << 1; result[1] = (int) textAlign; } if (textDirection != null) { result[0] |= 1 << 2; result[2] = (int) textDirection; } if (fontWeight != null) { result[0] |= 1 << 3; result[3] = fontWeight.index; } if (fontStyle != null) { result[0] |= 1 << 4; result[4] = (int) fontStyle; } if (maxLines != null) { result[0] |= 1 << 5; result[5] = maxLines.Value; } if (textHeightBehavior != null) { result[0] |= 1 << 6; result[6] = textHeightBehavior.encode(); } if (fontFamily != null) { result[0] |= 1 << 7; // Passed separately to native. } if (fontSize != null) { result[0] |= 1 << 8; // Passed separately to native. } if (height != null) { result[0] |= 1 << 9; // Passed separately to native. } if (strutStyle != null) { result[0] |= 1 << 10; // Passed separately to native. } if (ellipsis != null) { result[0] |= 1 << 11; // Passed separately to native. } if (locale != null) { result[0] |= 1 << 12; // Passed separately to native. } return result; } internal static unsafe void setFloat(this byte[] bytes, int byteOffset, float value) { D.assert(byteOffset >= 0 && byteOffset + 4 < bytes.Length); fixed (byte* b = &bytes[byteOffset]) { *(float*) b = value; } } internal static byte[] _encodeStrut( string fontFamily, List fontFamilyFallback, float? fontSize, float? height, float? leading, FontWeight fontWeight, FontStyle? fontStyle, bool? forceStrutHeight) { if (fontFamily == null && fontSize == null && height == null && leading == null && fontWeight == null && fontStyle == null && forceStrutHeight == null) { return new byte[0]; } byte[] data = new byte[15]; // Max size is 15 bytes int bitmask = 0; // 8 bit mask int byteCount = 1; if (fontWeight != null) { bitmask |= 1 << 0; data[byteCount] = (byte) fontWeight.index; byteCount += 1; } if (fontStyle != null) { bitmask |= 1 << 1; data[byteCount] = (byte) fontStyle; byteCount += 1; } if (fontFamily != null || (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty())) { bitmask |= 1 << 2; // passed separately to native } if (fontSize != null) { bitmask |= 1 << 3; data.setFloat(byteCount, (float) fontSize); byteCount += 4; } if (height != null) { bitmask |= 1 << 4; data.setFloat(byteCount, (float) height); byteCount += 4; } if (leading != null) { bitmask |= 1 << 5; data.setFloat(byteCount, (float) leading); byteCount += 4; } if (forceStrutHeight != null) { bitmask |= 1 << 6; // We store this boolean directly in the bitmask since there is // extra space in the 16 bit int. bitmask |= (forceStrutHeight.Value ? 1 : 0) << 7; } data[0] = (byte) bitmask; return data.range(0, byteCount); } public static unsafe Future loadFontFromList(byte[] list, string fontFamily = null) { return _futurize((_Callback callback) => { // var completer = new Promise(true); GCHandle completerHandle = GCHandle.Alloc(callback); fixed (byte* listPtr = list) { Font_LoadFontFromList(listPtr, list.Length, _loadFontCallback, (IntPtr) completerHandle, fontFamily); } return null; }); } [MonoPInvokeCallback(typeof(_loadFontFromListCallback))] static void _loadFontCallback(IntPtr callbackHandle) { GCHandle completerHandle = (GCHandle) callbackHandle; var callback = (_Callback) completerHandle.Target; completerHandle.Free(); _sendFontChangeMessage(); try { callback(VoidObject); } catch (Exception ex) { Debug.LogException(ex); } } static byte[] _fontChangeMessage = JSONMessageCodec.instance.encodeMessage( new Dictionary() { {"type", "fontsChange"} }); static void _sendFontChangeMessage() { Window.instance.onPlatformMessage?.Invoke("uiwidgets/system", _fontChangeMessage, (_) => { }); } delegate void _loadFontFromListCallback(IntPtr callbackHandle); [DllImport(NativeBindings.dllName)] static extern unsafe void Font_LoadFontFromList(byte* list, int size, _loadFontFromListCallback callback, IntPtr callbackHandle, string fontFamily); public static float[] toFloatArrayAndFree(this Float32List data) { float[] result = new float[data.length]; Marshal.Copy(data.data, result, 0, data.length); Lists_Free(data.data); return result; } [DllImport(NativeBindings.dllName)] public static extern void Lists_Free(IntPtr data); } public class TextStyle : IEquatable { public TextStyle( Color color = null, TextDecoration decoration = null, Color decorationColor = null, TextDecorationStyle? decorationStyle = null, float? decorationThickness = null, FontWeight fontWeight = null, FontStyle? fontStyle = null, TextBaseline? textBaseline = null, string fontFamily = null, List fontFamilyFallback = null, float? fontSize = null, float? letterSpacing = null, float? wordSpacing = null, float? height = null, Locale locale = null, Paint background = null, Paint foreground = null, List shadows = null, List fontFeatures = null ) { D.assert(color == null || foreground == null, () => "Cannot provide both a color and a foreground\n" + "The color argument is just a shorthand for \"foreground: Paint()..color = color\"." ); _encoded = ui_._encodeTextStyle( color, decoration, decorationColor, decorationStyle, decorationThickness, fontWeight, fontStyle, textBaseline, fontFamily, fontFamilyFallback, fontSize, letterSpacing, wordSpacing, height, locale, background, foreground, shadows, fontFeatures ); _fontFamily = fontFamily ?? ""; _fontFamilyFallback = fontFamilyFallback; _fontSize = fontSize; _letterSpacing = letterSpacing; _wordSpacing = wordSpacing; _height = height; _decorationThickness = decorationThickness; _locale = locale; _background = background; _foreground = foreground; _shadows = shadows; _fontFeatures = fontFeatures; } internal readonly int[] _encoded; internal readonly string _fontFamily; internal readonly List _fontFamilyFallback; internal readonly float? _fontSize; internal readonly float? _letterSpacing; internal readonly float? _wordSpacing; internal readonly float? _height; internal readonly float? _decorationThickness; internal readonly Locale _locale; internal readonly Paint _background; internal readonly Paint _foreground; internal readonly List _shadows; internal readonly List _fontFeatures; public override string ToString() { string color = (_encoded[0] & 0x00002) == 0x00002 ? (new Color((uint) _encoded[1])).ToString() : "unspecified"; string decoration = (_encoded[0] & 0x00004) == 0x00004 ? (new TextDecoration(_encoded[2])).ToString() : "unspecified"; string decorationColor = (_encoded[0] & 0x00008) == 0x00008 ? (new Color((uint) _encoded[3])).ToString() : "unspecified"; string decorationStyle = (_encoded[0] & 0x00010) == 0x00010 ? ((TextDecorationStyle) _encoded[4]).ToString() : "unspecified"; // The decorationThickness is not in encoded order in order to keep it near the other decoration properties. string decorationThickness = (_encoded[0] & 0x00100) == 0x00100 ? _decorationThickness.ToString() : "unspecified"; string fontWeight = (_encoded[0] & 0x00020) == 0x00020 ? FontWeight.values[_encoded[5]].ToString() : "unspecified"; string fontStyle = (_encoded[0] & 0x00040) == 0x00040 ? ((FontStyle) _encoded[6]).ToString() : "unspecified"; string textBaseline = (_encoded[0] & 0x00080) == 0x00080 ? ((TextBaseline) _encoded[7]).ToString() : "unspecified"; string fontFamily = (_encoded[0] & 0x00200) == 0x00200 && _fontFamily != null ? _fontFamily : "unspecified"; string fontFamilyFallback = (_encoded[0] & 0x00200) == 0x00200 && _fontFamilyFallback != null && _fontFamilyFallback.isNotEmpty() ? _fontFamilyFallback.ToString() : "unspecified"; string fontSize = (_encoded[0] & 0x00400) == 0x00400 ? _fontSize.ToString() : "unspecified"; string letterSpacing = (_encoded[0] & 0x00800) == 0x00800 ? "_letterSpacing}x" : "unspecified"; string wordSpacing = (_encoded[0] & 0x01000) == 0x01000 ? "_wordSpacing}x" : "unspecified"; string height = (_encoded[0] & 0x02000) == 0x02000 ? "_height}x" : "unspecified"; string locale = (_encoded[0] & 0x04000) == 0x04000 ? _locale.ToString() : "unspecified"; string background = (_encoded[0] & 0x08000) == 0x08000 ? _locale.ToString() : "unspecified"; string foreground = (_encoded[0] & 0x10000) == 0x10000 ? _locale.ToString() : "unspecified"; string shadows = (_encoded[0] & 0x20000) == 0x20000 ? _locale.ToString() : "unspecified"; string fontFeatures = (_encoded[0] & 0x40000) == 0x40000 ? _fontFeatures.ToString() : "unspecified"; return "TextStyle(\n" + $"color: {color}\n" + $"decoration: {decoration}\n" + $"decorationColor: {decorationColor}\n" + $"decorationStyle: {decorationStyle}\n" + // The decorationThickness is not in encoded order in order to keep it near the other decoration properties. $"decorationThickness: {decorationThickness}\n" + $"fontWeight: {fontWeight}\n" + $"fontStyle: {fontStyle}\n" + $"textBaseline: {textBaseline}\n" + $"fontFamily: {fontFamily}\n" + $"fontFamilyFallback: {fontFamilyFallback}\n" + $"fontSize: {fontSize}\n" + $"letterSpacing: {letterSpacing}\n" + $"wordSpacing: {wordSpacing}\n" + $"height: {height}\n" + $"locale: {locale}\n" + $"background: {background}\n" + $"foreground: {foreground}\n" + $"shadows: {shadows}\n" + $"fontFeatures: {fontFeatures}\n" + ")"; } public bool Equals(TextStyle other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return _encoded.equalsList(other._encoded) && Equals(_fontFamily, other._fontFamily) && _fontFamilyFallback.equalsList(other._fontFamilyFallback) && Nullable.Equals(_fontSize, other._fontSize) && Nullable.Equals(_letterSpacing, other._letterSpacing) && Nullable.Equals(_wordSpacing, other._wordSpacing) && Nullable.Equals(_height, other._height) && Nullable.Equals(_decorationThickness, other._decorationThickness) && Equals(_locale, other._locale) && Equals(_background, other._background) && Equals(_foreground, other._foreground) && _shadows.equalsList(other._shadows) && _fontFeatures.equalsList(other._fontFeatures); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((TextStyle) obj); } public override int GetHashCode() { unchecked { var hashCode = (_encoded != null ? _encoded.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_fontFamily != null ? _fontFamily.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_fontFamilyFallback != null ? _fontFamilyFallback.GetHashCode() : 0); hashCode = (hashCode * 397) ^ _fontSize.GetHashCode(); hashCode = (hashCode * 397) ^ _letterSpacing.GetHashCode(); hashCode = (hashCode * 397) ^ _wordSpacing.GetHashCode(); hashCode = (hashCode * 397) ^ _height.GetHashCode(); hashCode = (hashCode * 397) ^ _decorationThickness.GetHashCode(); hashCode = (hashCode * 397) ^ (_locale != null ? _locale.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_background != null ? _background.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_foreground != null ? _foreground.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_shadows != null ? _shadows.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_fontFeatures != null ? _fontFeatures.GetHashCode() : 0); return hashCode; } } } public class ParagraphStyle : IEquatable { public ParagraphStyle( TextAlign? textAlign = null, TextDirection? textDirection = null, int? maxLines = null, string fontFamily = null, float? fontSize = null, float? height = null, TextHeightBehavior textHeightBehavior = null, FontWeight fontWeight = null, FontStyle? fontStyle = null, StrutStyle strutStyle = null, string ellipsis = null, Locale locale = null ) { _encoded = ui_._encodeParagraphStyle( textAlign, textDirection, maxLines, fontFamily, fontSize, height, textHeightBehavior, fontWeight, fontStyle, strutStyle, ellipsis, locale ); _fontFamily = fontFamily; _fontSize = fontSize; _height = height; _strutStyle = strutStyle; _ellipsis = ellipsis; _locale = locale; } internal readonly List _encoded; internal readonly string _fontFamily; internal readonly float? _fontSize; internal readonly float? _height; internal readonly StrutStyle _strutStyle; internal readonly string _ellipsis; internal readonly Locale _locale; public override string ToString() { string textAlign = (_encoded[0] & 0x002) == 0x002 ? ((TextAlign) _encoded[1]).ToString() : "unspecified"; string textDirection = (_encoded[0] & 0x004) == 0x004 ? ((TextDirection) _encoded[2]).ToString() : "unspecified"; string fontWeight = (_encoded[0] & 0x008) == 0x008 ? FontWeight.values[_encoded[3]].ToString() : "unspecified"; string fontStyle = (_encoded[0] & 0x010) == 0x010 ? ((FontStyle) _encoded[4]).ToString() : "unspecified"; string maxLines = (_encoded[0] & 0x020) == 0x020 ? _encoded[5].ToString() : "unspecified"; string textHeightBehavior = (_encoded[0] & 0x040) == 0x040 ? TextHeightBehavior.fromEncoded(_encoded[6]).ToString() : "unspecified"; string fontFamily = (_encoded[0] & 0x080) == 0x080 ? _fontFamily : "unspecified"; string fontSize = (_encoded[0] & 0x100) == 0x100 ? _fontSize.ToString() : "unspecified"; string height = (_encoded[0] & 0x200) == 0x200 ? $"{_height}x" : "unspecified"; string ellipsis = (_encoded[0] & 0x400) == 0x400 ? _ellipsis : "unspecified"; string locale = (_encoded[0] & 0x800) == 0x800 ? _locale.ToString() : "unspecified"; return "ParagraphStyle(\n" + $"textAlign: {textAlign}\n" + $"textDirection: {textDirection}\n" + $"fontWeight: {fontWeight}\n" + $"fontStyle: {fontStyle}\n" + $"maxLines: {maxLines}\n" + $"textHeightBehavior: {textHeightBehavior}$\n" + $"fontFamily: {fontFamily}\n" + $"fontSize: {fontSize}\n" + $"height: {height}\n" + $"ellipsis: {ellipsis}\n" + $"locale: {locale}\n" + ")"; } public bool Equals(ParagraphStyle other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return _encoded.equalsList(other._encoded) && _fontFamily == other._fontFamily && Nullable.Equals(_fontSize, other._fontSize) && Nullable.Equals(_height, other._height) && Equals(_strutStyle, other._strutStyle) && _ellipsis == other._ellipsis && Equals(_locale, other._locale); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((ParagraphStyle) obj); } public override int GetHashCode() { unchecked { var hashCode = (_encoded != null ? _encoded.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_fontFamily != null ? _fontFamily.GetHashCode() : 0); hashCode = (hashCode * 397) ^ _fontSize.GetHashCode(); hashCode = (hashCode * 397) ^ _height.GetHashCode(); hashCode = (hashCode * 397) ^ (_strutStyle != null ? _strutStyle.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_ellipsis != null ? _ellipsis.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_locale != null ? _locale.GetHashCode() : 0); return hashCode; } } } public class StrutStyle : IEquatable { public StrutStyle( string fontFamily = null, List fontFamilyFallback = null, float? fontSize = null, float? height = null, float? leading = null, FontWeight fontWeight = null, FontStyle? fontStyle = null, bool? forceStrutHeight = null ) { _encoded = ui_._encodeStrut( fontFamily, fontFamilyFallback, fontSize, height, leading, fontWeight, fontStyle, forceStrutHeight ); _fontFamily = fontFamily; _fontFamilyFallback = fontFamilyFallback; } internal readonly byte[] _encoded; // Most of the data for strut is encoded. internal readonly string _fontFamily; internal readonly List _fontFamilyFallback; public bool Equals(StrutStyle other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return _encoded.equalsList(other._encoded) && _fontFamily == other._fontFamily && _fontFamilyFallback.equalsList(other._fontFamilyFallback); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((StrutStyle) obj); } public override int GetHashCode() { unchecked { var hashCode = (_encoded != null ? _encoded.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_fontFamily != null ? _fontFamily.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_fontFamilyFallback != null ? _fontFamilyFallback.GetHashCode() : 0); return hashCode; } } } public enum TextDirection { rtl, ltr, } public class TextBox : IEquatable { TextBox( float left, float top, float right, float bottom, TextDirection direction) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; this.direction = direction; } public static TextBox fromLTRBD( float left, float top, float right, float bottom, TextDirection direction) { return new TextBox(left, top, right, bottom, direction); } public readonly float left; public readonly float top; public readonly float right; public readonly float bottom; public readonly TextDirection direction; public Rect toRect() => Rect.fromLTRB((float) left, (float) top, (float) right, (float) bottom); public float start { get { return (direction == TextDirection.ltr) ? left : right; } } public float end { get { return (direction == TextDirection.ltr) ? right : left; } } public override string ToString() { return $"TextBox.fromLTRBD({left.ToString("N1")}, {top.ToString("N1")}, {right.ToString("N1")}, {bottom.ToString("N1")}, {direction})"; } public bool Equals(TextBox other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return left.Equals(other.left) && top.Equals(other.top) && right.Equals(other.right) && bottom.Equals(other.bottom) && direction == other.direction; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((TextBox) obj); } public override int GetHashCode() { unchecked { var hashCode = left.GetHashCode(); hashCode = (hashCode * 397) ^ top.GetHashCode(); hashCode = (hashCode * 397) ^ right.GetHashCode(); hashCode = (hashCode * 397) ^ bottom.GetHashCode(); hashCode = (hashCode * 397) ^ (int) direction; return hashCode; } } } public enum TextAffinity { upstream, downstream, } public class TextPosition : IEquatable { public TextPosition( int offset, TextAffinity affinity = TextAffinity.downstream) { this.offset = offset; this.affinity = affinity; } public readonly int offset; public readonly TextAffinity affinity; public override string ToString() { return $"TextPosition(offset: {offset}, affinity: {affinity})"; } public bool Equals(TextPosition other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return offset == other.offset && affinity == other.affinity; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((TextPosition) obj); } public override int GetHashCode() { unchecked { return (offset * 397) ^ (int) affinity; } } } public class TextRange : IEquatable { public TextRange( int start, int end) { D.assert(start >= -1); D.assert(end >= -1); this.start = start; this.end = end; } public static TextRange collapsed(int offset) { D.assert(offset >= -1); return new TextRange(offset, offset); } public static readonly TextRange empty = new TextRange(start: -1, end: -1); public readonly int start; public readonly int end; public bool isValid { get { return start >= 0 && end >= 0; } } public bool isCollapsed { get { return start == end; } } public bool isNormalized { get { return end >= start; } } public string textBefore(string text) { D.assert(isNormalized); return text.Substring(0, start); } public string textAfter(string text) { D.assert(isNormalized); return text.Substring(end); } public string textInside(string text) { D.assert(isNormalized); return text.Substring(start, end - start); } public override string ToString() { return $"TextRange(start: {start}, end: {end})"; } public bool Equals(TextRange other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return start == other.start && end == other.end; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((TextRange) obj); } public override int GetHashCode() { unchecked { return (start * 397) ^ end; } } } public class ParagraphConstraints : IEquatable { public ParagraphConstraints(float width) { this.width = width; } public readonly float width; public override string ToString() { return $"ParagraphConstraints(width: {width})"; } public bool Equals(ParagraphConstraints other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return width.Equals(other.width); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((ParagraphConstraints) obj); } public override int GetHashCode() { return width.GetHashCode(); } } public enum BoxHeightStyle { tight, max, includeLineSpacingMiddle, includeLineSpacingTop, includeLineSpacingBottom, strut, } public enum BoxWidthStyle { tight, max, } public enum PlaceholderAlignment { baseline, aboveBaseline, belowBaseline, top, bottom, middle, } public class LineMetrics : IEquatable { public LineMetrics( bool hardBreak, float ascent, float descent, float unscaledAscent, float height, float width, float left, float baseline, int lineNumber) { this.hardBreak = hardBreak; this.ascent = ascent; this.descent = descent; this.unscaledAscent = unscaledAscent; this.height = height; this.width = width; this.left = left; this.baseline = baseline; this.lineNumber = lineNumber; } public readonly bool hardBreak; public readonly float ascent; public readonly float descent; public readonly float unscaledAscent; public readonly float height; public readonly float width; public readonly float left; public readonly float baseline; public readonly int lineNumber; public override string ToString() { return $"LineMetrics(hardBreak: {hardBreak}, \n" + $"ascent: {ascent}, " + $"descent: {descent}, " + $"unscaledAscent: {unscaledAscent}, " + $"height: {height}, " + $"width: {width}, " + $"left: {left}, " + $"baseline: {baseline}, " + $"lineNumber: {lineNumber})"; } public bool Equals(LineMetrics other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return hardBreak == other.hardBreak && ascent.Equals(other.ascent) && descent.Equals(other.descent) && unscaledAscent.Equals(other.unscaledAscent) && height.Equals(other.height) && width.Equals(other.width) && left.Equals(other.left) && baseline.Equals(other.baseline) && lineNumber == other.lineNumber; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((LineMetrics) obj); } public override int GetHashCode() { unchecked { var hashCode = hardBreak.GetHashCode(); hashCode = (hashCode * 397) ^ ascent.GetHashCode(); hashCode = (hashCode * 397) ^ descent.GetHashCode(); hashCode = (hashCode * 397) ^ unscaledAscent.GetHashCode(); hashCode = (hashCode * 397) ^ height.GetHashCode(); hashCode = (hashCode * 397) ^ width.GetHashCode(); hashCode = (hashCode * 397) ^ left.GetHashCode(); hashCode = (hashCode * 397) ^ baseline.GetHashCode(); hashCode = (hashCode * 397) ^ lineNumber; return hashCode; } } } public class Paragraph : NativeWrapper { internal Paragraph(IntPtr ptr) { _setPtr(ptr); } [DllImport(NativeBindings.dllName)] static extern float Paragraph_width(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern float Paragraph_height(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern float Paragraph_longestLine(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern float Paragraph_minIntrinsicWidth(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern float Paragraph_maxIntrinsicWidth(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern float Paragraph_alphabeticBaseline(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern float Paragraph_ideographicBaseline(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern bool Paragraph_didExceedMaxLines(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern void Paragraph_layout(IntPtr ptr, float width); [DllImport(NativeBindings.dllName)] static extern Float32List Paragraph_getRectsForRange(IntPtr ptr, int start, int end, int boxHeightStyle, int boxWidthStyle); [DllImport(NativeBindings.dllName)] static extern Float32List Paragraph_getRectsForPlaceholders(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern unsafe void Paragraph_getPositionForOffset(IntPtr ptr, float dx, float dy, int* encodedPtr); [DllImport(NativeBindings.dllName)] static extern unsafe void Paragraph_getWordBoundary(IntPtr ptr, int offset, int* boundaryPtr); [DllImport(NativeBindings.dllName)] static extern unsafe void Paragraph_getLineBoundary(IntPtr ptr, int offset, int* boundaryPtr); [DllImport(NativeBindings.dllName)] static extern void Paragraph_paint(IntPtr ptr, IntPtr canvas, float x, float y); [DllImport(NativeBindings.dllName)] static extern Float32List Paragraph_computeLineMetrics(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern void Paragraph_dispose(IntPtr ptr); public void layout(ParagraphConstraints constraints) => _layout(constraints.width); void _layout(float width) => Paragraph_layout(_ptr, width); List _decodeTextBoxes(float[] encoded, int size) { int count = size / 5; List boxes = new List(); int position = 0; for (int index = 0; index < count; index += 1) { unsafe { boxes.Add(TextBox.fromLTRBD( encoded[position++], encoded[position++], encoded[position++], encoded[position++], (TextDirection) encoded[position++] )); } } return boxes; } public override void DisposePtr(IntPtr ptr) { Paragraph_dispose(ptr); } public float width() { return Paragraph_width(_ptr); } public float height() { return Paragraph_height(_ptr); } public float longestLine() { return Paragraph_longestLine(_ptr); } public float minIntrinsicWidth() { return Paragraph_minIntrinsicWidth(_ptr); } public float maxIntrinsicWidth() { return Paragraph_maxIntrinsicWidth(_ptr); } public float alphabeticBaseline() { return Paragraph_alphabeticBaseline(_ptr); } public float ideographicBaseline() { return Paragraph_ideographicBaseline(_ptr); } public bool didExceedMaxLines() { return Paragraph_didExceedMaxLines(_ptr); } public List getBoxesForRange(int start, int end, BoxHeightStyle boxHeightStyle = BoxHeightStyle.tight, BoxWidthStyle boxWidthStyle = BoxWidthStyle.tight) { var data = _getBoxesForRange(start, end, (int) boxHeightStyle, (int) boxWidthStyle).toFloatArrayAndFree(); return _decodeTextBoxes(data, data.Length); } // See paragraph.cc for the layout of this return value. Float32List _getBoxesForRange(int start, int end, int boxHeightStyle, int boxWidthStyle) => Paragraph_getRectsForRange(_ptr, start, end, boxHeightStyle, boxWidthStyle); public List getBoxesForPlaceholders() { float[] data = _getBoxesForPlaceholders().toFloatArrayAndFree(); return _decodeTextBoxes(data, data.Length); } Float32List _getBoxesForPlaceholders() => Paragraph_getRectsForPlaceholders(_ptr); public unsafe TextPosition getPositionForOffset(Offset offset) { int[] encoded = new int[2]; fixed (int* encodedPtr = encoded) { _getPositionForOffset(offset.dx, offset.dy, encodedPtr); } return new TextPosition(offset: encoded[0], affinity: (TextAffinity) encoded[1]); } unsafe void _getPositionForOffset(float dx, float dy, int* encodedPtr) => Paragraph_getPositionForOffset(_ptr, dx, dy, encodedPtr); public unsafe TextRange getWordBoundary(TextPosition position) { int[] boundary = new int[2]; fixed (int* boundaryPtr = boundary) { _getWordBoundary(position.offset, boundaryPtr); } return new TextRange(start: boundary[0], end: boundary[1]); } unsafe void _getWordBoundary(int offset, int* boundaryPtr) => Paragraph_getWordBoundary(_ptr, offset, boundaryPtr); public unsafe TextRange getLineBoundary(TextPosition position) { int[] boundary = new int[2]; fixed (int* boundaryPtr = boundary) { _getLineBoundary(position.offset, boundaryPtr); } return new TextRange(start: boundary[0], end: boundary[1]); } unsafe void _getLineBoundary(int offset, int* boundaryPtr) => Paragraph_getLineBoundary(_ptr, offset, boundaryPtr); internal void _paint(Canvas canvas, float x, float y) { Paragraph_paint(_ptr, canvas._ptr, x, y); } public List computeLineMetrics() { var data = _computeLineMetrics().toFloatArrayAndFree(); int count = data.Length / 9; int position = 0; List metrics = new List(); for (int index = 0; index < count; index += 1) { unsafe { metrics.Add(new LineMetrics( hardBreak: data[position++] != 0, ascent: data[position++], descent: data[position++], unscaledAscent: data[position++], height: data[position++], width: data[position++], left: data[position++], baseline: data[position++], lineNumber: (int) data[position++] )); } } return metrics; } Float32List _computeLineMetrics() => Paragraph_computeLineMetrics(_ptr); } public class ParagraphBuilder : NativeWrapper { [DllImport(NativeBindings.dllName)] static extern IntPtr ParagraphBuilder_constructor( int[] encoded, int encodeSize, byte[] strutData, int structDataSize, string fontFamily, string[] structFontFamily, int structFontFamilySize, float fontSize, float height, [MarshalAs(UnmanagedType.LPWStr)] string ellipsis, string locale); [DllImport(NativeBindings.dllName)] static unsafe extern void ParagraphBuilder_pushStyle( IntPtr ptr, int[] encoded, int encodedSize, string[] fontFamilies, int fontFamiliesSize, float fontSize, float letterSpacing, float wordSpacing, float height, float decorationThickness, string locale, IntPtr* backgroundObjects, byte[] backgroundData, IntPtr* foregroundObjects, byte[] foregroundData, byte[] shadowsData, int shadowDataSize, byte[] fontFeaturesData, int fontFeatureDataSize ); [DllImport(NativeBindings.dllName)] static extern void ParagraphBuilder_pop(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern IntPtr ParagraphBuilder_addText(IntPtr ptr, [MarshalAs(UnmanagedType.LPWStr)] string text); [DllImport(NativeBindings.dllName)] static extern IntPtr ParagraphBuilder_addPlaceholder(IntPtr ptr, float width, float height, int alignment, float baselineOffset, int baseline); [DllImport(NativeBindings.dllName)] static extern IntPtr ParagraphBuilder_build(IntPtr ptr); [DllImport(NativeBindings.dllName)] static extern void ParagraphBuilder_dispose(IntPtr ptr); public ParagraphBuilder(ParagraphStyle style) { List strutFontFamilies = null; StrutStyle strutStyle = style._strutStyle; if (strutStyle != null) { strutFontFamilies = new List(); string fontFamily = strutStyle._fontFamily; if (fontFamily != null) strutFontFamilies.Add(fontFamily); if (strutStyle._fontFamilyFallback != null) strutFontFamilies.AddRange(strutStyle._fontFamilyFallback); } _setPtr(_constructor( style._encoded.ToArray(), strutStyle?._encoded.ToArray(), style._fontFamily, strutFontFamilies?.ToArray(), style._fontSize ?? 0, style._height ?? 0, style._ellipsis, _encodeLocale(style._locale) )); } IntPtr _constructor( int[] encoded, byte[] structData, string fontFamily, string[] structFontFamily, float fontSize, float height, string ellipsis, string locale ) => ParagraphBuilder_constructor( encoded, encoded == null ? 0 : encoded.Length, structData, structData == null ? 0 : structData.Length, fontFamily, structFontFamily, structFontFamily == null ? 0 : structFontFamily.Length, fontSize, height, ellipsis, locale ); public override void DisposePtr(IntPtr ptr) { ParagraphBuilder_dispose(ptr); } public int placeholderCount { get { return _placeholderCount; } } int _placeholderCount = 0; public List placeholderScales { get { return _placeholderScales; } } List _placeholderScales = new List(); public void pushStyle(TextStyle style) { List fullFontFamilies = new List(); fullFontFamilies.Add(style._fontFamily); if (style._fontFamilyFallback != null) fullFontFamilies.AddRange(style._fontFamilyFallback); byte[] encodedFontFeatures = null; List fontFeatures = style._fontFeatures; if (fontFeatures != null) { encodedFontFeatures = new byte[fontFeatures.Count * FontFeature._kEncodedSize]; int byteOffset = 0; foreach (FontFeature feature in fontFeatures) { var byteFeature = new byte[FontFeature._kEncodedSize - byteOffset]; Array.Copy(encodedFontFeatures, 0, byteFeature, 0, FontFeature._kEncodedSize); feature._encode(byteFeature); byteOffset += FontFeature._kEncodedSize; } } _pushStyle( style._encoded.ToArray(), fullFontFamilies.ToArray(), style._fontSize, style._letterSpacing, style._wordSpacing, style._height, style._decorationThickness, _encodeLocale(style._locale), style._background?._objectPtrs, style._background?._data, style._foreground?._objectPtrs, style._foreground?._data, Shadow._encodeShadows(style._shadows), encodedFontFeatures ); } unsafe void _pushStyle( int[] encoded, string[] fontFamilies, float? fontSize, float? letterSpacing, float? wordSpacing, float? height, float? decorationThickness, string locale, IntPtr[] backgroundObjects, byte[] backgroundData, IntPtr[] foregroundObjects, byte[] foregroundData, byte[] shadowsData, byte[] fontFeaturesData ) { fixed (IntPtr* backgroundObjectsPtr = backgroundObjects) fixed (IntPtr* foregroundObjectsPtr = foregroundObjects) ParagraphBuilder_pushStyle(_ptr, encoded, encoded.Length, fontFamilies, fontFamilies.Length, fontSize.GetValueOrDefault(0), letterSpacing.GetValueOrDefault(0), wordSpacing.GetValueOrDefault(0), height.GetValueOrDefault(0), decorationThickness.GetValueOrDefault(0), locale, backgroundObjectsPtr, backgroundData, foregroundObjectsPtr, foregroundData, shadowsData, shadowsData?.Length ?? 0, fontFeaturesData, fontFeaturesData?.Length ?? 0 ); } static string _encodeLocale(Locale locale) => locale?.ToString() ?? ""; public void pop() => ParagraphBuilder_pop(_ptr); public void addText(string text) { IntPtr error = _addText(text); if (error != IntPtr.Zero) { throw new Exception(Marshal.PtrToStringAnsi(error)); } } IntPtr _addText(string text) => ParagraphBuilder_addText(_ptr, text); public void addPlaceholder(float width, float height, PlaceholderAlignment alignment, TextBaseline baseline, float scale = 1.0f, float? baselineOffset = null ) { // Require a baseline to be specified if using a baseline-based alignment. D.assert(alignment == PlaceholderAlignment.aboveBaseline || alignment == PlaceholderAlignment.belowBaseline || alignment == PlaceholderAlignment.baseline); // Default the baselineOffset to height if null. This will place the placeholder // fully above the baseline, similar to [PlaceholderAlignment.aboveBaseline]. float baselineOffsetFloat = baselineOffset ?? height; _addPlaceholder(width * scale, height * scale, (int) alignment, baselineOffsetFloat * scale, (int) baseline); _placeholderCount++; _placeholderScales.Add(scale); } IntPtr _addPlaceholder(float width, float height, int alignment, float baselineOffset, int baseline) => ParagraphBuilder_addPlaceholder(_ptr, width, height, alignment, baselineOffset, baseline); public Paragraph build() { Paragraph paragraph = new Paragraph(_build()); return paragraph; } IntPtr _build() => ParagraphBuilder_build(_ptr); } }