طراحی و چاپ دقیق اسناد A4 در وب با ASP.NET Core MVC
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۷/۱۵ ۱۳:۳۵
آدرس: www.dntips.ir
چکیده: چاپ یک سند از طریق مرورگر، در نگاه اول ساده به نظر میرسد؛ اما زمانی که خروجی باید با ابعاد فیزیکی مشخص، روی کاغذ A4 و با دقت مناسب چاپ شود، موضوع پیچیدهتر میشود. تفاوت میان اندازههای CSS و ابعاد فیزیکی کاغذ، حاشیههای مرورگر و چاپگر،page-break، نحوه محاسبهwidthوheight،paddingوborderو همچنین تنظیمات چاپگر میتواند باعث شود خروجی نهایی با چیزی که روی صفحه نمایش دیده میشود تفاوت قابلتوجهی داشته باشد. در این مقاله، روشی برای طراحی یک خروجی چاپی چهارقسمتی روی کاغذ A4 با استفاده از ASP.NET Core MVC بررسی میشود. تمرکز اصلی مقاله بر نکاتی است که برای دستیابی به چاپ دقیق و قابل پیشبینی اهمیت دارند؛ از جمله استفاده از واحدmm، کنترل حاشیه، مرکزچین کردن محتوا، جلوگیری از ایجاد صفحه سفید اضافی، مدیریتpage-breakو انتخاب روش مناسب برای چیدمان عناصر.
@media print)@pagewidth: 210mm; height: 297mm;
┌───────────────────────────────┐ │ │ │ │ سند ۱ │ سند ۲ │ │ │ │ ├─────────────┼─────────────────┤ │ │ │ │ سند ۳ │ سند ۴ │ │ │ │ └───────────────────────────────┘
210mm × 297mm
عرض: 210 - 5 - 5 = 200mm ارتفاع: 297 - 5 - 5 = 287mm
عرض: 100mm ارتفاع: 143.5mm
public class DocumentItem
{
public string Title { get; set; } = "";
public string Description { get; set; } = "";
public string Date { get; set; } = "";
public string From { get; set; } = "";
public string To { get; set; } = "";
}public class PrintPageViewModel
{
public List<DocumentItem?> Items { get; set; } = new();
}var pages = new List<PrintPageViewModel>();
for (int i = 0; i < items.Count; i += 4)
{
var page = new PrintPageViewModel();
for (int j = 0; j < 4 && i + j < items.Count; j++)
page.Items.Add(items[i + j]);
while (page.Items.Count < 4)
page.Items.Add(null);
pages.Add(page);
}
return View(pages);هر چهار آیتم متعلق به یک صفحه چاپ هستند.
page-break نیز بسیار سادهتر میشود.tableانتخاب مناسبی است؟Flexbox یا CSS Grid پیشنهاد میشود. اما خروجی چاپی دقیق، خصوصاً زمانی که سازگاری با مرورگرهای قدیمی نیز اهمیت داشته باشد، شرایط متفاوتی دارد. در این مثال ما یک Layout کاملاً جدولی داریم:2 ستون × 2 ردیف
<table>
<tr>
<td>...</td>
<td>...</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
</tr>
</table>table برای داده نیست؛ بلکه از رفتار تثبیتشده جدول برای یک Layout ثابت چاپی استفاده میکنیم.@page {
size: A4 portrait;
margin: 0;
}@media print {
html,
body {
margin: 0;
padding: 0;
}
}@page و عنصر HTML ما دو مفهوم متفاوت هستند.@page درباره صفحه چاپ صحبت میکند؛ در حالی که .print-page یک عنصر HTML است که محتوای خود را درون آن قرار میدهیم.@page { margin: 0; }کافی نیست؟@page {
size: A4;
margin: 5mm;
}@page {
margin: 5mm;
}.print-page {
padding: 5mm;
}@page را بدون margin تعریف کنیم و حاشیه داخلی را خودمان کنترل کنیم:@page {
size: A4 portrait;
margin: 0;
}
.print-page {
width: 210mm;
height: 297mm;
padding: 5mm;
box-sizing: border-box;
}A4 ┌─────────────────────────────────┐ │ 5mm │ │ ┌─────────────────────────┐ │ │ │ │ │ │ │ Content Area │ │ │ │ │ │ │ └─────────────────────────┘ │ │ 5mm │ └─────────────────────────────────┘
210 - 10 = 200mm
297 - 10 = 287mm
box-sizing: border-boxbox-sizing است. فرض کنید بنویسیم:.card {
width: 100mm;
padding: 5mm;
border: 1mm solid;
}content-box استفاده شود، width فقط مربوط به محتوای داخلی خواهد بود. در نتیجه اندازه واقعی عنصر بیشتر از 100mm خواهد شد. برای خروجی چاپی ثابت بهتر است از:box-sizing: border-box;
.card {
width: 100mm;
height: 143.5mm;
padding: 5mm;
border: 1px solid #333;
box-sizing: border-box;
}padding و border، همان:100mm × 143.5mm
@page {
size: A4 portrait;
margin: 0;
}
@media print {
html,
body {
margin: 0;
padding: 0;
background: #fff;
}
.print-page {
width: 210mm;
height: 297mm;
padding: 5mm;
box-sizing: border-box;
page-break-after: always;
overflow: hidden;
}
.print-page:last-child {
page-break-after: auto;
}
.page-table {
width: 200mm;
height: 287mm;
margin-left: auto;
margin-right: auto;
border-collapse: collapse;
table-layout: fixed;
}
.card-cell {
width: 100mm;
height: 143.5mm;
padding: 0;
vertical-align: top;
}
.card {
width: 100mm;
height: 143.5mm;
box-sizing: border-box;
padding: 5mm;
border: 3px double #333;
direction: rtl;
text-align: right;
overflow: hidden;
}
}display: flex; display: grid; break-inside; aspect-ratio;
.page-table {
width: 200mm;
margin: 0 auto;
}210mm ┌──────────────────────────────────┐ │ 5mm ┌────────────────────────┐ │ │ │ │ │ │ │ 200mm │ │ │ │ │ │ │ └────────────────────────┘ │ │ 5mm │ └──────────────────────────────────┘
direction: rtl;
direction: rtl باعث شود جدول در مرکز صفحه قرار بگیرد. برای مرورگرهای بسیار قدیمی که در برخی شرایط چاپی با margin: auto رفتار مطلوبی ندارند، میتوان از یک wrapper جدولی نیز استفاده کرد:<table class="print-wrapper">
<tr>
<td align="center">
<table class="page-table">
...
</table>
</td>
</tr>
</table>@model List<PrintPageViewModel>
@foreach (var page in Model)
{
<div class="print-page">
<table class="page-table">
<tr>
@for (var i = 0; i < 2; i++)
{
<td class="card-cell">
@if (page.Items[i] != null)
{
<div class="card">
<h2>@page.Items[i]!.Title</h2>
<p>
@page.Items[i]!.Description
</p>
<p>
تاریخ:
@page.Items[i]!.Date
</p>
<p>
فرستنده:
@page.Items[i]!.From
</p>
<p>
گیرنده:
@page.Items[i]!.To
</p>
</div>
}
</td>
}
</tr>
<tr>
@for (var i = 2; i < 4; i++)
{
<td class="card-cell">
@if (page.Items[i] != null)
{
<div class="card">
<h2>@page.Items[i]!.Title</h2>
<p>
@page.Items[i]!.Description
</p>
<p>
تاریخ:
@page.Items[i]!.Date
</p>
<p>
فرستنده:
@page.Items[i]!.From
</p>
<p>
گیرنده:
@page.Items[i]!.To
</p>
</div>
}
</td>
}
</tr>
</table>
</div>
}صفحه ۱ → ۴ آیتم صفحه ۲ → ۴ آیتم صفحه ۳ → ۱ آیتم
page-break-after: always;
Page 1 Page 2 Page 3 Blank Page
page-break-after: always داشته باشد:.print-page:last-child {
page-break-after: auto;
}public class PrintPageViewModel
{
public List<DocumentItem?> Items { get; set; } = new();
public bool IsLastPage { get; set; }
}<div class="print-page @(page.IsLastPage ? "last-page" : "")">
.print-page {
page-break-after: always;
}
.print-page.last-page {
page-break-after: auto;
}page-break نیست. چند علت متداول وجود دارد.page-break-after: always;
height: 297mm; padding: 5mm;
box-sizing: border-box;
table، tr، td و عناصر داخلی میتوانند در اثر border، padding و محتوای طولانی باعث افزایش اندازه واقعی Layout شوند.100% چاپ کند، Layout فیزیکی دیگر با محاسبات CSS یکسان نخواهد بود.height: auto;
143.5mm
Description بسیار طولانی شود، ممکن است متن از کارت خارج شود یا ارتفاع کارت افزایش یابد. برای فرمهای ثابت معمولاً بهتر است:.card {
height: 143.5mm;
overflow: hidden;
}overflow: hidden نباید برای پنهان کردن بیدلیل اطلاعات مهم استفاده شود.mmاستفاده کنیم؟px
mm cm in
width: 100mm; height: 143.5mm;
استفاده از mm به معنی تضمین چاپ دقیق فیزیکی نیست.| تنظیم | مقدار پیشنهادی |
| Paper Size | A4 |
| Orientation | Portrait |
| Scale | 100% / Actual Size |
| Fit to Page | خاموش |
| Headers/Footers | خاموش، در صورت عدم نیاز |
| Background Graphics | در صورت نیاز روشن |
| Printer Margins | مطابق قابلیت چاپگر |
Fit to page Shrink to fit Scale to fit
97%
@page {
margin: 0;
}.print-page {
padding: 5mm;
}.card {
margin: 5mm;
}Printer ↓ Paper ↓ @page ↓ .print-page ↓ .page-table ↓ .card-cell ↓ .card ↓ content
@page {
margin: 0;
}Borderless Printing
5mm
10mm
table width height padding margin border overflow page-break-after
grid flex break-inside
«مدرنتر بودن CSS» الزاماً به معنی «قابل اعتمادتر بودن در چاپ» نیست.
Controller
│
▼
List<DocumentItem>
│
▼
Group by 4
│
▼
List<PrintPageViewModel>
│
▼
Razor View
│
▼
A4 Print Page
│
├── Card 1
├── Card 2
├── Card 3
└── Card 4چه اطلاعاتی در هر صفحه قرار میگیرد؟
این اطلاعات چگونه روی کاغذ قرار میگیرند؟
این Layout چگونه برای رسانه چاپی اندازهگذاری شود؟
5 آیتم
Page 1 → 4 items Page 2 → 1 item
عرض کاغذ ≈ 210mm ارتفاع کاغذ ≈ 297mm
حاشیه چپ ≈ 5mm حاشیه راست ≈ 5mm
100mm × 143.5mm
100%
┌───────────────┐ │ A4 │ │ │ │ ┌─────┬────┐ │ │ │ 1 │ 2 │ │ │ ├─────┼────┤ │ │ │ 3 │ 4 │ │ │ └─────┴────┘ │ │ │ └───────────────┘
mm مدل شود.@page مربوط به صفحه چاپ است و با حاشیه داخلی عناصر HTML تفاوت دارد.@page را بدون حاشیه تعریف و Safe Margin را داخل .print-page ایجاد کرد.box-sizing: border-box برای جلوگیری از افزایش ناخواسته ابعاد بسیار مهم است.direction: rtl جهت متن را کنترل میکند، نه مرکزچین شدن Layout.table میتواند حتی از تکنیکهای مدرنتر نیز قابل پیشبینیتر باشد.page-break-after باید با دقت مدیریت شود، خصوصاً در آخرین صفحه؛ تا بیجهت یک صفحهی خالی ایجاد نشود.page-break، Overflow، ابعاد واقعی عناصر و Scaling است.