Add project files.
This commit is contained in:
+330
@@ -0,0 +1,330 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BoxPacker3D
|
||||
{
|
||||
public static class PdfExporter
|
||||
{
|
||||
private const float PageW = 842f, PageH = 595f, Margin = 36f;
|
||||
|
||||
public static void Export(string path,
|
||||
List<BoxSolution> solutions,
|
||||
List<Bitmap> overviews,
|
||||
List<Bitmap> layerBitmaps,
|
||||
List<(double YMin, double YMax)> layers,
|
||||
double wallPad, double itemPad,
|
||||
List<Item> unpackedItems = null)
|
||||
{
|
||||
var pages = new List<byte[]>();
|
||||
|
||||
// Layer pages
|
||||
foreach (var bmp in layerBitmaps)
|
||||
pages.Add(BitmapToJpeg(bmp));
|
||||
|
||||
// Overview pages
|
||||
foreach (var bmp in overviews)
|
||||
pages.Add(BitmapToJpeg(bmp));
|
||||
|
||||
// Add unpacked items text page if needed
|
||||
bool hasUnpackedPage = unpackedItems != null && unpackedItems.Count > 0;
|
||||
|
||||
using (var fs = new FileStream(path, FileMode.Create))
|
||||
{
|
||||
var w = new PdfWriter(fs);
|
||||
w.Raw("%PDF-1.4\n%\xe2\xe3\xcf\xd3\n");
|
||||
|
||||
int n = pages.Count + (hasUnpackedPage ? 1 : 0); // Total pages including text page
|
||||
int basePage = 3;
|
||||
int baseContent = basePage + n;
|
||||
int baseImage = baseContent + n;
|
||||
int objFont1 = baseImage + pages.Count; // After image objects
|
||||
int objFont2 = objFont1 + 1;
|
||||
|
||||
var offsets = new long[objFont2 + 1];
|
||||
|
||||
// Object 1: Catalog
|
||||
offsets[1] = w.Pos;
|
||||
w.Obj(1); w.Raw("<< /Type /Catalog /Pages 2 0 R >>\n"); w.EndObj();
|
||||
|
||||
// Object 2: Pages
|
||||
var kids = string.Join(" ", Enumerable.Range(basePage, n).Select(i => $"{i} 0 R"));
|
||||
offsets[2] = w.Pos;
|
||||
w.Obj(2);
|
||||
w.Raw($"<< /Type /Pages /Kids [{kids}] /Count {n} >>\n");
|
||||
w.EndObj();
|
||||
|
||||
// Per-page objects (image pages)
|
||||
for (int i = 0; i < pages.Count; i++)
|
||||
{
|
||||
bool isLayer = (i < layerBitmaps.Count);
|
||||
string pageTitle;
|
||||
if (isLayer)
|
||||
{
|
||||
int layIdx = i;
|
||||
pageTitle = $"Vrstva {layIdx + 1} / {layerBitmaps.Count}" +
|
||||
(layIdx < layers.Count ? $" (Y: {layers[layIdx].YMin:F1} – {layers[layIdx].YMax:F1} mm)" : "");
|
||||
}
|
||||
else
|
||||
{
|
||||
int boxIdx = i - layerBitmaps.Count;
|
||||
var sol = solutions[boxIdx];
|
||||
pageTitle = $"Škatuľa {boxIdx + 1} / {solutions.Count} " +
|
||||
$"({sol.Box.W}×{sol.Box.D}×{sol.Box.H} mm)";
|
||||
}
|
||||
|
||||
int imgObj = baseImage + i;
|
||||
int contentObj= baseContent + i;
|
||||
int pageObj = basePage + i;
|
||||
|
||||
var imgBytes = pages[i];
|
||||
int imgW, imgH;
|
||||
using (var ms = new MemoryStream(imgBytes))
|
||||
using (var bmp = new Bitmap(ms))
|
||||
{ imgW = bmp.Width; imgH = bmp.Height; }
|
||||
|
||||
float headerH = 52f, footerH = 22f;
|
||||
float availW = PageW - 2 * Margin;
|
||||
float availH = PageH - headerH - footerH - Margin;
|
||||
float sc = Math.Min(availW / imgW, availH / imgH);
|
||||
float scaledW = imgW * sc, scaledH = imgH * sc;
|
||||
float imgX = Margin + (availW - scaledW) / 2;
|
||||
float imgY = PageH - headerH - scaledH - 4;
|
||||
|
||||
var cs = new StringBuilder();
|
||||
// White background
|
||||
cs.AppendLine("1 1 1 rg");
|
||||
cs.AppendLine($"0 0 {PageW:F2} {PageH:F2} re f");
|
||||
// Header bar
|
||||
cs.AppendLine("0.06 0.35 0.72 rg");
|
||||
cs.AppendLine($"0 {PageH - 44:F2} {PageW:F2} 44 re f");
|
||||
// Title
|
||||
cs.AppendLine("1 1 1 rg BT /F1 14 Tf");
|
||||
cs.AppendLine($"{Margin:F2} {PageH - 30:F2} Td");
|
||||
cs.AppendLine($"({EscPdf(pageTitle)}) Tj ET");
|
||||
// Date
|
||||
cs.AppendLine("0.70 0.80 0.90 rg BT /F2 8 Tf");
|
||||
cs.AppendLine($"{PageW - 200:F2} {PageH - 30:F2} Td");
|
||||
cs.AppendLine($"(Strana {i + 1} / {n} {DateTime.Now:dd.MM.yyyy}) Tj ET");
|
||||
// Box info
|
||||
if (isLayer && i < solutions.Count)
|
||||
{
|
||||
var sol = solutions[0]; // assume first box for layer pages
|
||||
cs.AppendLine("0.40 0.40 0.50 rg BT /F2 8 Tf");
|
||||
cs.AppendLine($"{Margin:F2} {PageH - 44:F2} Td");
|
||||
string infoLine = $"Skatula: {sol.Box.W}×{sol.Box.H}×{sol.Box.D} mm | " +
|
||||
$"Stenova izolacia: {wallPad} mm | Medikusova: {itemPad} mm";
|
||||
cs.AppendLine($"({EscPdf(infoLine)}) Tj ET");
|
||||
}
|
||||
// Image
|
||||
cs.AppendLine("q");
|
||||
cs.AppendLine($"{scaledW:F2} 0 0 {scaledH:F2} {imgX:F2} {imgY:F2} cm");
|
||||
cs.AppendLine($"/Im{i} Do Q");
|
||||
// Footer
|
||||
cs.AppendLine("0.35 0.35 0.45 rg BT /F2 7 Tf");
|
||||
cs.AppendLine($"{Margin:F2} 10 Td");
|
||||
cs.AppendLine("(Generovane programom 3D Box Packer) Tj ET");
|
||||
|
||||
byte[] csBytes = Encoding.Latin1.GetBytes(cs.ToString());
|
||||
|
||||
// Page
|
||||
offsets[pageObj] = w.Pos;
|
||||
w.Obj(pageObj);
|
||||
w.Raw($"<< /Type /Page /Parent 2 0 R\n");
|
||||
w.Raw($" /MediaBox [0 0 {PageW:F2} {PageH:F2}]\n");
|
||||
w.Raw($" /Contents {contentObj} 0 R\n");
|
||||
w.Raw($" /Resources << /Font << /F1 {objFont1} 0 R /F2 {objFont2} 0 R >>");
|
||||
w.Raw($" /XObject << /Im{i} {imgObj} 0 R >> >>\n>>\n");
|
||||
w.EndObj();
|
||||
|
||||
// Content
|
||||
offsets[contentObj] = w.Pos;
|
||||
w.Obj(contentObj);
|
||||
w.Raw($"<< /Length {csBytes.Length} >>\nstream\n");
|
||||
w.Bytes(csBytes);
|
||||
w.Raw("\nendstream\n");
|
||||
w.EndObj();
|
||||
|
||||
// Image
|
||||
offsets[imgObj] = w.Pos;
|
||||
w.Obj(imgObj);
|
||||
w.Raw($"<< /Type /XObject /Subtype /Image /Width {imgW} /Height {imgH}");
|
||||
w.Raw($" /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode");
|
||||
w.Raw($" /Length {imgBytes.Length} >>\nstream\n");
|
||||
w.Bytes(imgBytes);
|
||||
w.Raw("\nendstream\n");
|
||||
w.EndObj();
|
||||
}
|
||||
|
||||
// Unpacked items text page (if any)
|
||||
if (hasUnpackedPage)
|
||||
{
|
||||
int pageIdx = pages.Count;
|
||||
int pageObj = basePage + pageIdx;
|
||||
int contentObj = baseContent + pageIdx;
|
||||
|
||||
// Group unpacked items by name
|
||||
var grouped = unpackedItems
|
||||
.GroupBy(it => it.Name)
|
||||
.Select(g => new {
|
||||
Name = g.Key,
|
||||
Count = g.Count(),
|
||||
W = g.First().W,
|
||||
H = g.First().H,
|
||||
D = g.First().D
|
||||
})
|
||||
.OrderBy(x => x.Name)
|
||||
.ToList();
|
||||
|
||||
var cs = new StringBuilder();
|
||||
// White background
|
||||
cs.AppendLine("1 1 1 rg");
|
||||
cs.AppendLine($"0 0 {PageW:F2} {PageH:F2} re f");
|
||||
|
||||
// Header bar (red for warning)
|
||||
cs.AppendLine("0.83 0.18 0.18 rg");
|
||||
cs.AppendLine($"0 {PageH - 44:F2} {PageW:F2} 44 re f");
|
||||
|
||||
// Title
|
||||
cs.AppendLine("1 1 1 rg BT /F1 14 Tf");
|
||||
cs.AppendLine($"{Margin:F2} {PageH - 30:F2} Td");
|
||||
cs.AppendLine($"(NEZABALENE KUSY) Tj ET");
|
||||
|
||||
// Date
|
||||
cs.AppendLine("1 1 1 rg BT /F2 8 Tf");
|
||||
cs.AppendLine($"{PageW - 200:F2} {PageH - 30:F2} Td");
|
||||
cs.AppendLine($"(Strana {n} / {n} {DateTime.Now:dd.MM.yyyy}) Tj ET");
|
||||
|
||||
// Summary
|
||||
cs.AppendLine("0.20 0.20 0.30 rg BT /F1 11 Tf");
|
||||
cs.AppendLine($"{Margin:F2} {PageH - 80:F2} Td");
|
||||
cs.AppendLine($"(Nasledujuce kusy sa nezmestili do skatul:) Tj ET");
|
||||
|
||||
// Table header
|
||||
float y = PageH - 120;
|
||||
cs.AppendLine("0.85 0.85 0.90 rg");
|
||||
cs.AppendLine($"{Margin:F2} {y:F2} {PageW - 2*Margin:F2} 20 re f");
|
||||
|
||||
cs.AppendLine("0.25 0.25 0.35 rg BT /F1 9 Tf");
|
||||
cs.AppendLine($"{Margin + 10:F2} {y + 6:F2} Td");
|
||||
cs.AppendLine("(Nazov) Tj");
|
||||
cs.AppendLine($"{250:F2} {0:F2} Td (Rozmery \\(mm\\)) Tj");
|
||||
cs.AppendLine($"{160:F2} {0:F2} Td (Pocet) Tj ET");
|
||||
|
||||
y -= 25;
|
||||
|
||||
// Table rows
|
||||
int rowNum = 0;
|
||||
foreach (var item in grouped)
|
||||
{
|
||||
if (y < 60) break; // Don't overflow page
|
||||
|
||||
// Alternating row background
|
||||
if (rowNum % 2 == 0)
|
||||
{
|
||||
cs.AppendLine("0.96 0.96 0.98 rg");
|
||||
cs.AppendLine($"{Margin:F2} {y:F2} {PageW - 2*Margin:F2} 18 re f");
|
||||
}
|
||||
|
||||
cs.AppendLine("0.20 0.20 0.30 rg BT /F2 8 Tf");
|
||||
cs.AppendLine($"{Margin + 10:F2} {y + 5:F2} Td");
|
||||
cs.AppendLine($"({EscPdf(item.Name)}) Tj");
|
||||
cs.AppendLine($"{250:F2} {0:F2} Td ({item.W:F0} x {item.H:F0} x {item.D:F0}) Tj");
|
||||
cs.AppendLine($"{160:F2} {0:F2} Td ({item.Count}) Tj ET");
|
||||
|
||||
y -= 18;
|
||||
rowNum++;
|
||||
}
|
||||
|
||||
if (grouped.Count > rowNum)
|
||||
{
|
||||
cs.AppendLine("0.50 0.50 0.60 rg BT /F2 7 Tf");
|
||||
cs.AppendLine($"{Margin + 10:F2} {y:F2} Td");
|
||||
cs.AppendLine($"(... a dalsich {grouped.Count - rowNum} typov kusov) Tj ET");
|
||||
}
|
||||
|
||||
// Footer
|
||||
cs.AppendLine("0.35 0.35 0.45 rg BT /F2 7 Tf");
|
||||
cs.AppendLine($"{Margin:F2} 10 Td");
|
||||
cs.AppendLine("(Generovane programom 3D Box Packer) Tj ET");
|
||||
|
||||
byte[] csBytes = Encoding.Latin1.GetBytes(cs.ToString());
|
||||
|
||||
// Page
|
||||
offsets[pageObj] = w.Pos;
|
||||
w.Obj(pageObj);
|
||||
w.Raw($"<< /Type /Page /Parent 2 0 R\n");
|
||||
w.Raw($" /MediaBox [0 0 {PageW:F2} {PageH:F2}]\n");
|
||||
w.Raw($" /Contents {contentObj} 0 R\n");
|
||||
w.Raw($" /Resources << /Font << /F1 {objFont1} 0 R /F2 {objFont2} 0 R >> >>\n>>\n");
|
||||
w.EndObj();
|
||||
|
||||
// Content
|
||||
offsets[contentObj] = w.Pos;
|
||||
w.Obj(contentObj);
|
||||
w.Raw($"<< /Length {csBytes.Length} >>\nstream\n");
|
||||
w.Bytes(csBytes);
|
||||
w.Raw("\nendstream\n");
|
||||
w.EndObj();
|
||||
}
|
||||
|
||||
// Fonts
|
||||
offsets[objFont1] = w.Pos;
|
||||
w.Obj(objFont1);
|
||||
w.Raw("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>\n");
|
||||
w.EndObj();
|
||||
|
||||
offsets[objFont2] = w.Pos;
|
||||
w.Obj(objFont2);
|
||||
w.Raw("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\n");
|
||||
w.EndObj();
|
||||
|
||||
// xref
|
||||
long xrefPos = w.Pos;
|
||||
w.Raw($"xref\n0 {offsets.Length}\n");
|
||||
w.Raw("0000000000 65535 f \n");
|
||||
for (int i = 1; i < offsets.Length; i++)
|
||||
w.Raw($"{offsets[i]:D10} 00000 n \n");
|
||||
|
||||
w.Raw($"trailer\n<< /Size {offsets.Length} /Root 1 0 R >>\n");
|
||||
w.Raw($"startxref\n{xrefPos}\n%%EOF\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] BitmapToJpeg(Bitmap bmp)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
var enc = GetJpegEncoder();
|
||||
var prms = new EncoderParameters(1);
|
||||
prms.Param[0] = new EncoderParameter(
|
||||
System.Drawing.Imaging.Encoder.Quality, (long)88);
|
||||
bmp.Save(ms, enc, prms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static ImageCodecInfo GetJpegEncoder() =>
|
||||
ImageCodecInfo.GetImageEncoders().First(c => c.MimeType == "image/jpeg");
|
||||
|
||||
private static string EscPdf(string s) =>
|
||||
s.Replace("\\", "\\\\").Replace("(", "\\(").Replace(")", "\\)");
|
||||
|
||||
private class PdfWriter
|
||||
{
|
||||
private readonly FileStream _fs;
|
||||
public long Pos => _fs.Position;
|
||||
|
||||
public PdfWriter(FileStream fs) { _fs = fs; }
|
||||
|
||||
public void Raw(string s) { var b = Encoding.Latin1.GetBytes(s); _fs.Write(b, 0, b.Length); }
|
||||
public void Bytes(byte[] b) { _fs.Write(b, 0, b.Length); }
|
||||
public void Obj(int n) { Raw($"{n} 0 obj\n"); }
|
||||
public void EndObj() { Raw("endobj\n"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user