MergeRows.cs
//
// This code is part of GrapeCity Documents for Imaging samples.
// Copyright (c) GrapeCity, Inc. All rights reserved.
//
using System;
using System.IO;
using System.Drawing;
using System.Text;
using System.Data;
using System.Linq;
using System.Collections.Generic;
using GrapeCity.Documents.Drawing;
using GrapeCity.Documents.Imaging;
using GrapeCity.Documents.Text;
using GrapeCity.Documents.Html;

namespace GcImagingWeb.Samples
{
    // This sample shows how to build and render a table-based report
    // grouped by the first column, with that column's cells with same
    // values merged.
    // This sample uses a JavaScript code in the HTML to actually
    // merge the cells, demonstrating the use of JavaScript when
    // rendering HTML to images.
    // Note that the sample limits the number of rows so that
    // the whole table fits in the image.
    //
    // Please see notes in comments at the top of HelloWorldHtml
    // sample code for details on adding GcHtml to your projects.
    public class MergeRows
    {
        public Stream GenerateImageStream(ImageEncoding targetEncoding, int pixelWidth = 1024, int pixelHeight = 1024, bool opaque = true, float dpiX = 96, float dpiY = 96)
        {
            const string TTAG = "___TABLE___";

            // HTML page template:
            const string tableTpl =
                "<!DOCTYPE html>" +
                "<html>" +
                "<head>" +
                "<style>" +

                "html * {" +
                "  font-family: 'Trebuchet MS', Arial, Helvetica, sans-serif !important;" +
                "}" +

                "h1 {" +
                "  color: #1a5276;" +
                "  background-color: #d2b4de;" +
                "  text-align: center;" +
                "  padding: 6px;" +
                "}" +

                "thead {display: table-header-group;}" +

                "#products {" +
                "  font-family: 'Trebuchet MS', Arial, Helvetica, sans-serif;" +
                "  border-collapse: collapse;" +
                "  width: 100%;" +
                "}" +

                "#products td, #products th {" +
                "  border: 1px solid #ddd;" +
                "  padding: 8px;" +
                "}" +

                // "#products tr:nth-child(even){background-color: #f2f2f2;}" +

                "#products tr:hover {background-color: #ddd;}" +

                "#products th {" +
                "  padding-top: 12px;" +
                "  padding-bottom: 12px;" +
                "  text-align: left;" +
                "  background-color: #a569bd;" +
                "  color: white;" +
                "}" +
                "</style>" +

                "</head>" +
                "<body onload='mergeRows()'>" +

                // solution from https://stackoverflow.com/questions/56587070/merge-neighbouring-html-table-cells-with-same-value-using-js
                "<script>" +
                "function mergeRows() {" +
                "  const table = document.querySelector('table');" +
                "  let headerCell = null;" +
                "  for (let row of table.rows)" +
                "  {" +
                "    const firstCell = row.cells[0];" +
                "    if (headerCell === null || firstCell.innerText !== headerCell.innerText)" +
                "    {" +
                "      headerCell = firstCell;" +
                "    }" +
                "    else" +
                "    {" +
                "      headerCell.rowSpan++;" +
                "      firstCell.remove();" +
                "    }" +
                "  }" +
                "}" +
                "</script>" +

                TTAG +

                "</body>" +
                "</html>";

            const string tableHead = "<h1>Products by Suppliers</h1>";

            const string tableFmt =
                "<table id='products'>" +
                "  <thead>" +
                "    <th>Supplier</th>" +
                "    <th>Description</th>" +
                "    <th>Quantity Per Unit</th>" +
                "    <th>Unit Price</th>" +
                "  </thead>" +
                "{0}" +
                "</table>";

            const string dataRowFmt =
                "  <tr>" +
                "    <td>{0}</td>" +
                "    <td>{1}</td>" +
                "    <td>{2}</td>" +
                "    <td align='right'>{3:C}</td>" +
                "  </tr>";

            DataSet ds = new DataSet();
            ds.ReadXml(Path.Combine("Resources", "data", "GcNWind.xml"));

            DataTable dtProds = ds.Tables["Products"];
            DataTable dtSupps = ds.Tables["Suppliers"];

            var products =
                (from prod in dtProds.Select()
                join supp in dtSupps.Select()
                on prod["SupplierID"] equals supp["SupplierID"]
                orderby supp["CompanyName"]
                select new
                {
                    ProductName = prod["ProductName"],
                    Supplier = supp["CompanyName"],
                    QuantityPerUnit = prod["QuantityPerUnit"],
                    UnitPrice = prod["UnitPrice"]
                }).Take(16);

            var sb = new StringBuilder();
            sb.AppendLine(tableHead);
            foreach (var prod in products)
                sb.AppendFormat(dataRowFmt, prod.Supplier, prod.ProductName, prod.QuantityPerUnit, prod.UnitPrice);

            var html = tableTpl.Replace(TTAG, string.Format(tableFmt, sb.ToString()));

            var tfile = Path.GetTempFileName();
            var ms = new MemoryStream();
            // We use GcHtmlRenderer to render the whole generated HTML to an image.
            // Note that GcHtmlRenderer natively supports only JPEG and PNG.
            // In this sample we limite the output to those two formats.
            // For a more flexible approach that allows to render HTML into any
            // image format supported by GcImaging, please see HtmlRenderPage0.
            using (var re = new GcHtmlRenderer(html))
            {
                switch (targetEncoding)
                {
                    case ImageEncoding.Jpeg:
                        tfile = Path.GetTempFileName();
                        re.RenderToJpeg(tfile, new JpegSettings() { WindowSize = new Size(pixelWidth, pixelHeight) });
                        break;
                    case ImageEncoding.Png:
                        tfile = Path.GetTempFileName();
                        re.RenderToPng(tfile, new PngSettings() { WindowSize = new Size(pixelWidth, pixelHeight) });
                        break;
                    default:
                        throw new Exception("Only JPEG and PNG are supported.");
                }
            }
            // Copy the created image from the temp file to target stream:
            using (var ts = File.OpenRead(tfile))
                ts.CopyTo(ms);
            // Clean up:
            File.Delete(tfile);
            // Done.
            return ms;
        }
    }
}