MergeRows.cs
  1. //
  2. // This code is part of Document Solutions for PDF demos.
  3. // Copyright (c) MESCIUS inc. All rights reserved.
  4. //
  5. using System;
  6. using System.IO;
  7. using System.Drawing;
  8. using System.Text;
  9. using System.Data;
  10. using System.Linq;
  11. using System.Collections.Generic;
  12. using GrapeCity.Documents.Pdf;
  13. using GrapeCity.Documents.Text;
  14. using GrapeCity.Documents.Html;
  15.  
  16. namespace DsPdfWeb.Demos
  17. {
  18. // This sample shows how to build and render a table-based report
  19. // grouped by the first column, with that column's cells with same
  20. // values merged.
  21. // This sample uses a JavaScript code in the HTML to actually
  22. // merge the cells, demonstrating the use of JavaScript when
  23. // rendering HTML to PDF.
  24. // Note that the sample limits the number of rows so that
  25. // the whole table fits on a single page.
  26. //
  27. // Please see notes in comments at the top of HelloWorldHtml
  28. // sample code for details on adding DsHtml to your projects.
  29. public class MergeRows
  30. {
  31. public void CreatePDF(Stream stream)
  32. {
  33. const string TTAG = "___TABLE___";
  34.  
  35. // HTML page template:
  36. const string tableTpl =
  37. "<!DOCTYPE html>" +
  38. "<html>" +
  39. "<head>" +
  40. "<style>" +
  41.  
  42. "html * {" +
  43. " font-family: 'Trebuchet MS', Arial, Helvetica, sans-serif !important;" +
  44. "}" +
  45.  
  46. "h1 {" +
  47. " color: #1a5276;" +
  48. " background-color: #d2b4de;" +
  49. " text-align: center;" +
  50. " padding: 6px;" +
  51. "}" +
  52.  
  53. "thead {display: table-header-group;}" +
  54.  
  55. "#products {" +
  56. " font-family: 'Trebuchet MS', Arial, Helvetica, sans-serif;" +
  57. " border-collapse: collapse;" +
  58. " width: 100%;" +
  59. "}" +
  60.  
  61. "#products td, #products th {" +
  62. " border: 1px solid #ddd;" +
  63. " padding: 8px;" +
  64. "}" +
  65.  
  66. "#products tr:hover {background-color: #ddd;}" +
  67.  
  68. "#products th {" +
  69. " padding-top: 12px;" +
  70. " padding-bottom: 12px;" +
  71. " text-align: left;" +
  72. " background-color: #a569bd;" +
  73. " color: white;" +
  74. "}" +
  75. "</style>" +
  76.  
  77. "</head>" +
  78. "<body onload='mergeRows()'>" +
  79.  
  80. // solution from https://stackoverflow.com/questions/56587070/merge-neighbouring-html-table-cells-with-same-value-using-js
  81. "<script>" +
  82. "function mergeRows() {" +
  83. " const table = document.querySelector('table');" +
  84. " let headerCell = null;" +
  85. " for (let row of table.rows)" +
  86. " {" +
  87. " const firstCell = row.cells[0];" +
  88. " if (headerCell === null || firstCell.innerText !== headerCell.innerText)" +
  89. " {" +
  90. " headerCell = firstCell;" +
  91. " }" +
  92. " else" +
  93. " {" +
  94. " headerCell.rowSpan++;" +
  95. " firstCell.remove();" +
  96. " }" +
  97. " }" +
  98. "}" +
  99. "</script>" +
  100.  
  101. TTAG +
  102.  
  103. "</body>" +
  104. "</html>";
  105.  
  106. const string tableHead = "<h1>Products by Suppliers</h1>";
  107.  
  108. const string tableFmt =
  109. "<table id='products'>" +
  110. " <thead>" +
  111. " <th>Supplier</th>" +
  112. " <th>Description</th>" +
  113. " <th>Quantity Per Unit</th>" +
  114. " <th>Unit Price</th>" +
  115. " </thead>" +
  116. "{0}" +
  117. "</table>";
  118.  
  119. const string dataRowFmt =
  120. " <tr>" +
  121. " <td>{0}</td>" +
  122. " <td>{1}</td>" +
  123. " <td>{2}</td>" +
  124. " <td align='right'>{3:C}</td>" +
  125. " </tr>";
  126.  
  127. using var ds = new DataSet();
  128.  
  129. ds.ReadXml(Path.Combine("Resources", "data", "DsNWind.xml"));
  130.  
  131. DataTable dtProds = ds.Tables["Products"];
  132. DataTable dtSupps = ds.Tables["Suppliers"];
  133.  
  134. var products =
  135. (from prod in dtProds.Select()
  136. join supp in dtSupps.Select()
  137. on prod["SupplierID"] equals supp["SupplierID"]
  138. orderby supp["CompanyName"]
  139. select new
  140. {
  141. ProductName = prod["ProductName"],
  142. Supplier = supp["CompanyName"],
  143. QuantityPerUnit = prod["QuantityPerUnit"],
  144. UnitPrice = prod["UnitPrice"]
  145. }).Take(16);
  146.  
  147. var sb = new StringBuilder();
  148. sb.AppendLine(tableHead);
  149. foreach (var prod in products)
  150. sb.AppendFormat(dataRowFmt, prod.Supplier, prod.ProductName, prod.QuantityPerUnit, prod.UnitPrice);
  151.  
  152. var html = tableTpl.Replace(TTAG, string.Format(tableFmt, sb.ToString()));
  153.  
  154. // PdfOptions specifies options for HTML to PDF conversion:
  155. var pdfOptions = new PdfOptions()
  156. {
  157. Margins = new PdfMargins(0.2f, 1, 0.2f, 1),
  158. DisplayHeaderFooter = true,
  159. HeaderTemplate = "<div style='color:#1a5276; font-size:12px; width:1000px; margin-left:0.2in; margin-right:0.2in'>" +
  160. "<span style='float:left;'>Product Price List</span>" +
  161. "<span style='float:right'>Page <span class='pageNumber'></span> of <span class='totalPages'></span></span>" +
  162. "</div>",
  163. FooterTemplate = "<div style='color: #1a5276; font-size:12em; width:1000px; margin-left:0.2in; margin-right:0.2in;'>" +
  164. "<span>(c) MESCIUS inc. All Rights Reserved.</span>" +
  165. "<span style='float:right'>Generated on <span class='date'></span></span></div>"
  166. };
  167.  
  168. // Create an instance of GcHtmlBrowser that is used to render HTML:
  169. using var browser = Common.Util.NewHtmlBrowser();
  170.  
  171. using var htmlPage = browser.NewPage(html);
  172. var tmp = Path.GetTempFileName();
  173. htmlPage.SaveAsPdf(tmp, pdfOptions);
  174.  
  175. // Copy the created PDF from the temp file to target stream:
  176. using (var ts = File.OpenRead(tmp))
  177. ts.CopyTo(stream);
  178. // Clean up:
  179. File.Delete(tmp);
  180.  
  181. // Done.
  182. }
  183. }
  184. }
  185.