From dd4a95aeb502fa54d040fce8df2bf69a2e9609a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Wed, 9 Aug 2023 16:23:35 +0200 Subject: [PATCH 1/2] :sparkles: add support for us payroll check register --- .../payroll_check_register_v1_async.txt | 43 ++++ src/Mindee/Parsing/Standard/DecimalField.cs | 26 +++ .../Parsing/Standard/DecimalJsonConverter.cs | 33 +++ src/Mindee/Parsing/SummaryHelper.cs | 5 + .../PayrollCheckRegisterV1.cs | 13 ++ .../PayrollCheckRegisterV1Deduction.cs | 75 +++++++ .../PayrollCheckRegisterV1Document.cs | 60 +++++ .../PayrollCheckRegisterV1Earning.cs | 97 ++++++++ .../PayrollCheckRegisterV1Payment.cs | 207 ++++++++++++++++++ .../PayrollCheckRegisterV1Tax.cs | 86 ++++++++ .../PayrollCheckRegisterV1Test.cs | 71 ++++++ tests/resources | 2 +- 12 files changed, 717 insertions(+), 1 deletion(-) create mode 100644 docs/code_samples/payroll_check_register_v1_async.txt create mode 100644 src/Mindee/Parsing/Standard/DecimalField.cs create mode 100644 src/Mindee/Parsing/Standard/DecimalJsonConverter.cs create mode 100644 src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1.cs create mode 100644 src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Deduction.cs create mode 100644 src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Document.cs create mode 100644 src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Earning.cs create mode 100644 src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Payment.cs create mode 100644 src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Tax.cs create mode 100644 tests/Mindee.UnitTests/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Test.cs diff --git a/docs/code_samples/payroll_check_register_v1_async.txt b/docs/code_samples/payroll_check_register_v1_async.txt new file mode 100644 index 00000000..6e1a0e37 --- /dev/null +++ b/docs/code_samples/payroll_check_register_v1_async.txt @@ -0,0 +1,43 @@ +using Mindee; +using Mindee.Input; +using Mindee.Parsing.Common; +using Mindee.Product.Us.PayrollCheckRegister; +using System.Threading; + +string apiKey = "my-api-key"; +string filePath = "/path/to/the/file.ext"; + +// Don't try to get the document more than this many times +const int maxRetries = 10; + +// Wait this many seconds between each try +const int intervalSec = 6; + +// Construct a new client +MindeeClient mindeeClient = new MindeeClient(apiKey); + +// load an input source +var inputSource = new LocalInputSource(filePath); + +var enqueueResponse = await mindeeClient + .EnqueueAsync(inputSource); + +string jobId = enqueueResponse.Job.Id; + +int retryCount = 0; +AsyncPredictResponse response; + +while (retryCount < maxRetries) +{ + response = await mindeeClient.ParseQueuedAsync(jobId); + if (response.Document != null) + { + // Print a summary of the predictions + System.Console.WriteLine(response.Document.ToString()); + break; + } + retryCount++; + + // Wait for intervalSec seconds before retrying + Thread.Sleep(intervalSec * 1000); +} \ No newline at end of file diff --git a/src/Mindee/Parsing/Standard/DecimalField.cs b/src/Mindee/Parsing/Standard/DecimalField.cs new file mode 100644 index 00000000..b63177ff --- /dev/null +++ b/src/Mindee/Parsing/Standard/DecimalField.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; + +namespace Mindee.Parsing.Standard +{ + /// + /// Represent an amount. + /// + public class DecimalField : BaseField + { + /// + /// An amount value. + /// + /// 5.89 + [JsonPropertyName("value")] + [JsonConverter(typeof(DecimalJsonConverter))] + public decimal? Value { get; set; } + + /// + /// A pretty summary of the value. + /// + public override string ToString() + { + return SummaryHelper.FormatAmount(Value); + } + } +} diff --git a/src/Mindee/Parsing/Standard/DecimalJsonConverter.cs b/src/Mindee/Parsing/Standard/DecimalJsonConverter.cs new file mode 100644 index 00000000..e049341c --- /dev/null +++ b/src/Mindee/Parsing/Standard/DecimalJsonConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Mindee.Parsing.Standard +{ + /// + /// + /// + public class DecimalJsonConverter : JsonConverter + { + /// + /// + /// + public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string valueString = reader.GetString(); + if (valueString == "") + { + return null; + } + return decimal.Parse(valueString); + } + + /// + /// + /// + public override void Write(Utf8JsonWriter writer, decimal? value, JsonSerializerOptions options) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Mindee/Parsing/SummaryHelper.cs b/src/Mindee/Parsing/SummaryHelper.cs index bfdb819f..9fb0c091 100644 --- a/src/Mindee/Parsing/SummaryHelper.cs +++ b/src/Mindee/Parsing/SummaryHelper.cs @@ -19,6 +19,11 @@ public static string FormatAmount(double? amount) return amount == null ? "" : amount.Value.ToString(".00###"); } + public static string FormatAmount(decimal? amount) + { + return amount == null ? "" : amount.Value.ToString(".00###"); + } + public static string FormatString(string str) { return str ?? ""; diff --git a/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1.cs b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1.cs new file mode 100644 index 00000000..2dfaef29 --- /dev/null +++ b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1.cs @@ -0,0 +1,13 @@ +using Mindee.Http; +using Mindee.Parsing.Common; + +namespace Mindee.Product.Us.PayrollCheckRegister +{ + /// + /// The definition for Payroll Check Register, API version 1. + /// + [Endpoint("payroll_check_register", "1")] + public sealed class PayrollCheckRegisterV1 : Inference + { + } +} diff --git a/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Deduction.cs b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Deduction.cs new file mode 100644 index 00000000..36557962 --- /dev/null +++ b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Deduction.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; +using Mindee.Parsing; +using Mindee.Parsing.Standard; + +namespace Mindee.Product.Us.PayrollCheckRegister +{ + /// + /// The deductions. + /// + public sealed class PayrollCheckRegisterV1Deduction : ILineItemField + { + /// + /// The deduction line amount. + /// + [JsonPropertyName("amount")] + [JsonConverter(typeof(DecimalJsonConverter))] + public decimal? Amount { get; set; } + + /// + /// The deduction line code or type. + /// + [JsonPropertyName("code")] + public string Code { get; set; } + + /// + /// Output the line in a format suitable for inclusion in an rST table. + /// + public string ToTableLine() + { + Dictionary printable = PrintableValues(); + return "| " + + String.Format("{0,-6}", printable["Amount"]) + + " | " + + String.Format("{0,-14}", printable["Code"]) + + " |"; + } + + private Dictionary PrintableValues() + { + return new Dictionary() + { + {"Amount", Amount.ToString()}, + {"Code", SummaryHelper.FormatString(Code)}, + }; + } + } + + /// + /// The deductions. + /// + public class PayrollCheckRegisterV1Deductions : List + { + /// + /// Default string representation. + /// + public override string ToString() + { + if (this.Count == 0) + { + return "\n"; + } + int[] columnSizes = { 8, 16 }; + StringBuilder outStr = new StringBuilder("\n"); + outStr.Append(" " + SummaryHelper.LineSeparator(columnSizes, '-') + " "); + outStr.Append("| Amount "); + outStr.Append("| Deduction Code "); + outStr.Append("|\n " + SummaryHelper.LineSeparator(columnSizes, '=')); + outStr.Append(SummaryHelper.ArrayToString(this, columnSizes)); + return outStr.ToString(); + } + } +} diff --git a/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Document.cs b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Document.cs new file mode 100644 index 00000000..42b659c5 --- /dev/null +++ b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Document.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using Mindee.Parsing; +using Mindee.Parsing.Standard; + +namespace Mindee.Product.Us.PayrollCheckRegister +{ + /// + /// Document data for Payroll Check Register, API version 1. + /// + public class PayrollCheckRegisterV1Document : IPrediction + { + /// + /// The name of the company. + /// + [JsonPropertyName("company_name")] + public StringField CompanyName { get; set; } + + /// + /// The date on which the payment was made. + /// + [JsonPropertyName("pay_date")] + public DateField PayDate { get; set; } + + /// + /// List of payments. + /// + [JsonPropertyName("payments")] + [JsonConverter(typeof(ObjectListJsonConverter))] + public PayrollCheckRegisterV1Payments Payments { get; set; } + + /// + /// The date at which the period ends. + /// + [JsonPropertyName("period_end")] + public DateField PeriodEnd { get; set; } + + /// + /// The date at which the period starts. + /// + [JsonPropertyName("period_start")] + public DateField PeriodStart { get; set; } + + /// + /// A prettier representation of the current model values. + /// + public override string ToString() + { + StringBuilder result = new StringBuilder(); + result.Append($":Company Name: {CompanyName}\n"); + result.Append($":Period Start: {PeriodStart}\n"); + result.Append($":Period End: {PeriodEnd}\n"); + result.Append($":Pay Date: {PayDate}\n"); + result.Append($":Payments:{Payments}"); + return SummaryHelper.Clean(result.ToString()); + } + } +} diff --git a/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Earning.cs b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Earning.cs new file mode 100644 index 00000000..ecf301b8 --- /dev/null +++ b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Earning.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; +using Mindee.Parsing; +using Mindee.Parsing.Standard; + +namespace Mindee.Product.Us.PayrollCheckRegister +{ + /// + /// The earnings. + /// + public sealed class PayrollCheckRegisterV1Earning : ILineItemField + { + /// + /// The earning line amount. + /// + [JsonPropertyName("amount")] + [JsonConverter(typeof(DecimalJsonConverter))] + public decimal? Amount { get; set; } + + /// + /// The earning line title or type. + /// + [JsonPropertyName("code")] + public string Code { get; set; } + + /// + /// The earning line hours. + /// + [JsonPropertyName("hours")] + [JsonConverter(typeof(DecimalJsonConverter))] + public decimal? Hours { get; set; } + + /// + /// The earning line rate. + /// + [JsonPropertyName("rate")] + [JsonConverter(typeof(DecimalJsonConverter))] + public decimal? Rate { get; set; } + + /// + /// Output the line in a format suitable for inclusion in an rST table. + /// + public string ToTableLine() + { + Dictionary printable = PrintableValues(); + return "| " + + String.Format("{0,-6}", printable["Amount"]) + + " | " + + String.Format("{0,-12}", printable["Code"]) + + " | " + + String.Format("{0,-5}", printable["Hours"]) + + " | " + + String.Format("{0,-4}", printable["Rate"]) + + " |"; + } + + private Dictionary PrintableValues() + { + return new Dictionary() + { + {"Amount", SummaryHelper.FormatAmount(Amount)}, + {"Code", SummaryHelper.FormatString(Code)}, + {"Hours", SummaryHelper.FormatAmount(Hours)}, + {"Rate", SummaryHelper.FormatAmount(Rate)}, + }; + } + } + + /// + /// The earnings. + /// + public class PayrollCheckRegisterV1Earnings : List + { + /// + /// Default string representation. + /// + public override string ToString() + { + if (this.Count == 0) + { + return "\n"; + } + int[] columnSizes = { 8, 14, 7, 6 }; + StringBuilder outStr = new StringBuilder("\n"); + outStr.Append(" " + SummaryHelper.LineSeparator(columnSizes, '-') + " "); + outStr.Append("| Amount "); + outStr.Append("| Earning Code "); + outStr.Append("| Hours "); + outStr.Append("| rate "); + outStr.Append("|\n " + SummaryHelper.LineSeparator(columnSizes, '=')); + outStr.Append(SummaryHelper.ArrayToString(this, columnSizes)); + return outStr.ToString(); + } + } +} diff --git a/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Payment.cs b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Payment.cs new file mode 100644 index 00000000..775bc415 --- /dev/null +++ b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Payment.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; +using Mindee.Parsing; +using Mindee.Parsing.Standard; + +namespace Mindee.Product.Us.PayrollCheckRegister +{ + /// + /// List of payments. + /// + public sealed class PayrollCheckRegisterV1Payment : ILineItemField + { + /// + /// The deductions. + /// + [JsonPropertyName("deductions")] + [JsonConverter(typeof(ObjectListJsonConverter))] + public PayrollCheckRegisterV1Deductions Deductions { get; set; } + + /// + /// The earnings. + /// + [JsonPropertyName("earnings")] + [JsonConverter(typeof(ObjectListJsonConverter))] + public PayrollCheckRegisterV1Earnings Earnings { get; set; } + + /// + /// The full name of the employee. + /// + [JsonPropertyName("employee_name")] + public StringField EmployeeName { get; set; } + + /// + /// The employee code or number. + /// + [JsonPropertyName("employee_number")] + public StringField EmployeeNumber { get; set; } + + /// + /// The net pay amount. + /// + [JsonPropertyName("net_pay")] + public DecimalField NetPay { get; set; } + + /// + /// The pay date in ISO format (YYYY-MM-DD). + /// + [JsonPropertyName("pay_date")] + public DateField PayDate { get; set; } + + /// + /// The payment number or identifier (i.e. check number). + /// + [JsonPropertyName("payment_number")] + public StringField PaymentNumber { get; set; } + + /// + /// The type of payment (Voucher or Check). + /// + [JsonPropertyName("payment_type")] + public ClassificationField PaymentType { get; set; } + + /// + /// The date at which the period ends in YYYY-MM-DD or MM-DD format. + /// + [JsonPropertyName("period_end")] + public DateField PeriodEnd { get; set; } + + /// + /// The date at which the period starts in YYYY-MM-DD or MM-DD format. + /// + [JsonPropertyName("period_start")] + public DateField PeriodStart { get; set; } + + /// + /// The taxes. + /// + [JsonPropertyName("taxes")] + [JsonConverter(typeof(ObjectListJsonConverter))] + public PayrollCheckRegisterV1Taxes Taxes { get; set; } + + /// + /// The total amount of deductions. + /// + [JsonPropertyName("total_deductions")] + public DecimalField TotalDeductions { get; set; } + + /// + /// The total amount earned. + /// + [JsonPropertyName("total_earnings")] + public DecimalField TotalEarnings { get; set; } + + /// + /// The total amount of hours worked. + /// + [JsonPropertyName("total_hours")] + public DecimalField TotalHours { get; set; } + + /// + /// The total amount of taxes. + /// + [JsonPropertyName("total_tax")] + public DecimalField TotalTax { get; set; } + + /// + /// Output the line in a format suitable for inclusion in an rST table. + /// + public string ToTableLine() + { + Dictionary printable = PrintableValues(); + return "| " + + String.Format("{0,-10}", printable["Deductions"]) + + " | " + + String.Format("{0,-8}", printable["Earnings"]) + + " | " + + String.Format("{0,-13}", printable["EmployeeName"]) + + " | " + + String.Format("{0,-15}", printable["EmployeeNumber"]) + + " | " + + String.Format("{0,-7}", printable["NetPay"]) + + " | " + + String.Format("{0,-8}", printable["PayDate"]) + + " | " + + String.Format("{0,-14}", printable["PaymentNumber"]) + + " | " + + String.Format("{0,-12}", printable["PaymentType"]) + + " | " + + String.Format("{0,-10}", printable["PeriodEnd"]) + + " | " + + String.Format("{0,-12}", printable["PeriodStart"]) + + " | " + + String.Format("{0,-5}", printable["Taxes"]) + + " | " + + String.Format("{0,-16}", printable["TotalDeductions"]) + + " | " + + String.Format("{0,-14}", printable["TotalEarnings"]) + + " | " + + String.Format("{0,-11}", printable["TotalHours"]) + + " | " + + String.Format("{0,-9}", printable["TotalTax"]) + + " |"; + } + + private Dictionary PrintableValues() + { + return new Dictionary() + { + {"Deductions", Deductions.ToString()}, + {"Earnings", Earnings.ToString()}, + {"EmployeeName", EmployeeName.ToString()}, + {"EmployeeNumber", EmployeeNumber.ToString()}, + {"NetPay", NetPay.ToString()}, + {"PayDate", PayDate.ToString()}, + {"PaymentNumber", PaymentNumber.ToString()}, + {"PaymentType", PaymentType.ToString()}, + {"PeriodEnd", PeriodEnd.ToString()}, + {"PeriodStart", PeriodStart.ToString()}, + {"Taxes", Taxes.ToString()}, + {"TotalDeductions", TotalDeductions.ToString()}, + {"TotalEarnings", TotalEarnings.ToString()}, + {"TotalHours", TotalHours.ToString()}, + {"TotalTax", TotalTax.ToString()}, + }; + } + } + + /// + /// List of payments. + /// + public class PayrollCheckRegisterV1Payments : List + { + /// + /// Default string representation. + /// + public override string ToString() + { + if (this.Count == 0) + { + return "\n"; + } + int[] columnSizes = { 12, 10, 15, 17, 9, 10, 16, 14, 12, 14, 7, 18, 16, 13, 11 }; + StringBuilder outStr = new StringBuilder("\n"); + outStr.Append(" " + SummaryHelper.LineSeparator(columnSizes, '-') + " "); + outStr.Append("| Deductions "); + outStr.Append("| Earnings "); + outStr.Append("| Employee Name "); + outStr.Append("| Employee Number "); + outStr.Append("| Net Pay "); + outStr.Append("| Pay Date "); + outStr.Append("| Payment Number "); + outStr.Append("| Payment Type "); + outStr.Append("| Period End "); + outStr.Append("| Period Start "); + outStr.Append("| Taxes "); + outStr.Append("| Total Deductions "); + outStr.Append("| Total Earnings "); + outStr.Append("| Total Hours "); + outStr.Append("| Total Tax "); + outStr.Append("|\n " + SummaryHelper.LineSeparator(columnSizes, '=')); + outStr.Append(SummaryHelper.ArrayToString(this, columnSizes)); + return outStr.ToString(); + } + } +} diff --git a/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Tax.cs b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Tax.cs new file mode 100644 index 00000000..9e61d8e8 --- /dev/null +++ b/src/Mindee/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Tax.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; +using Mindee.Parsing; +using Mindee.Parsing.Standard; + +namespace Mindee.Product.Us.PayrollCheckRegister +{ + /// + /// The taxes. + /// + public sealed class PayrollCheckRegisterV1Tax : ILineItemField + { + /// + /// The tax line amount. + /// + [JsonPropertyName("amount")] + [JsonConverter(typeof(DecimalJsonConverter))] + public decimal? Amount { get; set; } + + /// + /// The tax line base. + /// + [JsonPropertyName("base")] + [JsonConverter(typeof(DecimalJsonConverter))] + public decimal? Base { get; set; } + + /// + /// The tax line code or type. + /// + [JsonPropertyName("code")] + public string Code { get; set; } + + /// + /// Output the line in a format suitable for inclusion in an rST table. + /// + public string ToTableLine() + { + Dictionary printable = PrintableValues(); + return "| " + + String.Format("{0,-6}", printable["Amount"]) + + " | " + + String.Format("{0,-4}", printable["Base"]) + + " | " + + String.Format("{0,-4}", printable["Code"]) + + " |"; + } + + private Dictionary PrintableValues() + { + return new Dictionary() + { + {"Amount", SummaryHelper.FormatAmount(Amount)}, + {"Base", SummaryHelper.FormatAmount(Amount)}, + {"Code", SummaryHelper.FormatString(Code)}, + }; + } + } + + /// + /// The taxes. + /// + public class PayrollCheckRegisterV1Taxes : List + { + /// + /// Default string representation. + /// + public override string ToString() + { + if (this.Count == 0) + { + return "\n"; + } + int[] columnSizes = { 8, 6, 6 }; + StringBuilder outStr = new StringBuilder("\n"); + outStr.Append(" " + SummaryHelper.LineSeparator(columnSizes, '-') + " "); + outStr.Append("| Amount "); + outStr.Append("| Base "); + outStr.Append("| Code "); + outStr.Append("|\n " + SummaryHelper.LineSeparator(columnSizes, '=')); + outStr.Append(SummaryHelper.ArrayToString(this, columnSizes)); + return outStr.ToString(); + } + } +} diff --git a/tests/Mindee.UnitTests/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Test.cs b/tests/Mindee.UnitTests/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Test.cs new file mode 100644 index 00000000..48cfd5cc --- /dev/null +++ b/tests/Mindee.UnitTests/Product/Us/PayrollCheckRegister/PayrollCheckRegisterV1Test.cs @@ -0,0 +1,71 @@ +using Mindee.Parsing.Common; +using Mindee.Product.Us.PayrollCheckRegister; + +namespace Mindee.UnitTests.Product.Us.PayrollCheckRegister +{ + [Trait("Category", "PayrollCheckRegisterV1")] + public class PayrollCheckRegisterV1Test + { + [Fact] + public async Task Predict_CheckEmpty() + { + var response = await GetPrediction("empty"); + Assert.Null(response.Document.Inference.Prediction.CompanyName.Value); + Assert.Null(response.Document.Inference.Prediction.PeriodStart.Value); + Assert.Null(response.Document.Inference.Prediction.PeriodEnd.Value); + Assert.Null(response.Document.Inference.Prediction.PayDate.Value); + Assert.Empty(response.Document.Inference.Prediction.Payments); + } + + [Fact] + public async Task Predict_CheckDocument() + { + var response = await GetPrediction("complete"); + Assert.Equal(expected: 2, actual: response.Document.Inference.Pages.Count); + PayrollCheckRegisterV1Document docPrediction = response.Document.Inference.Prediction; + Assert.Equal(expected: 13, actual: docPrediction.Payments.Count); + Assert.Equal(expected: "Economists For Hire, LLC", actual: docPrediction.CompanyName.Value); + Console.Out.Write(response.Document.ToString()); + } + + [Fact] + public async Task Predict_CheckPage0() + { + var response = await GetPrediction("complete"); + PayrollCheckRegisterV1Document page0Prediction = response.Document.Inference.Pages[0].Prediction; + + Assert.Equal(expected: "Economists For Hire, LLC", actual: page0Prediction.CompanyName.Value); + + Assert.Equal(expected: "1994-12-20", actual: page0Prediction.PeriodStart.Value); + Assert.Equal(expected: "1994-12-20", actual: page0Prediction.PeriodStart.DateObject?.ToString("yyyy-MM-dd")); + Assert.Equal(expected: "1994-12-26", actual: page0Prediction.PeriodEnd.Value); + Assert.Equal(expected: "1994-12-30", actual: page0Prediction.PayDate.Value); + + Assert.Equal(expected: 5, actual: page0Prediction.Payments.Count); + + PayrollCheckRegisterV1Payment payment = page0Prediction.Payments[3]; + Assert.Equal(expected: "Hobbes, Thomas", actual: payment.EmployeeName.Value); + + Assert.Equal(expected: 7, actual: payment.Taxes.Count); + Assert.Equal(expected: new decimal(0.6000), actual: payment.Taxes[4].Amount); + Assert.Equal(expected: 0.6, actual: (double?)payment.Taxes[4].Amount); + Assert.False(payment.Taxes[4].Amount.Equals(other: payment.Taxes[3].Amount)); + Assert.Null(payment.Taxes[4].Base); + Assert.Equal(expected: "NYSDI", actual: payment.Taxes[4].Code); + + Assert.Single(payment.Earnings); + Assert.Equal(expected: new decimal(1463.24), actual: payment.Earnings[0].Amount); + Assert.Equal(expected: new decimal(0), actual: payment.Earnings[0].Hours); + Assert.Equal(expected: "SA", actual: payment.Earnings[0].Code); + + Assert.Empty(payment.Deductions); + } + + private static async Task> GetPrediction(string name) + { + string fileName = $"Resources/us/payroll_check_register/response_v1/{name}.json"; + var mindeeAPi = UnitTestBase.GetMindeeApi(fileName); + return await mindeeAPi.DocumentQueueGetAsync(jobId: "fake-job-id"); + } + } +} diff --git a/tests/resources b/tests/resources index 9a341467..ed36efe3 160000 --- a/tests/resources +++ b/tests/resources @@ -1 +1 @@ -Subproject commit 9a34146755c348281c28bbf351900229b412797e +Subproject commit ed36efe3ab194390deb6789bbda74c0d2678d321 From 019fc9fca32bde0e90ceed22d49c37642c6ec098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Thu, 10 Aug 2023 23:19:47 +0200 Subject: [PATCH 2/2] add console tables --- .../Table/AsciiDoubleHeadTableBorder.cs | 40 + src/Mindee/Parsing/Table/Color.Generated.cs | 1360 +++++++++++++++++ src/Mindee/Parsing/Table/Color.cs | 292 ++++ src/Mindee/Parsing/Table/ColorPalette.cs | 76 + src/Mindee/Parsing/Table/ColorTable.cs | 55 + src/Mindee/Parsing/Table/IAlignable.cs | 12 + src/Mindee/Parsing/Table/IColumn.cs | 18 + src/Mindee/Parsing/Table/IExpandable.cs | 14 + src/Mindee/Parsing/Table/IHasBorder.cs | 19 + src/Mindee/Parsing/Table/IHasTableBorder.cs | 12 + src/Mindee/Parsing/Table/IRenderable.cs | 26 + src/Mindee/Parsing/Table/Justify.cs | 22 + src/Mindee/Parsing/Table/Measurement.cs | 76 + src/Mindee/Parsing/Table/Padding.cs | 132 ++ src/Mindee/Parsing/Table/RenderOptions.cs | 66 + src/Mindee/Parsing/Table/Renderable.cs | 43 + src/Mindee/Parsing/Table/Segment.cs | 647 ++++++++ src/Mindee/Parsing/Table/StringExtensions.cs | 215 +++ src/Mindee/Parsing/Table/Style.cs | 210 +++ src/Mindee/Parsing/Table/Table.cs | 169 ++ src/Mindee/Parsing/Table/TableAccessor.cs | 20 + src/Mindee/Parsing/Table/TableBorder.Known.cs | 12 + src/Mindee/Parsing/Table/TableBorder.cs | 102 ++ src/Mindee/Parsing/Table/TableBorderPart.cs | 117 ++ src/Mindee/Parsing/Table/TableColumn.cs | 64 + src/Mindee/Parsing/Table/TableMeasurer.cs | 158 ++ src/Mindee/Parsing/Table/TablePart.cs | 27 + src/Mindee/Parsing/Table/TableRenderer.cs | 179 +++ .../Parsing/Table/TableRendererContext.cs | 58 + src/Mindee/Parsing/Table/TableRow.cs | 79 + .../Parsing/Table/TableRowCollection.cs | 206 +++ .../Parsing/Table/TableRowEnumerator.cs | 34 + src/Mindee/Parsing/Table/TableTitle.cs | 57 + 33 files changed, 4617 insertions(+) create mode 100644 src/Mindee/Parsing/Table/AsciiDoubleHeadTableBorder.cs create mode 100644 src/Mindee/Parsing/Table/Color.Generated.cs create mode 100644 src/Mindee/Parsing/Table/Color.cs create mode 100644 src/Mindee/Parsing/Table/ColorPalette.cs create mode 100644 src/Mindee/Parsing/Table/ColorTable.cs create mode 100644 src/Mindee/Parsing/Table/IAlignable.cs create mode 100644 src/Mindee/Parsing/Table/IColumn.cs create mode 100644 src/Mindee/Parsing/Table/IExpandable.cs create mode 100644 src/Mindee/Parsing/Table/IHasBorder.cs create mode 100644 src/Mindee/Parsing/Table/IHasTableBorder.cs create mode 100644 src/Mindee/Parsing/Table/IRenderable.cs create mode 100644 src/Mindee/Parsing/Table/Justify.cs create mode 100644 src/Mindee/Parsing/Table/Measurement.cs create mode 100644 src/Mindee/Parsing/Table/Padding.cs create mode 100644 src/Mindee/Parsing/Table/RenderOptions.cs create mode 100644 src/Mindee/Parsing/Table/Renderable.cs create mode 100644 src/Mindee/Parsing/Table/Segment.cs create mode 100644 src/Mindee/Parsing/Table/StringExtensions.cs create mode 100644 src/Mindee/Parsing/Table/Style.cs create mode 100644 src/Mindee/Parsing/Table/Table.cs create mode 100644 src/Mindee/Parsing/Table/TableAccessor.cs create mode 100644 src/Mindee/Parsing/Table/TableBorder.Known.cs create mode 100644 src/Mindee/Parsing/Table/TableBorder.cs create mode 100644 src/Mindee/Parsing/Table/TableBorderPart.cs create mode 100644 src/Mindee/Parsing/Table/TableColumn.cs create mode 100644 src/Mindee/Parsing/Table/TableMeasurer.cs create mode 100644 src/Mindee/Parsing/Table/TablePart.cs create mode 100644 src/Mindee/Parsing/Table/TableRenderer.cs create mode 100644 src/Mindee/Parsing/Table/TableRendererContext.cs create mode 100644 src/Mindee/Parsing/Table/TableRow.cs create mode 100644 src/Mindee/Parsing/Table/TableRowCollection.cs create mode 100644 src/Mindee/Parsing/Table/TableRowEnumerator.cs create mode 100644 src/Mindee/Parsing/Table/TableTitle.cs diff --git a/src/Mindee/Parsing/Table/AsciiDoubleHeadTableBorder.cs b/src/Mindee/Parsing/Table/AsciiDoubleHeadTableBorder.cs new file mode 100644 index 00000000..367068dc --- /dev/null +++ b/src/Mindee/Parsing/Table/AsciiDoubleHeadTableBorder.cs @@ -0,0 +1,40 @@ +using System; + +namespace Spectre.Console; + +/// +/// Represents an old school ASCII border with a double header border. +/// +public sealed class AsciiDoubleHeadTableBorder : TableBorder +{ + /// + public override string GetPart(TableBorderPart part) + { + return part switch + { + TableBorderPart.HeaderTopLeft => "+", + TableBorderPart.HeaderTop => "-", + TableBorderPart.HeaderTopSeparator => "+", + TableBorderPart.HeaderTopRight => "+", + TableBorderPart.HeaderLeft => "|", + TableBorderPart.HeaderSeparator => "|", + TableBorderPart.HeaderRight => "|", + TableBorderPart.HeaderBottomLeft => "|", + TableBorderPart.HeaderBottom => "=", + TableBorderPart.HeaderBottomSeparator => "+", + TableBorderPart.HeaderBottomRight => "|", + TableBorderPart.CellLeft => "|", + TableBorderPart.CellSeparator => "|", + TableBorderPart.CellRight => "|", + TableBorderPart.FooterTopLeft => "+", + TableBorderPart.FooterTop => "-", + TableBorderPart.FooterTopSeparator => "+", + TableBorderPart.FooterTopRight => "+", + TableBorderPart.FooterBottomLeft => "+", + TableBorderPart.FooterBottom => "-", + TableBorderPart.FooterBottomSeparator => "+", + TableBorderPart.FooterBottomRight => "+", + _ => throw new InvalidOperationException("Unknown border part."), + }; + } +} diff --git a/src/Mindee/Parsing/Table/Color.Generated.cs b/src/Mindee/Parsing/Table/Color.Generated.cs new file mode 100644 index 00000000..2a8208fd --- /dev/null +++ b/src/Mindee/Parsing/Table/Color.Generated.cs @@ -0,0 +1,1360 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System.Diagnostics.CodeAnalysis; + +namespace Spectre.Console +{ + /// + /// Represents a color. + /// + public partial struct Color + { + internal Color(byte number, byte red, byte green, byte blue, bool isDefault = false) + : this(red, green, blue) + { + Number = number; + IsDefault = isDefault; + } + + /// + /// Gets the color "Black" (RGB 0,0,0). + /// + public static Color Black { get; } = new Color(0, 0, 0, 0); + + /// + /// Gets the color "Maroon" (RGB 128,0,0). + /// + public static Color Maroon { get; } = new Color(1, 128, 0, 0); + + /// + /// Gets the color "Green" (RGB 0,128,0). + /// + public static Color Green { get; } = new Color(2, 0, 128, 0); + + /// + /// Gets the color "Olive" (RGB 128,128,0). + /// + public static Color Olive { get; } = new Color(3, 128, 128, 0); + + /// + /// Gets the color "Navy" (RGB 0,0,128). + /// + public static Color Navy { get; } = new Color(4, 0, 0, 128); + + /// + /// Gets the color "Purple" (RGB 128,0,128). + /// + public static Color Purple { get; } = new Color(5, 128, 0, 128); + + /// + /// Gets the color "Teal" (RGB 0,128,128). + /// + public static Color Teal { get; } = new Color(6, 0, 128, 128); + + /// + /// Gets the color "Silver" (RGB 192,192,192). + /// + public static Color Silver { get; } = new Color(7, 192, 192, 192); + + /// + /// Gets the color "Grey" (RGB 128,128,128). + /// + public static Color Grey { get; } = new Color(8, 128, 128, 128); + + /// + /// Gets the color "Red" (RGB 255,0,0). + /// + public static Color Red { get; } = new Color(9, 255, 0, 0); + + /// + /// Gets the color "Lime" (RGB 0,255,0). + /// + public static Color Lime { get; } = new Color(10, 0, 255, 0); + + /// + /// Gets the color "Yellow" (RGB 255,255,0). + /// + public static Color Yellow { get; } = new Color(11, 255, 255, 0); + + /// + /// Gets the color "Blue" (RGB 0,0,255). + /// + public static Color Blue { get; } = new Color(12, 0, 0, 255); + + /// + /// Gets the color "Fuchsia" (RGB 255,0,255). + /// + public static Color Fuchsia { get; } = new Color(13, 255, 0, 255); + + /// + /// Gets the color "Aqua" (RGB 0,255,255). + /// + public static Color Aqua { get; } = new Color(14, 0, 255, 255); + + /// + /// Gets the color "White" (RGB 255,255,255). + /// + public static Color White { get; } = new Color(15, 255, 255, 255); + + /// + /// Gets the color "Grey0" (RGB 0,0,0). + /// + public static Color Grey0 { get; } = new Color(16, 0, 0, 0); + + /// + /// Gets the color "NavyBlue" (RGB 0,0,95). + /// + public static Color NavyBlue { get; } = new Color(17, 0, 0, 95); + + /// + /// Gets the color "DarkBlue" (RGB 0,0,135). + /// + public static Color DarkBlue { get; } = new Color(18, 0, 0, 135); + + /// + /// Gets the color "Blue3" (RGB 0,0,175). + /// + public static Color Blue3 { get; } = new Color(19, 0, 0, 175); + + /// + /// Gets the color "Blue3_1" (RGB 0,0,215). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Blue3_1 { get; } = new Color(20, 0, 0, 215); + + /// + /// Gets the color "Blue1" (RGB 0,0,255). + /// + public static Color Blue1 { get; } = new Color(21, 0, 0, 255); + + /// + /// Gets the color "DarkGreen" (RGB 0,95,0). + /// + public static Color DarkGreen { get; } = new Color(22, 0, 95, 0); + + /// + /// Gets the color "DeepSkyBlue4" (RGB 0,95,95). + /// + public static Color DeepSkyBlue4 { get; } = new Color(23, 0, 95, 95); + + /// + /// Gets the color "DeepSkyBlue4_1" (RGB 0,95,135). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DeepSkyBlue4_1 { get; } = new Color(24, 0, 95, 135); + + /// + /// Gets the color "DeepSkyBlue4_2" (RGB 0,95,175). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DeepSkyBlue4_2 { get; } = new Color(25, 0, 95, 175); + + /// + /// Gets the color "DodgerBlue3" (RGB 0,95,215). + /// + public static Color DodgerBlue3 { get; } = new Color(26, 0, 95, 215); + + /// + /// Gets the color "DodgerBlue2" (RGB 0,95,255). + /// + public static Color DodgerBlue2 { get; } = new Color(27, 0, 95, 255); + + /// + /// Gets the color "Green4" (RGB 0,135,0). + /// + public static Color Green4 { get; } = new Color(28, 0, 135, 0); + + /// + /// Gets the color "SpringGreen4" (RGB 0,135,95). + /// + public static Color SpringGreen4 { get; } = new Color(29, 0, 135, 95); + + /// + /// Gets the color "Turquoise4" (RGB 0,135,135). + /// + public static Color Turquoise4 { get; } = new Color(30, 0, 135, 135); + + /// + /// Gets the color "DeepSkyBlue3" (RGB 0,135,175). + /// + public static Color DeepSkyBlue3 { get; } = new Color(31, 0, 135, 175); + + /// + /// Gets the color "DeepSkyBlue3_1" (RGB 0,135,215). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DeepSkyBlue3_1 { get; } = new Color(32, 0, 135, 215); + + /// + /// Gets the color "DodgerBlue1" (RGB 0,135,255). + /// + public static Color DodgerBlue1 { get; } = new Color(33, 0, 135, 255); + + /// + /// Gets the color "Green3" (RGB 0,175,0). + /// + public static Color Green3 { get; } = new Color(34, 0, 175, 0); + + /// + /// Gets the color "SpringGreen3" (RGB 0,175,95). + /// + public static Color SpringGreen3 { get; } = new Color(35, 0, 175, 95); + + /// + /// Gets the color "DarkCyan" (RGB 0,175,135). + /// + public static Color DarkCyan { get; } = new Color(36, 0, 175, 135); + + /// + /// Gets the color "LightSeaGreen" (RGB 0,175,175). + /// + public static Color LightSeaGreen { get; } = new Color(37, 0, 175, 175); + + /// + /// Gets the color "DeepSkyBlue2" (RGB 0,175,215). + /// + public static Color DeepSkyBlue2 { get; } = new Color(38, 0, 175, 215); + + /// + /// Gets the color "DeepSkyBlue1" (RGB 0,175,255). + /// + public static Color DeepSkyBlue1 { get; } = new Color(39, 0, 175, 255); + + /// + /// Gets the color "Green3_1" (RGB 0,215,0). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Green3_1 { get; } = new Color(40, 0, 215, 0); + + /// + /// Gets the color "SpringGreen3_1" (RGB 0,215,95). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color SpringGreen3_1 { get; } = new Color(41, 0, 215, 95); + + /// + /// Gets the color "SpringGreen2" (RGB 0,215,135). + /// + public static Color SpringGreen2 { get; } = new Color(42, 0, 215, 135); + + /// + /// Gets the color "Cyan3" (RGB 0,215,175). + /// + public static Color Cyan3 { get; } = new Color(43, 0, 215, 175); + + /// + /// Gets the color "DarkTurquoise" (RGB 0,215,215). + /// + public static Color DarkTurquoise { get; } = new Color(44, 0, 215, 215); + + /// + /// Gets the color "Turquoise2" (RGB 0,215,255). + /// + public static Color Turquoise2 { get; } = new Color(45, 0, 215, 255); + + /// + /// Gets the color "Green1" (RGB 0,255,0). + /// + public static Color Green1 { get; } = new Color(46, 0, 255, 0); + + /// + /// Gets the color "SpringGreen2_1" (RGB 0,255,95). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color SpringGreen2_1 { get; } = new Color(47, 0, 255, 95); + + /// + /// Gets the color "SpringGreen1" (RGB 0,255,135). + /// + public static Color SpringGreen1 { get; } = new Color(48, 0, 255, 135); + + /// + /// Gets the color "MediumSpringGreen" (RGB 0,255,175). + /// + public static Color MediumSpringGreen { get; } = new Color(49, 0, 255, 175); + + /// + /// Gets the color "Cyan2" (RGB 0,255,215). + /// + public static Color Cyan2 { get; } = new Color(50, 0, 255, 215); + + /// + /// Gets the color "Cyan1" (RGB 0,255,255). + /// + public static Color Cyan1 { get; } = new Color(51, 0, 255, 255); + + /// + /// Gets the color "DarkRed" (RGB 95,0,0). + /// + public static Color DarkRed { get; } = new Color(52, 95, 0, 0); + + /// + /// Gets the color "DeepPink4" (RGB 95,0,95). + /// + public static Color DeepPink4 { get; } = new Color(53, 95, 0, 95); + + /// + /// Gets the color "Purple4" (RGB 95,0,135). + /// + public static Color Purple4 { get; } = new Color(54, 95, 0, 135); + + /// + /// Gets the color "Purple4_1" (RGB 95,0,175). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Purple4_1 { get; } = new Color(55, 95, 0, 175); + + /// + /// Gets the color "Purple3" (RGB 95,0,215). + /// + public static Color Purple3 { get; } = new Color(56, 95, 0, 215); + + /// + /// Gets the color "BlueViolet" (RGB 95,0,255). + /// + public static Color BlueViolet { get; } = new Color(57, 95, 0, 255); + + /// + /// Gets the color "Orange4" (RGB 95,95,0). + /// + public static Color Orange4 { get; } = new Color(58, 95, 95, 0); + + /// + /// Gets the color "Grey37" (RGB 95,95,95). + /// + public static Color Grey37 { get; } = new Color(59, 95, 95, 95); + + /// + /// Gets the color "MediumPurple4" (RGB 95,95,135). + /// + public static Color MediumPurple4 { get; } = new Color(60, 95, 95, 135); + + /// + /// Gets the color "SlateBlue3" (RGB 95,95,175). + /// + public static Color SlateBlue3 { get; } = new Color(61, 95, 95, 175); + + /// + /// Gets the color "SlateBlue3_1" (RGB 95,95,215). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color SlateBlue3_1 { get; } = new Color(62, 95, 95, 215); + + /// + /// Gets the color "RoyalBlue1" (RGB 95,95,255). + /// + public static Color RoyalBlue1 { get; } = new Color(63, 95, 95, 255); + + /// + /// Gets the color "Chartreuse4" (RGB 95,135,0). + /// + public static Color Chartreuse4 { get; } = new Color(64, 95, 135, 0); + + /// + /// Gets the color "DarkSeaGreen4" (RGB 95,135,95). + /// + public static Color DarkSeaGreen4 { get; } = new Color(65, 95, 135, 95); + + /// + /// Gets the color "PaleTurquoise4" (RGB 95,135,135). + /// + public static Color PaleTurquoise4 { get; } = new Color(66, 95, 135, 135); + + /// + /// Gets the color "SteelBlue" (RGB 95,135,175). + /// + public static Color SteelBlue { get; } = new Color(67, 95, 135, 175); + + /// + /// Gets the color "SteelBlue3" (RGB 95,135,215). + /// + public static Color SteelBlue3 { get; } = new Color(68, 95, 135, 215); + + /// + /// Gets the color "CornflowerBlue" (RGB 95,135,255). + /// + public static Color CornflowerBlue { get; } = new Color(69, 95, 135, 255); + + /// + /// Gets the color "Chartreuse3" (RGB 95,175,0). + /// + public static Color Chartreuse3 { get; } = new Color(70, 95, 175, 0); + + /// + /// Gets the color "DarkSeaGreen4_1" (RGB 95,175,95). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DarkSeaGreen4_1 { get; } = new Color(71, 95, 175, 95); + + /// + /// Gets the color "CadetBlue" (RGB 95,175,135). + /// + public static Color CadetBlue { get; } = new Color(72, 95, 175, 135); + + /// + /// Gets the color "CadetBlue_1" (RGB 95,175,175). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color CadetBlue_1 { get; } = new Color(73, 95, 175, 175); + + /// + /// Gets the color "SkyBlue3" (RGB 95,175,215). + /// + public static Color SkyBlue3 { get; } = new Color(74, 95, 175, 215); + + /// + /// Gets the color "SteelBlue1" (RGB 95,175,255). + /// + public static Color SteelBlue1 { get; } = new Color(75, 95, 175, 255); + + /// + /// Gets the color "Chartreuse3_1" (RGB 95,215,0). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Chartreuse3_1 { get; } = new Color(76, 95, 215, 0); + + /// + /// Gets the color "PaleGreen3" (RGB 95,215,95). + /// + public static Color PaleGreen3 { get; } = new Color(77, 95, 215, 95); + + /// + /// Gets the color "SeaGreen3" (RGB 95,215,135). + /// + public static Color SeaGreen3 { get; } = new Color(78, 95, 215, 135); + + /// + /// Gets the color "Aquamarine3" (RGB 95,215,175). + /// + public static Color Aquamarine3 { get; } = new Color(79, 95, 215, 175); + + /// + /// Gets the color "MediumTurquoise" (RGB 95,215,215). + /// + public static Color MediumTurquoise { get; } = new Color(80, 95, 215, 215); + + /// + /// Gets the color "SteelBlue1_1" (RGB 95,215,255). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color SteelBlue1_1 { get; } = new Color(81, 95, 215, 255); + + /// + /// Gets the color "Chartreuse2" (RGB 95,255,0). + /// + public static Color Chartreuse2 { get; } = new Color(82, 95, 255, 0); + + /// + /// Gets the color "SeaGreen2" (RGB 95,255,95). + /// + public static Color SeaGreen2 { get; } = new Color(83, 95, 255, 95); + + /// + /// Gets the color "SeaGreen1" (RGB 95,255,135). + /// + public static Color SeaGreen1 { get; } = new Color(84, 95, 255, 135); + + /// + /// Gets the color "SeaGreen1_1" (RGB 95,255,175). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color SeaGreen1_1 { get; } = new Color(85, 95, 255, 175); + + /// + /// Gets the color "Aquamarine1" (RGB 95,255,215). + /// + public static Color Aquamarine1 { get; } = new Color(86, 95, 255, 215); + + /// + /// Gets the color "DarkSlateGray2" (RGB 95,255,255). + /// + public static Color DarkSlateGray2 { get; } = new Color(87, 95, 255, 255); + + /// + /// Gets the color "DarkRed_1" (RGB 135,0,0). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DarkRed_1 { get; } = new Color(88, 135, 0, 0); + + /// + /// Gets the color "DeepPink4_1" (RGB 135,0,95). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DeepPink4_1 { get; } = new Color(89, 135, 0, 95); + + /// + /// Gets the color "DarkMagenta" (RGB 135,0,135). + /// + public static Color DarkMagenta { get; } = new Color(90, 135, 0, 135); + + /// + /// Gets the color "DarkMagenta_1" (RGB 135,0,175). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DarkMagenta_1 { get; } = new Color(91, 135, 0, 175); + + /// + /// Gets the color "DarkViolet" (RGB 135,0,215). + /// + public static Color DarkViolet { get; } = new Color(92, 135, 0, 215); + + /// + /// Gets the color "Purple_1" (RGB 135,0,255). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Purple_1 { get; } = new Color(93, 135, 0, 255); + + /// + /// Gets the color "Orange4_1" (RGB 135,95,0). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Orange4_1 { get; } = new Color(94, 135, 95, 0); + + /// + /// Gets the color "LightPink4" (RGB 135,95,95). + /// + public static Color LightPink4 { get; } = new Color(95, 135, 95, 95); + + /// + /// Gets the color "Plum4" (RGB 135,95,135). + /// + public static Color Plum4 { get; } = new Color(96, 135, 95, 135); + + /// + /// Gets the color "MediumPurple3" (RGB 135,95,175). + /// + public static Color MediumPurple3 { get; } = new Color(97, 135, 95, 175); + + /// + /// Gets the color "MediumPurple3_1" (RGB 135,95,215). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color MediumPurple3_1 { get; } = new Color(98, 135, 95, 215); + + /// + /// Gets the color "SlateBlue1" (RGB 135,95,255). + /// + public static Color SlateBlue1 { get; } = new Color(99, 135, 95, 255); + + /// + /// Gets the color "Yellow4" (RGB 135,135,0). + /// + public static Color Yellow4 { get; } = new Color(100, 135, 135, 0); + + /// + /// Gets the color "Wheat4" (RGB 135,135,95). + /// + public static Color Wheat4 { get; } = new Color(101, 135, 135, 95); + + /// + /// Gets the color "Grey53" (RGB 135,135,135). + /// + public static Color Grey53 { get; } = new Color(102, 135, 135, 135); + + /// + /// Gets the color "LightSlateGrey" (RGB 135,135,175). + /// + public static Color LightSlateGrey { get; } = new Color(103, 135, 135, 175); + + /// + /// Gets the color "MediumPurple" (RGB 135,135,215). + /// + public static Color MediumPurple { get; } = new Color(104, 135, 135, 215); + + /// + /// Gets the color "LightSlateBlue" (RGB 135,135,255). + /// + public static Color LightSlateBlue { get; } = new Color(105, 135, 135, 255); + + /// + /// Gets the color "Yellow4_1" (RGB 135,175,0). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Yellow4_1 { get; } = new Color(106, 135, 175, 0); + + /// + /// Gets the color "DarkOliveGreen3" (RGB 135,175,95). + /// + public static Color DarkOliveGreen3 { get; } = new Color(107, 135, 175, 95); + + /// + /// Gets the color "DarkSeaGreen" (RGB 135,175,135). + /// + public static Color DarkSeaGreen { get; } = new Color(108, 135, 175, 135); + + /// + /// Gets the color "LightSkyBlue3" (RGB 135,175,175). + /// + public static Color LightSkyBlue3 { get; } = new Color(109, 135, 175, 175); + + /// + /// Gets the color "LightSkyBlue3_1" (RGB 135,175,215). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color LightSkyBlue3_1 { get; } = new Color(110, 135, 175, 215); + + /// + /// Gets the color "SkyBlue2" (RGB 135,175,255). + /// + public static Color SkyBlue2 { get; } = new Color(111, 135, 175, 255); + + /// + /// Gets the color "Chartreuse2_1" (RGB 135,215,0). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Chartreuse2_1 { get; } = new Color(112, 135, 215, 0); + + /// + /// Gets the color "DarkOliveGreen3_1" (RGB 135,215,95). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DarkOliveGreen3_1 { get; } = new Color(113, 135, 215, 95); + + /// + /// Gets the color "PaleGreen3_1" (RGB 135,215,135). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color PaleGreen3_1 { get; } = new Color(114, 135, 215, 135); + + /// + /// Gets the color "DarkSeaGreen3" (RGB 135,215,175). + /// + public static Color DarkSeaGreen3 { get; } = new Color(115, 135, 215, 175); + + /// + /// Gets the color "DarkSlateGray3" (RGB 135,215,215). + /// + public static Color DarkSlateGray3 { get; } = new Color(116, 135, 215, 215); + + /// + /// Gets the color "SkyBlue1" (RGB 135,215,255). + /// + public static Color SkyBlue1 { get; } = new Color(117, 135, 215, 255); + + /// + /// Gets the color "Chartreuse1" (RGB 135,255,0). + /// + public static Color Chartreuse1 { get; } = new Color(118, 135, 255, 0); + + /// + /// Gets the color "LightGreen" (RGB 135,255,95). + /// + public static Color LightGreen { get; } = new Color(119, 135, 255, 95); + + /// + /// Gets the color "LightGreen_1" (RGB 135,255,135). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color LightGreen_1 { get; } = new Color(120, 135, 255, 135); + + /// + /// Gets the color "PaleGreen1" (RGB 135,255,175). + /// + public static Color PaleGreen1 { get; } = new Color(121, 135, 255, 175); + + /// + /// Gets the color "Aquamarine1_1" (RGB 135,255,215). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Aquamarine1_1 { get; } = new Color(122, 135, 255, 215); + + /// + /// Gets the color "DarkSlateGray1" (RGB 135,255,255). + /// + public static Color DarkSlateGray1 { get; } = new Color(123, 135, 255, 255); + + /// + /// Gets the color "Red3" (RGB 175,0,0). + /// + public static Color Red3 { get; } = new Color(124, 175, 0, 0); + + /// + /// Gets the color "DeepPink4_2" (RGB 175,0,95). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DeepPink4_2 { get; } = new Color(125, 175, 0, 95); + + /// + /// Gets the color "MediumVioletRed" (RGB 175,0,135). + /// + public static Color MediumVioletRed { get; } = new Color(126, 175, 0, 135); + + /// + /// Gets the color "Magenta3" (RGB 175,0,175). + /// + public static Color Magenta3 { get; } = new Color(127, 175, 0, 175); + + /// + /// Gets the color "DarkViolet_1" (RGB 175,0,215). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DarkViolet_1 { get; } = new Color(128, 175, 0, 215); + + /// + /// Gets the color "Purple_2" (RGB 175,0,255). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Purple_2 { get; } = new Color(129, 175, 0, 255); + + /// + /// Gets the color "DarkOrange3" (RGB 175,95,0). + /// + public static Color DarkOrange3 { get; } = new Color(130, 175, 95, 0); + + /// + /// Gets the color "IndianRed" (RGB 175,95,95). + /// + public static Color IndianRed { get; } = new Color(131, 175, 95, 95); + + /// + /// Gets the color "HotPink3" (RGB 175,95,135). + /// + public static Color HotPink3 { get; } = new Color(132, 175, 95, 135); + + /// + /// Gets the color "MediumOrchid3" (RGB 175,95,175). + /// + public static Color MediumOrchid3 { get; } = new Color(133, 175, 95, 175); + + /// + /// Gets the color "MediumOrchid" (RGB 175,95,215). + /// + public static Color MediumOrchid { get; } = new Color(134, 175, 95, 215); + + /// + /// Gets the color "MediumPurple2" (RGB 175,95,255). + /// + public static Color MediumPurple2 { get; } = new Color(135, 175, 95, 255); + + /// + /// Gets the color "DarkGoldenrod" (RGB 175,135,0). + /// + public static Color DarkGoldenrod { get; } = new Color(136, 175, 135, 0); + + /// + /// Gets the color "LightSalmon3" (RGB 175,135,95). + /// + public static Color LightSalmon3 { get; } = new Color(137, 175, 135, 95); + + /// + /// Gets the color "RosyBrown" (RGB 175,135,135). + /// + public static Color RosyBrown { get; } = new Color(138, 175, 135, 135); + + /// + /// Gets the color "Grey63" (RGB 175,135,175). + /// + public static Color Grey63 { get; } = new Color(139, 175, 135, 175); + + /// + /// Gets the color "MediumPurple2_1" (RGB 175,135,215). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color MediumPurple2_1 { get; } = new Color(140, 175, 135, 215); + + /// + /// Gets the color "MediumPurple1" (RGB 175,135,255). + /// + public static Color MediumPurple1 { get; } = new Color(141, 175, 135, 255); + + /// + /// Gets the color "Gold3" (RGB 175,175,0). + /// + public static Color Gold3 { get; } = new Color(142, 175, 175, 0); + + /// + /// Gets the color "DarkKhaki" (RGB 175,175,95). + /// + public static Color DarkKhaki { get; } = new Color(143, 175, 175, 95); + + /// + /// Gets the color "NavajoWhite3" (RGB 175,175,135). + /// + public static Color NavajoWhite3 { get; } = new Color(144, 175, 175, 135); + + /// + /// Gets the color "Grey69" (RGB 175,175,175). + /// + public static Color Grey69 { get; } = new Color(145, 175, 175, 175); + + /// + /// Gets the color "LightSteelBlue3" (RGB 175,175,215). + /// + public static Color LightSteelBlue3 { get; } = new Color(146, 175, 175, 215); + + /// + /// Gets the color "LightSteelBlue" (RGB 175,175,255). + /// + public static Color LightSteelBlue { get; } = new Color(147, 175, 175, 255); + + /// + /// Gets the color "Yellow3" (RGB 175,215,0). + /// + public static Color Yellow3 { get; } = new Color(148, 175, 215, 0); + + /// + /// Gets the color "DarkOliveGreen3_2" (RGB 175,215,95). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DarkOliveGreen3_2 { get; } = new Color(149, 175, 215, 95); + + /// + /// Gets the color "DarkSeaGreen3_1" (RGB 175,215,135). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DarkSeaGreen3_1 { get; } = new Color(150, 175, 215, 135); + + /// + /// Gets the color "DarkSeaGreen2" (RGB 175,215,175). + /// + public static Color DarkSeaGreen2 { get; } = new Color(151, 175, 215, 175); + + /// + /// Gets the color "LightCyan3" (RGB 175,215,215). + /// + public static Color LightCyan3 { get; } = new Color(152, 175, 215, 215); + + /// + /// Gets the color "LightSkyBlue1" (RGB 175,215,255). + /// + public static Color LightSkyBlue1 { get; } = new Color(153, 175, 215, 255); + + /// + /// Gets the color "GreenYellow" (RGB 175,255,0). + /// + public static Color GreenYellow { get; } = new Color(154, 175, 255, 0); + + /// + /// Gets the color "DarkOliveGreen2" (RGB 175,255,95). + /// + public static Color DarkOliveGreen2 { get; } = new Color(155, 175, 255, 95); + + /// + /// Gets the color "PaleGreen1_1" (RGB 175,255,135). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color PaleGreen1_1 { get; } = new Color(156, 175, 255, 135); + + /// + /// Gets the color "DarkSeaGreen2_1" (RGB 175,255,175). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DarkSeaGreen2_1 { get; } = new Color(157, 175, 255, 175); + + /// + /// Gets the color "DarkSeaGreen1" (RGB 175,255,215). + /// + public static Color DarkSeaGreen1 { get; } = new Color(158, 175, 255, 215); + + /// + /// Gets the color "PaleTurquoise1" (RGB 175,255,255). + /// + public static Color PaleTurquoise1 { get; } = new Color(159, 175, 255, 255); + + /// + /// Gets the color "Red3_1" (RGB 215,0,0). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Red3_1 { get; } = new Color(160, 215, 0, 0); + + /// + /// Gets the color "DeepPink3" (RGB 215,0,95). + /// + public static Color DeepPink3 { get; } = new Color(161, 215, 0, 95); + + /// + /// Gets the color "DeepPink3_1" (RGB 215,0,135). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DeepPink3_1 { get; } = new Color(162, 215, 0, 135); + + /// + /// Gets the color "Magenta3_1" (RGB 215,0,175). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Magenta3_1 { get; } = new Color(163, 215, 0, 175); + + /// + /// Gets the color "Magenta3_2" (RGB 215,0,215). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Magenta3_2 { get; } = new Color(164, 215, 0, 215); + + /// + /// Gets the color "Magenta2" (RGB 215,0,255). + /// + public static Color Magenta2 { get; } = new Color(165, 215, 0, 255); + + /// + /// Gets the color "DarkOrange3_1" (RGB 215,95,0). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DarkOrange3_1 { get; } = new Color(166, 215, 95, 0); + + /// + /// Gets the color "IndianRed_1" (RGB 215,95,95). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color IndianRed_1 { get; } = new Color(167, 215, 95, 95); + + /// + /// Gets the color "HotPink3_1" (RGB 215,95,135). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color HotPink3_1 { get; } = new Color(168, 215, 95, 135); + + /// + /// Gets the color "HotPink2" (RGB 215,95,175). + /// + public static Color HotPink2 { get; } = new Color(169, 215, 95, 175); + + /// + /// Gets the color "Orchid" (RGB 215,95,215). + /// + public static Color Orchid { get; } = new Color(170, 215, 95, 215); + + /// + /// Gets the color "MediumOrchid1" (RGB 215,95,255). + /// + public static Color MediumOrchid1 { get; } = new Color(171, 215, 95, 255); + + /// + /// Gets the color "Orange3" (RGB 215,135,0). + /// + public static Color Orange3 { get; } = new Color(172, 215, 135, 0); + + /// + /// Gets the color "LightSalmon3_1" (RGB 215,135,95). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color LightSalmon3_1 { get; } = new Color(173, 215, 135, 95); + + /// + /// Gets the color "LightPink3" (RGB 215,135,135). + /// + public static Color LightPink3 { get; } = new Color(174, 215, 135, 135); + + /// + /// Gets the color "Pink3" (RGB 215,135,175). + /// + public static Color Pink3 { get; } = new Color(175, 215, 135, 175); + + /// + /// Gets the color "Plum3" (RGB 215,135,215). + /// + public static Color Plum3 { get; } = new Color(176, 215, 135, 215); + + /// + /// Gets the color "Violet" (RGB 215,135,255). + /// + public static Color Violet { get; } = new Color(177, 215, 135, 255); + + /// + /// Gets the color "Gold3_1" (RGB 215,175,0). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Gold3_1 { get; } = new Color(178, 215, 175, 0); + + /// + /// Gets the color "LightGoldenrod3" (RGB 215,175,95). + /// + public static Color LightGoldenrod3 { get; } = new Color(179, 215, 175, 95); + + /// + /// Gets the color "Tan" (RGB 215,175,135). + /// + public static Color Tan { get; } = new Color(180, 215, 175, 135); + + /// + /// Gets the color "MistyRose3" (RGB 215,175,175). + /// + public static Color MistyRose3 { get; } = new Color(181, 215, 175, 175); + + /// + /// Gets the color "Thistle3" (RGB 215,175,215). + /// + public static Color Thistle3 { get; } = new Color(182, 215, 175, 215); + + /// + /// Gets the color "Plum2" (RGB 215,175,255). + /// + public static Color Plum2 { get; } = new Color(183, 215, 175, 255); + + /// + /// Gets the color "Yellow3_1" (RGB 215,215,0). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Yellow3_1 { get; } = new Color(184, 215, 215, 0); + + /// + /// Gets the color "Khaki3" (RGB 215,215,95). + /// + public static Color Khaki3 { get; } = new Color(185, 215, 215, 95); + + /// + /// Gets the color "LightGoldenrod2" (RGB 215,215,135). + /// + public static Color LightGoldenrod2 { get; } = new Color(186, 215, 215, 135); + + /// + /// Gets the color "LightYellow3" (RGB 215,215,175). + /// + public static Color LightYellow3 { get; } = new Color(187, 215, 215, 175); + + /// + /// Gets the color "Grey84" (RGB 215,215,215). + /// + public static Color Grey84 { get; } = new Color(188, 215, 215, 215); + + /// + /// Gets the color "LightSteelBlue1" (RGB 215,215,255). + /// + public static Color LightSteelBlue1 { get; } = new Color(189, 215, 215, 255); + + /// + /// Gets the color "Yellow2" (RGB 215,255,0). + /// + public static Color Yellow2 { get; } = new Color(190, 215, 255, 0); + + /// + /// Gets the color "DarkOliveGreen1" (RGB 215,255,95). + /// + public static Color DarkOliveGreen1 { get; } = new Color(191, 215, 255, 95); + + /// + /// Gets the color "DarkOliveGreen1_1" (RGB 215,255,135). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DarkOliveGreen1_1 { get; } = new Color(192, 215, 255, 135); + + /// + /// Gets the color "DarkSeaGreen1_1" (RGB 215,255,175). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DarkSeaGreen1_1 { get; } = new Color(193, 215, 255, 175); + + /// + /// Gets the color "Honeydew2" (RGB 215,255,215). + /// + public static Color Honeydew2 { get; } = new Color(194, 215, 255, 215); + + /// + /// Gets the color "LightCyan1" (RGB 215,255,255). + /// + public static Color LightCyan1 { get; } = new Color(195, 215, 255, 255); + + /// + /// Gets the color "Red1" (RGB 255,0,0). + /// + public static Color Red1 { get; } = new Color(196, 255, 0, 0); + + /// + /// Gets the color "DeepPink2" (RGB 255,0,95). + /// + public static Color DeepPink2 { get; } = new Color(197, 255, 0, 95); + + /// + /// Gets the color "DeepPink1" (RGB 255,0,135). + /// + public static Color DeepPink1 { get; } = new Color(198, 255, 0, 135); + + /// + /// Gets the color "DeepPink1_1" (RGB 255,0,175). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color DeepPink1_1 { get; } = new Color(199, 255, 0, 175); + + /// + /// Gets the color "Magenta2_1" (RGB 255,0,215). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color Magenta2_1 { get; } = new Color(200, 255, 0, 215); + + /// + /// Gets the color "Magenta1" (RGB 255,0,255). + /// + public static Color Magenta1 { get; } = new Color(201, 255, 0, 255); + + /// + /// Gets the color "OrangeRed1" (RGB 255,95,0). + /// + public static Color OrangeRed1 { get; } = new Color(202, 255, 95, 0); + + /// + /// Gets the color "IndianRed1" (RGB 255,95,95). + /// + public static Color IndianRed1 { get; } = new Color(203, 255, 95, 95); + + /// + /// Gets the color "IndianRed1_1" (RGB 255,95,135). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color IndianRed1_1 { get; } = new Color(204, 255, 95, 135); + + /// + /// Gets the color "HotPink" (RGB 255,95,175). + /// + public static Color HotPink { get; } = new Color(205, 255, 95, 175); + + /// + /// Gets the color "HotPink_1" (RGB 255,95,215). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color HotPink_1 { get; } = new Color(206, 255, 95, 215); + + /// + /// Gets the color "MediumOrchid1_1" (RGB 255,95,255). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color MediumOrchid1_1 { get; } = new Color(207, 255, 95, 255); + + /// + /// Gets the color "DarkOrange" (RGB 255,135,0). + /// + public static Color DarkOrange { get; } = new Color(208, 255, 135, 0); + + /// + /// Gets the color "Salmon1" (RGB 255,135,95). + /// + public static Color Salmon1 { get; } = new Color(209, 255, 135, 95); + + /// + /// Gets the color "LightCoral" (RGB 255,135,135). + /// + public static Color LightCoral { get; } = new Color(210, 255, 135, 135); + + /// + /// Gets the color "PaleVioletRed1" (RGB 255,135,175). + /// + public static Color PaleVioletRed1 { get; } = new Color(211, 255, 135, 175); + + /// + /// Gets the color "Orchid2" (RGB 255,135,215). + /// + public static Color Orchid2 { get; } = new Color(212, 255, 135, 215); + + /// + /// Gets the color "Orchid1" (RGB 255,135,255). + /// + public static Color Orchid1 { get; } = new Color(213, 255, 135, 255); + + /// + /// Gets the color "Orange1" (RGB 255,175,0). + /// + public static Color Orange1 { get; } = new Color(214, 255, 175, 0); + + /// + /// Gets the color "SandyBrown" (RGB 255,175,95). + /// + public static Color SandyBrown { get; } = new Color(215, 255, 175, 95); + + /// + /// Gets the color "LightSalmon1" (RGB 255,175,135). + /// + public static Color LightSalmon1 { get; } = new Color(216, 255, 175, 135); + + /// + /// Gets the color "LightPink1" (RGB 255,175,175). + /// + public static Color LightPink1 { get; } = new Color(217, 255, 175, 175); + + /// + /// Gets the color "Pink1" (RGB 255,175,215). + /// + public static Color Pink1 { get; } = new Color(218, 255, 175, 215); + + /// + /// Gets the color "Plum1" (RGB 255,175,255). + /// + public static Color Plum1 { get; } = new Color(219, 255, 175, 255); + + /// + /// Gets the color "Gold1" (RGB 255,215,0). + /// + public static Color Gold1 { get; } = new Color(220, 255, 215, 0); + + /// + /// Gets the color "LightGoldenrod2_1" (RGB 255,215,95). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color LightGoldenrod2_1 { get; } = new Color(221, 255, 215, 95); + + /// + /// Gets the color "LightGoldenrod2_2" (RGB 255,215,135). + /// + [SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")] + public static Color LightGoldenrod2_2 { get; } = new Color(222, 255, 215, 135); + + /// + /// Gets the color "NavajoWhite1" (RGB 255,215,175). + /// + public static Color NavajoWhite1 { get; } = new Color(223, 255, 215, 175); + + /// + /// Gets the color "MistyRose1" (RGB 255,215,215). + /// + public static Color MistyRose1 { get; } = new Color(224, 255, 215, 215); + + /// + /// Gets the color "Thistle1" (RGB 255,215,255). + /// + public static Color Thistle1 { get; } = new Color(225, 255, 215, 255); + + /// + /// Gets the color "Yellow1" (RGB 255,255,0). + /// + public static Color Yellow1 { get; } = new Color(226, 255, 255, 0); + + /// + /// Gets the color "LightGoldenrod1" (RGB 255,255,95). + /// + public static Color LightGoldenrod1 { get; } = new Color(227, 255, 255, 95); + + /// + /// Gets the color "Khaki1" (RGB 255,255,135). + /// + public static Color Khaki1 { get; } = new Color(228, 255, 255, 135); + + /// + /// Gets the color "Wheat1" (RGB 255,255,175). + /// + public static Color Wheat1 { get; } = new Color(229, 255, 255, 175); + + /// + /// Gets the color "Cornsilk1" (RGB 255,255,215). + /// + public static Color Cornsilk1 { get; } = new Color(230, 255, 255, 215); + + /// + /// Gets the color "Grey100" (RGB 255,255,255). + /// + public static Color Grey100 { get; } = new Color(231, 255, 255, 255); + + /// + /// Gets the color "Grey3" (RGB 8,8,8). + /// + public static Color Grey3 { get; } = new Color(232, 8, 8, 8); + + /// + /// Gets the color "Grey7" (RGB 18,18,18). + /// + public static Color Grey7 { get; } = new Color(233, 18, 18, 18); + + /// + /// Gets the color "Grey11" (RGB 28,28,28). + /// + public static Color Grey11 { get; } = new Color(234, 28, 28, 28); + + /// + /// Gets the color "Grey15" (RGB 38,38,38). + /// + public static Color Grey15 { get; } = new Color(235, 38, 38, 38); + + /// + /// Gets the color "Grey19" (RGB 48,48,48). + /// + public static Color Grey19 { get; } = new Color(236, 48, 48, 48); + + /// + /// Gets the color "Grey23" (RGB 58,58,58). + /// + public static Color Grey23 { get; } = new Color(237, 58, 58, 58); + + /// + /// Gets the color "Grey27" (RGB 68,68,68). + /// + public static Color Grey27 { get; } = new Color(238, 68, 68, 68); + + /// + /// Gets the color "Grey30" (RGB 78,78,78). + /// + public static Color Grey30 { get; } = new Color(239, 78, 78, 78); + + /// + /// Gets the color "Grey35" (RGB 88,88,88). + /// + public static Color Grey35 { get; } = new Color(240, 88, 88, 88); + + /// + /// Gets the color "Grey39" (RGB 98,98,98). + /// + public static Color Grey39 { get; } = new Color(241, 98, 98, 98); + + /// + /// Gets the color "Grey42" (RGB 108,108,108). + /// + public static Color Grey42 { get; } = new Color(242, 108, 108, 108); + + /// + /// Gets the color "Grey46" (RGB 118,118,118). + /// + public static Color Grey46 { get; } = new Color(243, 118, 118, 118); + + /// + /// Gets the color "Grey50" (RGB 128,128,128). + /// + public static Color Grey50 { get; } = new Color(244, 128, 128, 128); + + /// + /// Gets the color "Grey54" (RGB 138,138,138). + /// + public static Color Grey54 { get; } = new Color(245, 138, 138, 138); + + /// + /// Gets the color "Grey58" (RGB 148,148,148). + /// + public static Color Grey58 { get; } = new Color(246, 148, 148, 148); + + /// + /// Gets the color "Grey62" (RGB 158,158,158). + /// + public static Color Grey62 { get; } = new Color(247, 158, 158, 158); + + /// + /// Gets the color "Grey66" (RGB 168,168,168). + /// + public static Color Grey66 { get; } = new Color(248, 168, 168, 168); + + /// + /// Gets the color "Grey70" (RGB 178,178,178). + /// + public static Color Grey70 { get; } = new Color(249, 178, 178, 178); + + /// + /// Gets the color "Grey74" (RGB 188,188,188). + /// + public static Color Grey74 { get; } = new Color(250, 188, 188, 188); + + /// + /// Gets the color "Grey78" (RGB 198,198,198). + /// + public static Color Grey78 { get; } = new Color(251, 198, 198, 198); + + /// + /// Gets the color "Grey82" (RGB 208,208,208). + /// + public static Color Grey82 { get; } = new Color(252, 208, 208, 208); + + /// + /// Gets the color "Grey85" (RGB 218,218,218). + /// + public static Color Grey85 { get; } = new Color(253, 218, 218, 218); + + /// + /// Gets the color "Grey89" (RGB 228,228,228). + /// + public static Color Grey89 { get; } = new Color(254, 228, 228, 228); + + /// + /// Gets the color "Grey93" (RGB 238,238,238). + /// + public static Color Grey93 { get; } = new Color(255, 238, 238, 238); + } +} \ No newline at end of file diff --git a/src/Mindee/Parsing/Table/Color.cs b/src/Mindee/Parsing/Table/Color.cs new file mode 100644 index 00000000..fb68b6ad --- /dev/null +++ b/src/Mindee/Parsing/Table/Color.cs @@ -0,0 +1,292 @@ +using System; +using System.Diagnostics; +using System.Drawing.Imaging; +using System.Globalization; + +namespace Spectre.Console; + +/// +/// Represents a color. +/// +public partial struct Color : IEquatable +{ + /// + /// Gets the default color. + /// + public static Color Default { get; } + + static Color() + { + Default = new Color(0, 0, 0, 0, true); + } + + /// + /// Gets the red component. + /// + public byte R { get; } + + /// + /// Gets the green component. + /// + public byte G { get; } + + /// + /// Gets the blue component. + /// + public byte B { get; } + + /// + /// Gets the number of the color, if any. + /// + internal byte? Number { get; } + + /// + /// Gets a value indicating whether or not this is the default color. + /// + internal bool IsDefault { get; } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + public Color(byte red, byte green, byte blue) + { + R = red; + G = green; + B = blue; + IsDefault = false; + Number = null; + } + + /// + /// Blends two colors. + /// + /// The other color. + /// The blend factor. + /// The resulting color. + public Color Blend(Color other, float factor) + { + // https://github.com/willmcgugan/rich/blob/f092b1d04252e6f6812021c0f415dd1d7be6a16a/rich/color.py#L494 + return new Color( + (byte)(R + ((other.R - R) * factor)), + (byte)(G + ((other.G - G) * factor)), + (byte)(B + ((other.B - B) * factor))); + } + + /// + /// Gets the hexadecimal representation of the color. + /// + /// The hexadecimal representation of the color. + public string ToHex() + { + return string.Format( + CultureInfo.InvariantCulture, + "{0}{1}{2}", + R.ToString("X2", CultureInfo.InvariantCulture), + G.ToString("X2", CultureInfo.InvariantCulture), + B.ToString("X2", CultureInfo.InvariantCulture)); + } + + /// + public override int GetHashCode() + { + unchecked + { + var hash = (int)2166136261; + hash = (hash * 16777619) ^ R.GetHashCode(); + hash = (hash * 16777619) ^ G.GetHashCode(); + hash = (hash * 16777619) ^ B.GetHashCode(); + return hash; + } + } + + /// + public override bool Equals(object? obj) + { + return obj is Color color && Equals(color); + } + + /// + public bool Equals(Color other) + { + return (IsDefault && other.IsDefault) || + (IsDefault == other.IsDefault && R == other.R && G == other.G && B == other.B); + } + + /// + /// Checks if two instances are equal. + /// + /// The first color instance to compare. + /// The second color instance to compare. + /// true if the two colors are equal, otherwise false. + public static bool operator ==(Color left, Color right) + { + return left.Equals(right); + } + + /// + /// Checks if two instances are not equal. + /// + /// The first color instance to compare. + /// The second color instance to compare. + /// true if the two colors are not equal, otherwise false. + public static bool operator !=(Color left, Color right) + { + return !(left == right); + } + + /// + /// Converts a to a . + /// + /// The color number to convert. + public static implicit operator Color(int number) + { + return FromInt32(number); + } + + /// + /// Converts a to a . + /// + /// The color to convert. + public static implicit operator Color(ConsoleColor color) + { + return FromConsoleColor(color); + } + + /// + /// Converts a to a . + /// + /// The console color to convert. + public static implicit operator ConsoleColor(Color color) + { + return ToConsoleColor(color); + } + + /// + /// Converts a to a . + /// + /// The color to convert. + /// A representing the . + public static ConsoleColor ToConsoleColor(Color color) + { + if (color.IsDefault) + { + return (ConsoleColor)(-1); + } + + if (color.Number == null || color.Number.Value >= 16) + { + color = ColorPalette.ExactOrClosest(ColorSystem.Standard, color); + } + + // Should not happen, but this will make things easier if we mess things up... + Debug.Assert( + color.Number >= 0 && color.Number < 16, + "Color does not fall inside the standard palette range."); + + return color.Number.Value switch + { + 0 => ConsoleColor.Black, + 1 => ConsoleColor.DarkRed, + 2 => ConsoleColor.DarkGreen, + 3 => ConsoleColor.DarkYellow, + 4 => ConsoleColor.DarkBlue, + 5 => ConsoleColor.DarkMagenta, + 6 => ConsoleColor.DarkCyan, + 7 => ConsoleColor.Gray, + 8 => ConsoleColor.DarkGray, + 9 => ConsoleColor.Red, + 10 => ConsoleColor.Green, + 11 => ConsoleColor.Yellow, + 12 => ConsoleColor.Blue, + 13 => ConsoleColor.Magenta, + 14 => ConsoleColor.Cyan, + 15 => ConsoleColor.White, + _ => throw new InvalidOperationException("Cannot convert color to console color."), + }; + } + + /// + /// Converts a color number into a . + /// + /// The color number. + /// The color representing the specified color number. + public static Color FromInt32(int number) + { + return ColorTable.GetColor(number); + } + + /// + /// Converts a to a . + /// + /// The color to convert. + /// A representing the . + public static Color FromConsoleColor(ConsoleColor color) + { + return color switch + { + ConsoleColor.Black => Black, + ConsoleColor.Blue => Blue, + ConsoleColor.Cyan => Aqua, + ConsoleColor.DarkBlue => Navy, + ConsoleColor.DarkCyan => Teal, + ConsoleColor.DarkGray => Grey, + ConsoleColor.DarkGreen => Green, + ConsoleColor.DarkMagenta => Purple, + ConsoleColor.DarkRed => Maroon, + ConsoleColor.DarkYellow => Olive, + ConsoleColor.Gray => Silver, + ConsoleColor.Green => Lime, + ConsoleColor.Magenta => Fuchsia, + ConsoleColor.Red => Red, + ConsoleColor.White => White, + ConsoleColor.Yellow => Yellow, + _ => Default, + }; + } + + /// + /// Converts the color to a markup string. + /// + /// A representing the color as markup. + public string ToMarkup() + { + if (IsDefault) + { + return "default"; + } + + if (Number != null) + { + var name = ColorTable.GetName(Number.Value); + if (!string.IsNullOrWhiteSpace(name)) + { + return name; + } + } + + return string.Format(CultureInfo.InvariantCulture, "#{0:X2}{1:X2}{2:X2}", R, G, B); + } + + /// + public override string ToString() + { + if (IsDefault) + { + return "default"; + } + + if (Number != null) + { + var name = ColorTable.GetName(Number.Value); + if (!string.IsNullOrWhiteSpace(name)) + { + return name; + } + } + + return string.Format(CultureInfo.InvariantCulture, "#{0:X2}{1:X2}{2:X2} (RGB={0},{1},{2})", R, G, B); + } +} diff --git a/src/Mindee/Parsing/Table/ColorPalette.cs b/src/Mindee/Parsing/Table/ColorPalette.cs new file mode 100644 index 00000000..3e5e835a --- /dev/null +++ b/src/Mindee/Parsing/Table/ColorPalette.cs @@ -0,0 +1,76 @@ +namespace Spectre.Console; + +internal static partial class ColorPalette +{ + public static IReadOnlyList Legacy { get; } + public static IReadOnlyList Standard { get; } + public static IReadOnlyList EightBit { get; } + + static ColorPalette() + { + Legacy = GenerateLegacyPalette(); + Standard = GenerateStandardPalette(Legacy); + EightBit = GenerateEightBitPalette(Standard); + } + + internal static Color ExactOrClosest(ColorSystem system, Color color) + { + var exact = Exact(system, color); + return exact ?? Closest(system, color); + } + + private static Color? Exact(ColorSystem system, Color color) + { + if (system == ColorSystem.TrueColor) + { + return color; + } + + var palette = system switch + { + ColorSystem.Legacy => Legacy, + ColorSystem.Standard => Standard, + ColorSystem.EightBit => EightBit, + _ => throw new NotSupportedException(), + }; + + return palette + .Where(c => c.Equals(color)) + .Cast() + .FirstOrDefault(); + } + + private static Color Closest(ColorSystem system, Color color) + { + if (system == ColorSystem.TrueColor) + { + return color; + } + + var palette = system switch + { + ColorSystem.Legacy => Legacy, + ColorSystem.Standard => Standard, + ColorSystem.EightBit => EightBit, + _ => throw new NotSupportedException(), + }; + + // https://stackoverflow.com/a/9085524 + static double Distance(Color first, Color second) + { + var rmean = ((float)first.R + second.R) / 2; + var r = first.R - second.R; + var g = first.G - second.G; + var b = first.B - second.B; + return Math.Sqrt( + ((int)((512 + rmean) * r * r) >> 8) + + (4 * g * g) + + ((int)((767 - rmean) * b * b) >> 8)); + } + + return Enumerable.Range(0, int.MaxValue) + .Zip(palette, (id, other) => (Distance: Distance(other, color), Id: id, Color: other)) + .OrderBy(x => x.Distance) + .FirstOrDefault().Color; + } +} \ No newline at end of file diff --git a/src/Mindee/Parsing/Table/ColorTable.cs b/src/Mindee/Parsing/Table/ColorTable.cs new file mode 100644 index 00000000..6af78af7 --- /dev/null +++ b/src/Mindee/Parsing/Table/ColorTable.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; + +namespace Spectre.Console; + +internal static partial class ColorTable +{ + private static readonly Dictionary _nameLookup; + private static readonly Dictionary _numberLookup; + + static ColorTable() + { + _numberLookup = GenerateTable(); + _nameLookup = new Dictionary(); + foreach (var pair in _numberLookup) + { + if (_nameLookup.ContainsKey(pair.Value)) + { + continue; + } + + _nameLookup.Add(pair.Value, pair.Key); + } + } + + public static Color GetColor(int number) + { + if (number < 0 || number > 255) + { + throw new InvalidOperationException("Color number must be between 0 and 255"); + } + + return ColorPalette.EightBit[number]; + } + + public static Color? GetColor(string name) + { + if (!_numberLookup.TryGetValue(name, out var number)) + { + return null; + } + + if (number > ColorPalette.EightBit.Count - 1) + { + return null; + } + + return ColorPalette.EightBit[number]; + } + + public static string? GetName(int number) + { + _nameLookup.TryGetValue(number, out var name); + return name; + } +} diff --git a/src/Mindee/Parsing/Table/IAlignable.cs b/src/Mindee/Parsing/Table/IAlignable.cs new file mode 100644 index 00000000..6207bfa1 --- /dev/null +++ b/src/Mindee/Parsing/Table/IAlignable.cs @@ -0,0 +1,12 @@ +namespace Spectre.Console; + +/// +/// Represents something that is alignable. +/// +public interface IAlignable +{ + /// + /// Gets or sets the alignment. + /// + Justify? Alignment { get; set; } +} diff --git a/src/Mindee/Parsing/Table/IColumn.cs b/src/Mindee/Parsing/Table/IColumn.cs new file mode 100644 index 00000000..6d2c784e --- /dev/null +++ b/src/Mindee/Parsing/Table/IColumn.cs @@ -0,0 +1,18 @@ +namespace Spectre.Console; + +/// +/// Represents a column. +/// +public interface IColumn : IAlignable, IPaddable +{ + /// + /// Gets or sets a value indicating whether + /// or not wrapping should be prevented. + /// + bool NoWrap { get; set; } + + /// + /// Gets or sets the width of the column. + /// + int? Width { get; set; } +} \ No newline at end of file diff --git a/src/Mindee/Parsing/Table/IExpandable.cs b/src/Mindee/Parsing/Table/IExpandable.cs new file mode 100644 index 00000000..da00f964 --- /dev/null +++ b/src/Mindee/Parsing/Table/IExpandable.cs @@ -0,0 +1,14 @@ +namespace Spectre.Console; + +/// +/// Represents something that is expandable. +/// +public interface IExpandable +{ + /// + /// Gets or sets a value indicating whether or not the object should + /// expand to the available space. If false, the object's + /// width will be auto calculated. + /// + bool Expand { get; set; } +} \ No newline at end of file diff --git a/src/Mindee/Parsing/Table/IHasBorder.cs b/src/Mindee/Parsing/Table/IHasBorder.cs new file mode 100644 index 00000000..e76cadf3 --- /dev/null +++ b/src/Mindee/Parsing/Table/IHasBorder.cs @@ -0,0 +1,19 @@ +namespace Spectre.Console; + +/// +/// Represents something that has a border. +/// +public interface IHasBorder +{ + /// + /// Gets or sets a value indicating whether or not to use + /// a "safe" border on legacy consoles that might not be able + /// to render non-ASCII characters. + /// + bool UseSafeBorder { get; set; } + + /// + /// Gets or sets the box style. + /// + public Style? BorderStyle { get; set; } +} diff --git a/src/Mindee/Parsing/Table/IHasTableBorder.cs b/src/Mindee/Parsing/Table/IHasTableBorder.cs new file mode 100644 index 00000000..f9cd7b4c --- /dev/null +++ b/src/Mindee/Parsing/Table/IHasTableBorder.cs @@ -0,0 +1,12 @@ +namespace Spectre.Console; + +/// +/// Represents something that has a border. +/// +public interface IHasTableBorder : IHasBorder +{ + /// + /// Gets or sets the border. + /// + public TableBorder Border { get; set; } +} \ No newline at end of file diff --git a/src/Mindee/Parsing/Table/IRenderable.cs b/src/Mindee/Parsing/Table/IRenderable.cs new file mode 100644 index 00000000..47512acb --- /dev/null +++ b/src/Mindee/Parsing/Table/IRenderable.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Diagnostics.Metrics; + +namespace Spectre.Console; + +/// +/// Represents something that can be rendered to the console. +/// +public interface IRenderable +{ + /// + /// Measures the renderable object. + /// + /// The render options. + /// The maximum allowed width. + /// The minimum and maximum width of the object. + Measurement<> Measure(RenderOptions options, int maxWidth); + + /// + /// Renders the object. + /// + /// The render options. + /// The maximum allowed width. + /// A collection of segments. + IEnumerable Render(RenderOptions options, int maxWidth); +} diff --git a/src/Mindee/Parsing/Table/Justify.cs b/src/Mindee/Parsing/Table/Justify.cs new file mode 100644 index 00000000..a4578de7 --- /dev/null +++ b/src/Mindee/Parsing/Table/Justify.cs @@ -0,0 +1,22 @@ +namespace Spectre.Console; + +/// +/// Represents text justification. +/// +public enum Justify +{ + /// + /// Left justified. + /// + Left = 0, + + /// + /// Right justified. + /// + Right = 1, + + /// + /// Centered. + /// + Center = 2, +} \ No newline at end of file diff --git a/src/Mindee/Parsing/Table/Measurement.cs b/src/Mindee/Parsing/Table/Measurement.cs new file mode 100644 index 00000000..63d3c466 --- /dev/null +++ b/src/Mindee/Parsing/Table/Measurement.cs @@ -0,0 +1,76 @@ +using System; + +namespace Spectre.Console; + +/// +/// Represents a measurement. +/// +public struct Measurement : IEquatable +{ + /// + /// Gets the minimum width. + /// + public int Min { get; } + + /// + /// Gets the maximum width. + /// + public int Max { get; } + + /// + /// Initializes a new instance of the struct. + /// + /// The minimum width. + /// The maximum width. + public Measurement(int min, int max) + { + Min = min; + Max = max; + } + + /// + public override bool Equals(object? obj) + { + return obj is Measurement measurement && Equals(measurement); + } + + /// + public override int GetHashCode() + { + unchecked + { + var hash = (int)2166136261; + hash = (hash * 16777619) ^ Min.GetHashCode(); + hash = (hash * 16777619) ^ Max.GetHashCode(); + return hash; + } + } + + /// + public bool Equals(Measurement other) + { + return Min == other.Min && Max == other.Max; + } + + /// + /// Checks if two instances are equal. + /// + /// The first measurement instance to compare. + /// The second measurement instance to compare. + /// true if the two measurements are equal, otherwise false. + public static bool operator ==(Measurement left, Measurement right) + { + return left.Equals(right); + } + + /// + /// Checks if two instances are not equal. + /// + /// The first measurement instance to compare. + /// The second measurement instance to compare. + /// true if the two measurements are not equal, otherwise false. + public static bool operator !=(Measurement left, Measurement right) + { + return !(left == right); + } +} diff --git a/src/Mindee/Parsing/Table/Padding.cs b/src/Mindee/Parsing/Table/Padding.cs new file mode 100644 index 00000000..0e083183 --- /dev/null +++ b/src/Mindee/Parsing/Table/Padding.cs @@ -0,0 +1,132 @@ +using System; + +namespace Spectre.Console; + +/// +/// Represents padding. +/// +public struct Padding : IEquatable +{ + /// + /// Gets the left padding. + /// + public int Left { get; } + + /// + /// Gets the top padding. + /// + public int Top { get; } + + /// + /// Gets the right padding. + /// + public int Right { get; } + + /// + /// Gets the bottom padding. + /// + public int Bottom { get; } + + /// + /// Initializes a new instance of the struct. + /// + /// The padding for all sides. + public Padding(int size) + : this(size, size, size, size) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The left and right padding. + /// The top and bottom padding. + public Padding(int horizontal, int vertical) + : this(horizontal, vertical, horizontal, vertical) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The left padding. + /// The top padding. + /// The right padding. + /// The bottom padding. + public Padding(int left, int top, int right, int bottom) + { + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } + + /// + public override bool Equals(object? obj) + { + return obj is Padding padding && Equals(padding); + } + + /// + public override int GetHashCode() + { + unchecked + { + var hash = (int)2166136261; + hash = (hash * 16777619) ^ Left.GetHashCode(); + hash = (hash * 16777619) ^ Top.GetHashCode(); + hash = (hash * 16777619) ^ Right.GetHashCode(); + hash = (hash * 16777619) ^ Bottom.GetHashCode(); + return hash; + } + } + + /// + public bool Equals(Padding other) + { + return Left == other.Left + && Top == other.Top + && Right == other.Right + && Bottom == other.Bottom; + } + + /// + /// Checks if two instances are equal. + /// + /// The first instance to compare. + /// The second instance to compare. + /// true if the two instances are equal, otherwise false. + public static bool operator ==(Padding left, Padding right) + { + return left.Equals(right); + } + + /// + /// Checks if two instances are not equal. + /// + /// The first instance to compare. + /// The second instance to compare. + /// true if the two instances are not equal, otherwise false. + public static bool operator !=(Padding left, Padding right) + { + return !(left == right); + } + + /// + /// Gets the padding width. + /// + /// The padding width. + public int GetWidth() + { + return Left + Right; + } + + /// + /// Gets the padding height. + /// + /// The padding height. + public int GetHeight() + { + return Top + Bottom; + } +} diff --git a/src/Mindee/Parsing/Table/RenderOptions.cs b/src/Mindee/Parsing/Table/RenderOptions.cs new file mode 100644 index 00000000..639d2015 --- /dev/null +++ b/src/Mindee/Parsing/Table/RenderOptions.cs @@ -0,0 +1,66 @@ +using System; +using System.Drawing; + +namespace Spectre.Console; + +/// +/// Represents render options. +/// +/// The capabilities. +/// The console size. +public record class RenderOptions(IReadOnlyCapabilities Capabilities, Size ConsoleSize) +{ + /// + /// Gets the current color system. + /// + public ColorSystem ColorSystem => Capabilities.ColorSystem; + + /// + /// Gets a value indicating whether or not VT/Ansi codes are supported. + /// + public bool Ansi => Capabilities.Ansi; + + /// + /// Gets a value indicating whether or not unicode is supported. + /// + public bool Unicode => Capabilities.Unicode; + + /// + /// Gets the current justification. + /// + public Justify? Justification { get; init; } + + /// + /// Gets the requested height. + /// + public int? Height { get; init; } + + /// + /// Gets a value indicating whether the context want items to render without + /// line breaks and return a single line where applicable. + /// + internal bool SingleLine { get; init; } + + /// + /// Creates a instance from a . + /// + /// The console. + /// The capabilities, or null to use the provided console's capabilities. + /// A representing the provided . + public static RenderOptions Create(IAnsiConsole console, IReadOnlyCapabilities? capabilities = null) + { + if (console is null) + { + throw new ArgumentNullException(nameof(console)); + } + + return new RenderOptions( + capabilities ?? console.Profile.Capabilities, + new Size(console.Profile.Width, console.Profile.Height)) + { + Justification = null, + Height = null, + SingleLine = false, + }; + } +} diff --git a/src/Mindee/Parsing/Table/Renderable.cs b/src/Mindee/Parsing/Table/Renderable.cs new file mode 100644 index 00000000..0a9cf2a7 --- /dev/null +++ b/src/Mindee/Parsing/Table/Renderable.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace Spectre.Console; + +/// +/// Base class for a renderable object implementing . +/// +public abstract class Renderable : IRenderable +{ + /// + [DebuggerStepThrough] + Measurement IRenderable.Measure(RenderOptions options, int maxWidth) + { + return Measure(options, maxWidth); + } + + /// + [DebuggerStepThrough] + IEnumerable IRenderable.Render(RenderOptions options, int maxWidth) + { + return Render(options, maxWidth); + } + + /// + /// Measures the renderable object. + /// + /// The render options. + /// The maximum allowed width. + /// The minimum and maximum width of the object. + protected virtual Measurement Measure(RenderOptions options, int maxWidth) + { + return new Measurement(maxWidth, maxWidth); + } + + /// + /// Renders the object. + /// + /// The render options. + /// The maximum allowed width. + /// A collection of segments. + protected abstract IEnumerable Render(RenderOptions options, int maxWidth); +} diff --git a/src/Mindee/Parsing/Table/Segment.cs b/src/Mindee/Parsing/Table/Segment.cs new file mode 100644 index 00000000..f8687141 --- /dev/null +++ b/src/Mindee/Parsing/Table/Segment.cs @@ -0,0 +1,647 @@ +using System; + + +namespace Spectre.Console.Rendering; + +/// +/// Represents a renderable segment. +/// +[DebuggerDisplay("{Text,nq}")] +public class Segment +{ + /// + /// Gets the segment text. + /// + public string Text { get; } + + /// + /// Gets a value indicating whether or not this is an explicit line break + /// that should be preserved. + /// + public bool IsLineBreak { get; } + + /// + /// Gets a value indicating whether or not this is a whitespace + /// that should be preserved but not taken into account when + /// layouting text. + /// + public bool IsWhiteSpace { get; } + + /// + /// Gets a value indicating whether or not his is a + /// control code such as cursor movement. + /// + public bool IsControlCode { get; } + + /// + /// Gets the segment style. + /// + public Style Style { get; } + + /// + /// Gets a segment representing a line break. + /// + public static Segment LineBreak { get; } = new Segment(Environment.NewLine, Style.Plain, true, false); + + /// + /// Gets an empty segment. + /// + public static Segment Empty { get; } = new Segment(string.Empty, Style.Plain, false, false); + + /// + /// Creates padding segment. + /// + /// Number of whitespace characters. + /// Segment for specified padding size. + public static Segment Padding(int size) => new(new string(' ', size)); + + /// + /// Initializes a new instance of the class. + /// + /// The segment text. + public Segment(string text) + : this(text, Style.Plain) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The segment text. + /// The segment style. + public Segment(string text, Style style) + : this(text, style, false, false) + { + } + + private Segment(string text, Style style, bool lineBreak, bool control) + { + Text = text?.NormalizeNewLines() ?? throw new ArgumentNullException(nameof(text)); + Style = style ?? throw new ArgumentNullException(nameof(style)); + IsLineBreak = lineBreak; + IsWhiteSpace = string.IsNullOrWhiteSpace(text); + IsControlCode = control; + } + + /// + /// Creates a control segment. + /// + /// The control code. + /// A segment representing a control code. + public static Segment Control(string control) + { + return new Segment(control, Style.Plain, false, true); + } + + /// + /// Gets the number of cells that this segment + /// occupies in the console. + /// + /// The number of cells that this segment occupies in the console. + public int CellCount() + { + if (IsControlCode) + { + return 0; + } + + return Cell.GetCellLength(Text); + } + + /// + /// Gets the number of cells that the segments occupies in the console. + /// + /// The segments to measure. + /// The number of cells that the segments occupies in the console. + public static int CellCount(IEnumerable segments) + { + if (segments is null) + { + throw new ArgumentNullException(nameof(segments)); + } + + var sum = 0; + foreach (var segment in segments) + { + sum += segment.CellCount(); + } + + return sum; + } + + /// + /// Returns a new segment without any trailing line endings. + /// + /// A new segment without any trailing line endings. + public Segment StripLineEndings() + { + return new Segment(Text.TrimEnd('\n').TrimEnd('\r'), Style); + } + + /// + /// Splits the segment at the offset. + /// + /// The offset where to split the segment. + /// One or two new segments representing the split. + public (Segment First, Segment? Second) Split(int offset) + { + if (offset < 0) + { + return (this, null); + } + + if (offset >= CellCount()) + { + return (this, null); + } + + var index = 0; + if (offset > 0) + { + var accumulated = 0; + foreach (var character in Text) + { + index++; + accumulated += Cell.GetCellLength(character); + if (accumulated >= offset) + { + break; + } + } + } + + return ( + new Segment(Text.Substring(0, index), Style), + new Segment(Text.Substring(index, Text.Length - index), Style)); + } + + /// + /// Clones the segment. + /// + /// A new segment that's identical to this one. + public Segment Clone() + { + return new Segment(Text, Style); + } + + /// + /// Splits the provided segments into lines. + /// + /// The segments to split. + /// A collection of lines. + public static List SplitLines(IEnumerable segments) + { + if (segments is null) + { + throw new ArgumentNullException(nameof(segments)); + } + + return SplitLines(segments, int.MaxValue); + } + + /// + /// Splits the provided segments into lines with a maximum width. + /// + /// The segments to split into lines. + /// The maximum width. + /// The height (if any). + /// A list of lines. + public static List SplitLines(IEnumerable segments, int maxWidth, int? height = null) + { + if (segments is null) + { + throw new ArgumentNullException(nameof(segments)); + } + + var lines = new List(); + var line = new SegmentLine(); + + var stack = new Stack(segments.Reverse()); + + while (stack.Count > 0) + { + var segment = stack.Pop(); + var segmentLength = segment.CellCount(); + + // Does this segment make the line exceed the max width? + var lineLength = line.CellCount(); + if (lineLength + segmentLength > maxWidth) + { + var diff = -(maxWidth - (lineLength + segmentLength)); + var offset = segment.Text.Length - diff; + + var (first, second) = segment.Split(offset); + + line.Add(first); + lines.Add(line); + line = new SegmentLine(); + + if (second != null) + { + stack.Push(second); + } + + continue; + } + + // Does the segment contain a newline? + if (segment.Text.ContainsExact("\n")) + { + // Is it a new line? + if (segment.Text == "\n") + { + if (line.Length != 0 || segment.IsLineBreak) + { + lines.Add(line); + line = new SegmentLine(); + } + + continue; + } + + var text = segment.Text; + while (text != null) + { + var parts = text.SplitLines(); + if (parts.Length > 0) + { + if (parts[0].Length > 0) + { + line.Add(new Segment(parts[0], segment.Style)); + } + } + + if (parts.Length > 1) + { + if (line.Length > 0) + { + lines.Add(line); + line = new SegmentLine(); + } + + text = string.Concat(parts.Skip(1).Take(parts.Length - 1)); + } + else + { + text = null; + } + } + } + else + { + line.Add(segment); + } + } + + if (line.Count > 0) + { + lines.Add(line); + } + + // Got a height specified? + if (height != null) + { + if (lines.Count >= height) + { + // Remove lines + lines.RemoveRange(height.Value, lines.Count - height.Value); + } + else + { + // Add lines + var missing = height - lines.Count; + for (var i = 0; i < missing; i++) + { + lines.Add(new SegmentLine()); + } + } + } + + return lines; + } + + /// + /// Splits an overflowing segment into several new segments. + /// + /// The segment to split. + /// The overflow strategy to use. + /// The maximum width. + /// A list of segments that has been split. + public static List SplitOverflow(Segment segment, Overflow? overflow, int maxWidth) + { + if (segment is null) + { + throw new ArgumentNullException(nameof(segment)); + } + + if (segment.CellCount() <= maxWidth) + { + return new List(1) { segment }; + } + + // Default to folding + overflow ??= Overflow.Fold; + + var result = new List(); + + if (overflow == Overflow.Fold) + { + var splitted = SplitSegment(segment.Text, maxWidth); + foreach (var str in splitted) + { + result.Add(new Segment(str, segment.Style)); + } + } + else if (overflow == Overflow.Crop) + { + if (Math.Max(0, maxWidth - 1) == 0) + { + result.Add(new Segment(string.Empty, segment.Style)); + } + else + { + result.Add(new Segment(segment.Text.Substring(0, maxWidth), segment.Style)); + } + } + else if (overflow == Overflow.Ellipsis) + { + if (Math.Max(0, maxWidth - 1) == 0) + { + result.Add(new Segment("…", segment.Style)); + } + else + { + result.Add(new Segment(segment.Text.Substring(0, maxWidth - 1) + "…", segment.Style)); + } + } + + return result; + } + + /// + /// Truncates the segments to the specified width. + /// + /// The segments to truncate. + /// The maximum width that the segments may occupy. + /// A list of segments that has been truncated. + public static List Truncate(IEnumerable segments, int maxWidth) + { + if (segments is null) + { + throw new ArgumentNullException(nameof(segments)); + } + + var result = new List(); + + var totalWidth = 0; + foreach (var segment in segments) + { + var segmentCellWidth = segment.CellCount(); + if (totalWidth + segmentCellWidth > maxWidth) + { + break; + } + + result.Add(segment); + totalWidth += segmentCellWidth; + } + + if (result.Count == 0 && segments.Any()) + { + var segment = Truncate(segments.First(), maxWidth); + if (segment != null) + { + result.Add(segment); + } + } + + return result; + } + + /// + /// Truncates the segment to the specified width. + /// + /// The segment to truncate. + /// The maximum width that the segment may occupy. + /// A new truncated segment, or null. + public static Segment? Truncate(Segment? segment, int maxWidth) + { + if (segment is null) + { + return null; + } + + if (segment.CellCount() <= maxWidth) + { + return segment; + } + + var builder = new StringBuilder(); + foreach (var character in segment.Text) + { + var accumulatedCellWidth = builder.ToString().GetCellWidth(); + if (accumulatedCellWidth >= maxWidth) + { + break; + } + + builder.Append(character); + } + + if (builder.Length == 0) + { + return null; + } + + return new Segment(builder.ToString(), segment.Style); + } + + internal static IEnumerable Merge(IEnumerable segments) + { + if (segments is null) + { + throw new ArgumentNullException(nameof(segments)); + } + + var result = new List(); + + var segmentBuilder = (SegmentBuilder?)null; + foreach (var segment in segments) + { + if (segmentBuilder == null) + { + segmentBuilder = new SegmentBuilder(segment); + continue; + } + + // Both control codes? + if (segment.IsControlCode && segmentBuilder.IsControlCode()) + { + segmentBuilder.Append(segment.Text); + continue; + } + + // Same style? + if (segmentBuilder.StyleEquals(segment.Style) && !segmentBuilder.IsLineBreak() && !segmentBuilder.IsControlCode()) + { + segmentBuilder.Append(segment.Text); + continue; + } + + result.Add(segmentBuilder.Build()); + segmentBuilder.Reset(segment); + } + + if (segmentBuilder != null) + { + result.Add(segmentBuilder.Build()); + } + + return result; + } + + internal static List TruncateWithEllipsis(IEnumerable segments, int maxWidth) + { + if (segments is null) + { + throw new ArgumentNullException(nameof(segments)); + } + + if (CellCount(segments) <= maxWidth) + { + return new List(segments); + } + + segments = TrimEnd(Truncate(segments, maxWidth - 1)); + if (!segments.Any()) + { + return new List(1); + } + + var result = new List(segments); + result.Add(new Segment("…", result.Last().Style)); + return result; + } + + internal static List TrimEnd(IEnumerable segments) + { + if (segments is null) + { + throw new ArgumentNullException(nameof(segments)); + } + + var stack = new Stack(); + var checkForWhitespace = true; + foreach (var segment in segments.Reverse()) + { + if (checkForWhitespace) + { + if (segment.IsWhiteSpace) + { + continue; + } + + checkForWhitespace = false; + } + + stack.Push(segment); + } + + return stack.ToList(); + } + + // TODO: Move this to Table + internal static List> MakeSameHeight(int cellHeight, List> cells) + { + if (cells is null) + { + throw new ArgumentNullException(nameof(cells)); + } + + foreach (var cell in cells) + { + if (cell.Count < cellHeight) + { + while (cell.Count != cellHeight) + { + cell.Add(new SegmentLine()); + } + } + } + + return cells; + } + + internal static List MakeWidth(int expectedWidth, List lines) + { + foreach (var line in lines) + { + var width = line.CellCount(); + if (width < expectedWidth) + { + var diff = expectedWidth - width; + line.Add(new Segment(new string(' ', diff))); + } + } + + return lines; + } + + internal static List SplitSegment(string text, int maxCellLength) + { + var list = new List(); + + var length = 0; + var sb = new StringBuilder(); + foreach (var ch in text) + { + if (length + UnicodeCalculator.GetWidth(ch) > maxCellLength) + { + list.Add(sb.ToString()); + sb.Clear(); + length = 0; + } + + length += UnicodeCalculator.GetWidth(ch); + sb.Append(ch); + } + + list.Add(sb.ToString()); + + return list; + } + + private class SegmentBuilder + { + private readonly StringBuilder _textBuilder = new(); + private Segment _originalSegment; + + public SegmentBuilder(Segment originalSegment) + { + _originalSegment = originalSegment; + Reset(originalSegment); + } + + public bool IsControlCode() => _originalSegment.IsControlCode; + public bool IsLineBreak() => _originalSegment.IsLineBreak; + public bool StyleEquals(Style segmentStyle) => segmentStyle.Equals(_originalSegment.Style); + + public void Append(string text) + { + _textBuilder.Append(text); + } + + public Segment Build() + { + return new Segment(_textBuilder.ToString(), _originalSegment.Style, _originalSegment.IsLineBreak, + _originalSegment.IsControlCode); + } + + public void Reset(Segment segment) + { + _textBuilder.Clear(); + _textBuilder.Append(segment.Text); + _originalSegment = segment; + } + } +} diff --git a/src/Mindee/Parsing/Table/StringExtensions.cs b/src/Mindee/Parsing/Table/StringExtensions.cs new file mode 100644 index 00000000..1245304c --- /dev/null +++ b/src/Mindee/Parsing/Table/StringExtensions.cs @@ -0,0 +1,215 @@ +using System; +using System.Text; + +namespace Spectre.Console; + +/// +/// Contains extension methods for . +/// +public static class StringExtensions +{ + // Cache whether or not internally normalized line endings + // already are normalized. No reason to do yet another replace if it is. + private static readonly bool _alreadyNormalized + = Environment.NewLine.Equals("\n", StringComparison.OrdinalIgnoreCase); + + /// + /// Escapes text so that it won’t be interpreted as markup. + /// + /// The text to escape. + /// A string that is safe to use in markup. + public static string EscapeMarkup(this string? text) + { + if (text == null) + { + return string.Empty; + } + + return text + .ReplaceExact("[", "[[") + .ReplaceExact("]", "]]"); + } + + /// + /// Removes markup from the specified string. + /// + /// The text to remove markup from. + /// A string that does not have any markup. + public static string RemoveMarkup(this string? text) + { + if (string.IsNullOrWhiteSpace(text)) + { + return string.Empty; + } + + var result = new StringBuilder(); + + var tokenizer = new MarkupTokenizer(text); + while (tokenizer.MoveNext() && tokenizer.Current != null) + { + if (tokenizer.Current.Kind == MarkupTokenKind.Text) + { + result.Append(tokenizer.Current.Value); + } + } + + return result.ToString(); + } + + /// + /// Gets the cell width of the specified text. + /// + /// The text to get the cell width of. + /// The cell width of the text. + public static int GetCellWidth(this string text) + { + return Cell.GetCellLength(text); + } + + internal static string CapitalizeFirstLetter(this string? text, CultureInfo? culture = null) + { + if (text == null) + { + return string.Empty; + } + + culture ??= CultureInfo.InvariantCulture; + + if (text.Length > 0 && char.IsLower(text[0])) + { + text = string.Format(culture, "{0}{1}", char.ToUpper(text[0], culture), text.Substring(1)); + } + + return text; + } + + internal static string? RemoveNewLines(this string? text) + { + return text?.ReplaceExact("\r\n", string.Empty) + ?.ReplaceExact("\n", string.Empty); + } + + internal static string NormalizeNewLines(this string? text, bool native = false) + { + text = text?.ReplaceExact("\r\n", "\n"); + text ??= string.Empty; + + if (native && !_alreadyNormalized) + { + text = text.ReplaceExact("\n", Environment.NewLine); + } + + return text; + } + + internal static string[] SplitLines(this string text) + { + var result = text?.NormalizeNewLines()?.Split(new[] { '\n' }, StringSplitOptions.None); + return result ?? Array.Empty(); + } + + internal static string[] SplitWords(this string word, StringSplitOptions options = StringSplitOptions.None) + { + var result = new List(); + + static string Read(StringBuffer reader, Func criteria) + { + var buffer = new StringBuilder(); + while (!reader.Eof) + { + var current = reader.Peek(); + if (!criteria(current)) + { + break; + } + + buffer.Append(reader.Read()); + } + + return buffer.ToString(); + } + + using (var reader = new StringBuffer(word)) + { + while (!reader.Eof) + { + var current = reader.Peek(); + if (char.IsWhiteSpace(current)) + { + var x = Read(reader, c => char.IsWhiteSpace(c)); + if (options != StringSplitOptions.RemoveEmptyEntries) + { + result.Add(x); + } + } + else + { + result.Add(Read(reader, c => !char.IsWhiteSpace(c))); + } + } + } + + return result.ToArray(); + } + + internal static string Repeat(this string text, int count) + { + if (text is null) + { + throw new ArgumentNullException(nameof(text)); + } + + if (count <= 0) + { + return string.Empty; + } + + if (count == 1) + { + return text; + } + + return string.Concat(Enumerable.Repeat(text, count)); + } + + internal static string ReplaceExact(this string text, string oldValue, string? newValue) + { +#if NETSTANDARD2_0 + return text.Replace(oldValue, newValue); +#else + return text.Replace(oldValue, newValue, StringComparison.Ordinal); +#endif + } + + internal static bool ContainsExact(this string text, string value) + { +#if NETSTANDARD2_0 + return text.Contains(value); +#else + return text.Contains(value, StringComparison.Ordinal); +#endif + } + + /// + /// "Masks" every character in a string. + /// + /// String value to mask. + /// Character to use for masking. + /// Masked string. + public static string Mask(this string value, char? mask) + { + var output = string.Empty; + + if (mask is null) + { + return output; + } + + foreach (var c in value) + { + output += mask; + } + + return output; + } +} diff --git a/src/Mindee/Parsing/Table/Style.cs b/src/Mindee/Parsing/Table/Style.cs new file mode 100644 index 00000000..e34fe253 --- /dev/null +++ b/src/Mindee/Parsing/Table/Style.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; + +namespace Spectre.Console; + +/// +/// Represents color and text decoration. +/// +public sealed class Style : IEquatable