Part 3: Printing Full Dynamic Layout Content (Reports, Data Grids, etc…)

Jan 3, 2026
Updated Jun 26, 2026
adriancs

This is part of the series of introducing Vanilla ASP.NET Web Forms Architecture. Read more about this at the main menu: Main Article: [Introducing Vanilla ASP.NET Web Forms Architecture]

Part 3: Printing Full Dynamic Layout Content (Reports, Data Grids, etc…)

Introduction

In Part 1, we covered static layouts (tickets, cards) where everything is fixed. In Part 2, we tackled semi-dynamic layouts (invoices) where a fixed header/footer surrounds a variable-length item section — requiring careful height calculations and pagination logic.

Now in Part 3, we’re going full dynamic — and here’s the good news: it’s actually simpler than Part 2!

Why? Because with full dynamic reports:

  • No fixed footer to worry about — No totals section that must appear on the last page
  • No height calculations — Just count the rows and paginate
  • Simple math — 30 rows per page? Divide total rows by 30, done!

The classic examples are:

  • Daily/Monthly Sales Reports
  • Transaction Lists
  • Inventory Reports
  • Customer Lists
  • Audit Logs

These documents share a common structure:

  1. Simple Header — Report title, date, page number, summary totals
  2. Data Table — Rows and rows of data, same structure on every page
  3. No Footer — Or just a simple page number (already in header)

Visual Design – Full Dynamic Layout (Daily Sales Report)

Example: A daily sales report showing all invoices for the day

Works on: A4/Letter paper printers

Imagine we have a daily sales report that will be printed on A4 paper, which will look something like this:

The report consists of:

  • Header Section (20mm): Report title, date, summary totals, page number
  • Data Table (250mm): Invoice rows with consistent columns across all pages

Understanding the Page Layout

Here’s the simple layout for our report:

A4 Report Layout 297mm height
Top Padding 10mm
Header Section 20mm
  • Report title
  • Report date
  • Summary: Total Sales, Invoice Count
  • Page: X of Y
Report Table 250mm
  • Table header (8mm)
  • Table body (242mm)
  • ~30 rows @ ~8mm each
297mm
Padding (10mm)
Header (20mm)
Report Table (250mm)

The Simple Pagination Logic

This is where full dynamic shines — the pagination is dead simple:

// Total rows in the report
int totalRows = salesData.Count;

// Rows per page (fixed)
int rowsPerPage = 30;

// Calculate total pages (simple ceiling division)
int totalPages = (int)Math.Ceiling((double)totalRows / rowsPerPage);

// Example: 85 rows ÷ 30 = 2.83 → 3 pages
// Page 1: rows 1-30
// Page 2: rows 31-60
// Page 3: rows 61-85

No height calculations. No footer positioning. Just simple math.

Data Model

Prepare the C# Class object for the sales data:

/// <summary>
/// Represents a single row in the daily sales report
/// </summary>
public class DailySalesRow
{
    public int RowNo { get; set; }           // Sequential row number
    public string InvoiceNo { get; set; }    // Invoice number (e.g., INV-2025-0001)
    public string CustomerName { get; set; } // Customer name
    public decimal TotalAmount { get; set; } // Invoice total amount
    public decimal PaidAmount { get; set; }  // Amount paid so far
    public string PaidStatus { get; set; }   // "Paid", "Partial", "Unpaid"
}

/// <summary>
/// Container for the entire report data
/// </summary>
public class DailySalesReport
{
    public DateTime ReportDate { get; set; }       // The date of the report
    public decimal TotalSales { get; set; }        // Sum of all invoice totals
    public int TotalInvoices { get; set; }         // Count of invoices
    public List<DailySalesRow> Rows { get; set; }  // All the data rows
}

URL API Routing

Preparing the URL API Routing:

// Example of URL API format:

// Query String
// https://myweb.com/apiDailySales?date=2025-01-02&autoprint=1

// MVC alike URL
// https://myweb.com/apiDailySales/2025-01-02/1

// MVC alike URL Routing

using System.Web.Routing;

public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        // Route the main api page
        RouteTable.Routes.MapPageRoute("apiSales1", "apiDailySales", "~/apiDailySales.aspx");

        // Route with date parameter only (auto-print defaults to true)
        RouteTable.Routes.MapPageRoute("apiSales2", "apiDailySales/{date}", "~/apiDailySales.aspx");
        RouteTable.Routes.MapPageRoute("apiSales3", "apiDailySales/{date}/", "~/apiDailySales.aspx");

        // Route with date and auto-print parameters
        RouteTable.Routes.MapPageRoute("apiSales4", "apiDailySales/{date}/{autoprint}", "~/apiDailySales.aspx");
        RouteTable.Routes.MapPageRoute("apiSales5", "apiDailySales/{date}/{autoprint}/", "~/apiDailySales.aspx");
    }
}

Report Generator Engine

The Report Generator Engine — notice how much simpler it is compared to Part 2:

public class engineDailySales
{
    // ============================================
    // Configuration: Rows per page
    // This is the ONLY pagination setting needed!
    // ============================================
    const int ROWS_PER_PAGE = 30;

    /// <summary>
    /// Generate the Daily Sales Report HTML document
    /// </summary>
    /// <param name="reportDate">The date to generate report for</param>
    /// <param name="autoprint">1 = auto print, 0 = preview only</param>
    /// <returns>Complete HTML document as string</returns>
    public static string GenerateReport(DateTime reportDate, int autoprint)
    {
        // ============================================
        // Step 1: Get the report data from database
        // ============================================
        DailySalesReport report = Database.GetDailySalesReport(reportDate);

        // No data found for this date
        if (report == null || report.Rows == null || report.Rows.Count == 0)
        {
            return ReportNoData(reportDate);
        }

        // ============================================
        // Step 2: Calculate pagination
        // This is SO much simpler than Part 2!
        // ============================================
        int totalRows = report.Rows.Count;
        int totalPages = (int)Math.Ceiling((double)totalRows / ROWS_PER_PAGE);

        // ============================================
        // Step 3: Build the HTML document
        // ============================================
        StringBuilder sb = new StringBuilder();

        // Render the HTML head section
        sb.Append($@"
<!DOCTYPE html>
<html lang='en'>
<head>
    <meta charset='UTF-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1.0'>
    <title>Daily Sales Report - {reportDate:dd MMM yyyy}</title>
    <style>
        {GetCSS()}
    </style>
</head>
<body>
        ");

        // Insert auto-print script if requested
        if (autoprint == 1)
        {
            sb.Append("<script>window.onload = ()=> { window.print(); };</script>");
        }

        // ============================================
        // Step 4: Generate each page
        // Loop through pages and slice the data
        // ============================================
        for (int pageNum = 0; pageNum < totalPages; pageNum++)
        {
            // Calculate which rows belong to this page
            // Page 0: rows 0-29, Page 1: rows 30-59, etc.
            int startIndex = pageNum * ROWS_PER_PAGE;

            // Get the rows for this page using Skip and Take
            var pageRows = report.Rows
                .Skip(startIndex)           // Skip rows from previous pages
                .Take(ROWS_PER_PAGE)        // Take only rows for this page
                .ToList();

            // Insert page break before each page (except the first)
            if (pageNum > 0)
            {
                sb.Append("<div style='page-break-after: always;'></div>");
            }

            // ============================================
            // Render the page content
            // ============================================
            sb.Append("<div class='page'>");

            // Render header (same on every page, just different page number)
            RenderHeader(sb, report, pageNum + 1, totalPages);

            // Render the data table for this page's rows
            RenderDataTable(sb, pageRows);

            sb.Append("</div>"); // Close .page
        }

        // Close the HTML document
        sb.Append("</body></html>");

        return sb.ToString();
    }

    /// <summary>
    /// Render the report header section
    /// Contains: Title, Date, Summary totals, Page number
    /// </summary>
    static void RenderHeader(StringBuilder sb, DailySalesReport report, int currentPage, int totalPages)
    {
        sb.Append($@"
    <!-- ========================================
         Report Header Section (20mm)
         ======================================== -->
    <div class='report-header'>

        <!-- Left side: Title and Date -->
        <div>
            <div class='report-title'>Daily Sales Report</div>
            <div class='report-date'>Report Date: {report.ReportDate:dd MMM yyyy}</div>
        </div>

        <!-- Right side: Summary boxes -->
        <div class='report-meta'>
            <div class='report-summary'>

                <!-- Total Sales Amount -->
                <div class='summary-box'>
                    <div class='summary-label'>Total Sales</div>
                    <div class='summary-value'>${report.TotalSales:#,##0.00}</div>
                </div>

                <!-- Total Invoice Count -->
                <div class='summary-box'>
                    <div class='summary-label'>Invoices</div>
                    <div class='summary-value'>{report.TotalInvoices}</div>
                </div>

                <!-- Page Number -->
                <div class='summary-box'>
                    <div class='summary-label'>Page</div>
                    <div class='summary-value'>{currentPage} of {totalPages}</div>
                </div>

            </div>
        </div>

    </div>
        ");
    }

    /// <summary>
    /// Render the data table for the current page
    /// </summary>
    /// <param name="sb">StringBuilder to append to</param>
    /// <param name="rows">The rows to display on this page (max 30)</param>
    static void RenderDataTable(StringBuilder sb, List<DailySalesRow> rows)
    {
        sb.Append(@"
    <!-- ========================================
         Report Table Section (250mm)
         Table Header: 8mm
         Table Body: 242mm (~30 rows)
         ======================================== -->
    <div class='report-table-container'>
        <table class='report-table'>

            <!-- Table Header -->
            <thead>
                <tr>
                    <th class='col-no'>No</th>
                    <th class='col-invoice'>Invoice No</th>
                    <th class='col-customer'>Customer Name</th>
                    <th class='col-total'>Total Amount</th>
                    <th class='col-paid'>Paid Amount</th>
                    <th class='col-status'>Status</th>
                </tr>
            </thead>

            <!-- Table Body -->
            <tbody>
        ");

        // ============================================
        // Loop through each row and render it
        // ============================================
        foreach (var row in rows)
        {
            // Determine CSS class for paid amount coloring
            // Green for fully paid, red for zero, default for partial
            string paidClass = "amount";
            if (row.PaidAmount >= row.TotalAmount)
                paidClass = "amount amount-positive";  // Green - fully paid
            else if (row.PaidAmount == 0)
                paidClass = "amount amount-zero";      // Red - nothing paid

            // Determine status badge CSS class
            string statusClass = row.PaidStatus.ToLower() switch
            {
                "paid" => "status-badge status-paid",       // Green badge
                "partial" => "status-badge status-partial", // Yellow badge
                "unpaid" => "status-badge status-unpaid",   // Red badge
                _ => "status-badge"                         // Default
            };

            // Render the table row
            sb.Append($@"
                <tr>
                    <td class='col-no'>{row.RowNo}</td>
                    <td class='col-invoice'>{row.InvoiceNo}</td>
                    <td class='col-customer'>{row.CustomerName}</td>
                    <td class='col-total amount'>{row.TotalAmount:#,##0.00}</td>
                    <td class='col-paid {paidClass}'>{row.PaidAmount:#,##0.00}</td>
                    <td class='col-status'><span class='{statusClass}'>{row.PaidStatus}</span></td>
                </tr>
            ");
        }

        // Close the table
        sb.Append(@"
            </tbody>
        </table>
    </div>
        ");
    }

    /// <summary>
    /// Generate error page when no data is found
    /// </summary>
    static string ReportNoData(DateTime reportDate)
    {
        return $@"
<!DOCTYPE html>
<html lang='en'>
<head>
    <meta charset='UTF-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1.0'>
    <title>No Data Found</title>
</head>
<body>
    <script>
        window.onload = function() {{
            alert('No sales data found for {reportDate:dd MMM yyyy}');
            // Notify parent window if in iframe
            if (window.parent && window.parent !== window) {{
                window.parent.postMessage('no-data-found', '*');
            }}
        }};
    </script>
    <div style='display: none;'>No data found</div>
</body>
</html>";
    }

    /// <summary>
    /// Get the CSS styles for the report
    /// </summary>
    static string GetCSS()
    {
        return @"
        /* CSS content - see full CSS section below */
        ";
    }
}

Backend API Page

The Backend API page, create a blank ASP.NET Web Forms:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="apiDailySales.aspx.cs" Inherits="myweb.apiDailySales" %>

Delete all the frontend markup and leave only the first line page directive.

The code behind:

protected void Page_Load(object sender, EventArgs e)
{
    // ============================================
    // Step 1: User authentication verification
    // ============================================
    if (Session["login_user"] == null) 
    {
        // Option 1: 404 Page not found (hide the page existence)
        Response.StatusCode = 404;
        return;

        // Option 2: 401 Unauthorized (reveal that auth is required)
        // Response.StatusCode = 401;
        // return;
    }

    // ============================================
    // Step 2: Initialize default values
    // ============================================
    DateTime reportDate = DateTime.Today;  // Default to today
    int auto_print = 1;                    // Auto-print by default

    // ============================================
    // Step 3: Obtain Request Data
    // ============================================

    // Method 1: Query String & Form Post
    // Example: /apiDailySales?date=2025-01-02&autoprint=1

    // Parse the date parameter
    if (!string.IsNullOrEmpty(Request["date"]))
    {
        if (!DateTime.TryParse(Request["date"], out reportDate))
        {
            // Invalid date format - return 400 Bad Request
            Response.StatusCode = 400;
            return;
        }
    }

    // Parse the autoprint parameter
    if (Request["autoprint"] != null) 
    {
        int.TryParse(Request["autoprint"] + "", out auto_print);
    }

    // Method 2: JSON body request (for AJAX calls)
    // Uncomment if you prefer JSON body:
    /*
    string jsonBody;
    using (var reader = new StreamReader(Request.InputStream, Encoding.UTF8))
    {
        jsonBody = reader.ReadToEnd();
    }
    var reportRequest = JsonSerializer.Deserialize<ReportRequest>(jsonBody);
    reportDate = reportRequest.ReportDate;
    auto_print = reportRequest.AutoPrint;
    */

    // ============================================
    // Step 4: Generate the Report HTML document
    // ============================================
    var html = engineDailySales.GenerateReport(reportDate, auto_print);

    // Check if report generation failed
    if (html == null)
    {
        // 400 Bad Request
        Response.StatusCode = 400;
        return;
    }

    // ============================================
    // Step 5: Output the HTML to the browser
    // ============================================
    Response.Write(html);
}

Frontend Print Request

Auto-Print, No Preview Needed

<!-- Date picker for selecting report date -->
<input type="date" id="reportDate" value="2025-01-02" />

<!-- Print button -->
<button type="button" onclick="printReport();">Print Daily Sales Report</button>

<!-- Hidden iframe for printing -->
<iframe id="frameprint" src="" style="height:0; width:0; border:none; overflow:hidden;"></iframe>

<script>

    // ============================================
    // Global Configuration
    // ============================================
    const urlApi = "/apiDailySales";
    let auto_print = 1;  // Enable auto-print
    let frameprint = document.querySelector("#frameprint");

    // ============================================
    // Print function - triggers the report generation
    // ============================================
    function printReport() {
        // Get the selected date from the date picker
        let reportDate = document.querySelector("#reportDate").value;

        // Build the API URL with parameters
        let url = `${urlApi}?date=${reportDate}&autoprint=${auto_print}`;

        // Load the report into the hidden iframe
        // The report will auto-print due to the window.onload script
        frameprint.src = url;
    }

    // ============================================
    // Listen for messages from the iframe
    // (e.g., "no-data-found" error)
    // ============================================
    document.addEventListener('DOMContentLoaded', function() {
        window.addEventListener('message', handleIframeMessages);
    });

    function handleIframeMessages(event) {
        // Security: Verify the message origin
        if (event.origin !== window.location.origin) return;

        // Handle different message types
        switch(event.data) {
            case 'no-data-found':
                // Show user-friendly notification
                showNotification('No sales data found for the selected date', 'warning');
                // Clear the iframe
                document.querySelector("#frameprint").src = '';
                break;

            default:
                console.log('Unknown message from iframe:', event.data);
        }
    }

</script>

Print Preview, No Auto Print

Show the print preview in iframe:

<style>
/* ============================================
   Print Preview Overlay Styles
   ============================================ */
.div-print-preview {
    position: fixed;
    top: 20px;
    left: 20px;
    right: 20px;
    bottom: 20px;
    border: 2px solid gray;
    padding: 10px;
    display: none;           /* Hidden by default */
    z-index: 999999;         /* Always on top */
    background: white;
}

.div-print-preview-button {
    background: #35b1d4;
    color: white;
    display: inline-block;
    padding: 10px 20px;
    border: none;
    cursor: pointer;
    margin-right: 10px;
}

.div-print-preview-button:hover {
    background: #2a9cbf;
}

.div-print-preview-iframe {
    position: absolute;
    top: 60px;
    left: 10px;
    right: 10px;
    bottom: 10px;
    width: auto;
    height: auto;
    border: 1px solid gray;
}
</style>

<!-- Date picker -->
<input type="date" id="reportDate" value="2025-01-02" />

<!-- Preview button -->
<button type="button" onclick="showPrintPreview();">Print Preview</button>

<!-- Print Preview Container -->
<div id="divPrintPreview" class="div-print-preview">
    <button type="button" class="div-print-preview-button" onclick="printIframe();">Print</button>
    <button type="button" class="div-print-preview-button" onclick="closePrintPreview();">Close</button>
    <iframe id="frameprint" class="div-print-preview-iframe"></iframe>
</div>

<script>

// ============================================
// Global Configuration
// ============================================
const urlApi = "/apiDailySales";
let auto_print = 0;  // Disable auto-print for preview mode
let divPrintPreview = document.querySelector("#divPrintPreview");
let frameprint = document.querySelector("#frameprint");

// ============================================
// Show the print preview overlay
// ============================================
function showPrintPreview() {
    // Get the selected date
    let reportDate = document.querySelector("#reportDate").value;

    // Build URL (autoprint=0 for preview)
    let url = `${urlApi}?date=${reportDate}&autoprint=${auto_print}`;

    // Load report into iframe
    frameprint.src = url;

    // Show the preview container
    divPrintPreview.style.display = "block";
}

// ============================================
// Print the iframe content
// ============================================
function printIframe() {
    // Call print() on the iframe's window object
    // This prints only the iframe content, not the parent page
    frameprint.contentWindow.print();
}

// ============================================
// Close the print preview overlay
// ============================================
function closePrintPreview() {
    // Hide the preview container
    divPrintPreview.style.display = "none";

    // Clear the iframe to free memory
    frameprint.src = "";
}

</script>

The CSS for The Report

body {
	font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
	font-size: 11px;
	line-height: 1.4;
	color: #333;
	margin: 0;
	padding: 0;
}

.page {
	padding-top: 10mm;
	width: 175mm;
	margin: auto;
	background: white;
}

/* ========================================
   Header Section (20mm height)
   ======================================== */
.report-header {
	height: 20mm;
	display: flex;
	justify-content: space-between;
	align-items: center;
	border-bottom: 2px solid #2c5aa0;
	padding: 0 5mm;
	margin-bottom: 3mm;
}

.report-title {
	font-size: 18px;
	font-weight: bold;
	color: #2c5aa0;
	text-transform: uppercase;
	letter-spacing: 1px;
}

.report-date {
	font-size: 12px;
	color: #555;
}

.report-meta {
	text-align: right;
}

.report-meta-item {
	font-size: 10px;
	color: #666;
	margin-bottom: 2px;
}

.report-meta-value {
	font-weight: bold;
	color: #333;
}

.report-summary {
	display: flex;
	gap: 15mm;
	justify-content: flex-end;
}

.summary-box {
	text-align: center;
	padding: 2mm 4mm;
	background: #f8f9fa;
	border-radius: 3px;
	border: 1px solid #e0e0e0;
}

.summary-label {
	font-size: 9px;
	color: #666;
	text-transform: uppercase;
}

.summary-value {
	font-size: 14px;
	font-weight: bold;
	color: #2c5aa0;
}

/* ========================================
   Report Table Section (250mm height)
   ======================================== */
.report-table-container {
	height: 250mm;
	padding: 0 5mm;
}

.report-table {
	width: 100%;
	border-collapse: collapse;
	font-size: 10px;
}

/* Table Header (8mm height) */
.report-table thead tr {
	height: 8mm;
}
.report-table thead th {
	background: #2c5aa0;
	color: white;
	padding: 0 2mm;
	text-align: left;
	font-weight: 600;
	text-transform: uppercase;
	font-size: 9px;
	letter-spacing: 0.5px;
	border: 1px solid #1e4a8a;
}

/* Table Body */
.report-table tbody tr {
	height: 8mm;
}
.report-table tbody td {
	padding: 0 2mm;
	border-bottom: 1px solid #e0e0e0;
	border-left: 1px solid #e0e0e0;
	border-right: 1px solid #e0e0e0;
	vertical-align: middle;
}

.report-table tbody tr:nth-child(even) {
	background: #fafafa;
}

.report-table tbody tr:hover {
	background: #f0f7ff;
}

/* Column Widths */
.col-no { 
	width: 6mm; 
	text-align: center; 
}

.col-invoice { 
	width: 26mm; 
	font-family: 'Consolas', 'Courier New', monospace;
}

.col-customer { 
	width: auto; 
}

.col-total { 
	width: 20mm; 
	text-align: right; 
}

.col-paid { 
	width: 20mm; 
	text-align: right; 
}

.col-status { 
	width: 20mm; 
	text-align: center; 
}

/* Status Badges */
.status-badge {
	display: inline-block;
	padding: 1mm 3mm;
	border-radius: 3px;
	font-size: 9px;
	font-weight: 600;
	text-transform: uppercase;
}

.status-paid {
	background: #d4edda;
	color: #155724;
}

.status-partial {
	background: #fff3cd;
	color: #856404;
}

.status-unpaid {
	background: #f8d7da;
	color: #721c24;
}

/* Amount Formatting */
.amount {
	font-family: 'Consolas', 'Courier New', monospace;
}

.amount-positive {
	color: #28a745;
}

.amount-zero {
	color: #dc3545;
}

/* ============================================
   Page Settings
   - A4 portrait orientation
   - No margins (to hide browser print headers/footers)
   ============================================ */
@page {
    size: A4 portrait;
    margin: 0;

    /* Remove default browser print headers and footers */
    @top-center { content: none; }
    @bottom-center { content: none; }
}

Comparison: Part 2 vs Part 3 Pagination

Let’s compare the pagination complexity:

AspectPart 2 (Semi-Dynamic)Part 3 (Full Dynamic)
Height CalculationMust calculate each row height based on contentNot needed – fixed rows per page
Footer HandlingFooter only on last page, requires trackingNo footer to worry about
Pagination LogicComplex – accumulate heights, track available spaceSimple – totalRows / rowsPerPage
Page Break DecisionWhen accumulated height exceeds available spaceEvery N rows
Code Complexity~100 lines for pagination~10 lines for pagination

The key insight: When you don’t have a footer that must appear on the last page only, pagination becomes trivially simple!

Extending to Other Reports

The same pattern works for any data grid report:

Monthly Sales Report:

// Just change the data source and maybe adjust ROWS_PER_PAGE
const int ROWS_PER_PAGE = 25;  // Might want fewer rows for more columns
var report = Database.GetMonthlySalesReport(year, month);

Inventory Report:

// Different columns, same pattern
public class InventoryRow
{
    public string SKU { get; set; }
    public string ProductName { get; set; }
    public int QuantityOnHand { get; set; }
    public int ReorderLevel { get; set; }
    public string Status { get; set; }  // "OK", "Low", "Out of Stock"
}

Customer List:

// Even simpler - just customer data
public class CustomerRow
{
    public int RowNo { get; set; }
    public string CustomerCode { get; set; }
    public string CustomerName { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
    public decimal TotalPurchases { get; set; }
}

Summary

In this part, we covered:

  1. Full Dynamic Layout — Simpler than semi-dynamic because there’s no footer positioning
  2. Simple Pagination — Just divide total rows by rows-per-page
  3. Data Model — Clean separation of report metadata and row data
  4. Generator Engine — Straightforward loop with Skip/Take for pagination
  5. Status Badges — Visual indicators for data states (Paid/Partial/Unpaid)
  6. Professional Styling — Clean, print-friendly CSS with alternating row colors

The key difference from Part 2 is the absence of complex footer handling. When every page has the same structure (header + data rows), pagination becomes a simple math problem.

Feature Photo by Artem Podrez:
https://www.pexels.com/photo/close-up-shot-of-a-person-holding-printed-papers-8512120/