ExcelLabelGenerator.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. using DocumentFormat.OpenXml.Packaging;
  2. using DocumentFormat.OpenXml.Spreadsheet;
  3. using DocumentFormat.OpenXml;
  4. using System.Data;
  5. using System.Text.RegularExpressions;
  6. using Font = DocumentFormat.OpenXml.Spreadsheet.Font;
  7. using Color = DocumentFormat.OpenXml.Spreadsheet.Color;
  8. namespace qdr.app.studiou.orders2printpack.Labels
  9. {
  10. /// <summary>
  11. /// Generates Excel documents for printing labels using OpenXML format
  12. /// without relying on Microsoft Office libraries.
  13. /// </summary>
  14. public class ExcelLabelGenerator
  15. {
  16. // Default row height in points
  17. private const double DEFAULT_ROW_HEIGHT = 15.0;
  18. // Border styles
  19. private const int BORDER_STYLE_ID = 1;
  20. // Template pattern to match column names
  21. private static readonly Regex TemplatePattern = new Regex(@"\{([^{}]+)\}", RegexOptions.Compiled);
  22. // Formatting patterns
  23. private static readonly Regex BoldPattern = new Regex(@"\*\*(.+?)\*\*", RegexOptions.Compiled);
  24. private static readonly Regex ItalicPattern = new Regex(@"__(.+?)__", RegexOptions.Compiled);
  25. private static readonly Regex UnderlinePattern = new Regex(@"_(.+?)_", RegexOptions.Compiled);
  26. private static readonly Regex FontSizePattern = new Regex(@"\^\^(\d+)\^(.*?)\^\^", RegexOptions.Compiled);
  27. // Style indexes for different formatting
  28. private const int STYLE_NORMAL = 0;
  29. private const int STYLE_BORDER = 1;
  30. private const int STYLE_BOLD = 2;
  31. private const int STYLE_ITALIC = 3;
  32. private const int STYLE_UNDERLINE = 4;
  33. private const int STYLE_BOLD_BORDER = 5;
  34. private const int STYLE_ITALIC_BORDER = 6;
  35. private const int STYLE_UNDERLINE_BORDER = 7;
  36. // Font size style indexes start at 8 and go up
  37. private const int STYLE_FONT_SIZE_START = 8;
  38. /// <summary>
  39. /// Label format template that will be used to generate the content of each label.
  40. /// Use {columnName} placeholders to reference DataTable columns.
  41. /// Example: "ID: {ID}\nName: {ProductName}\nPrice: ${Price}"
  42. /// </summary>
  43. private readonly string _labelTemplate;
  44. /// <summary>
  45. /// Initializes a new instance of the ExcelLabelGenerator class with a specific label template.
  46. /// </summary>
  47. /// <param name="labelTemplate">Template string with {columnName} placeholders</param>
  48. public ExcelLabelGenerator(string labelTemplate)
  49. {
  50. _labelTemplate = labelTemplate ?? throw new ArgumentNullException(nameof(labelTemplate));
  51. }
  52. /// <summary>
  53. /// Initializes a new instance of the ExcelLabelGenerator class with a default template.
  54. /// The default template will simply concatenate all column values.
  55. /// </summary>
  56. public ExcelLabelGenerator()
  57. {
  58. _labelTemplate = string.Empty;
  59. }
  60. /// <summary>
  61. /// Generates an Excel document with labels formatted according to specified layout
  62. /// </summary>
  63. /// <param name="data">DataTable containing the label data</param>
  64. /// <param name="outputPath">Path where the Excel file will be saved</param>
  65. /// <param name="columnsPerPage">Number of label columns per A4 page</param>
  66. /// <param name="rowsPerPage">Number of label rows per A4 page</param>
  67. /// <param name="labelWidthPt">Width of each label in points</param>
  68. /// <param name="labelHeightPt">Height of each label in points</param>
  69. /// <returns>True if generation was successful, otherwise false</returns>
  70. public bool GenerateLabels(
  71. DataTable data,
  72. string outputPath,
  73. int columnsPerPage,
  74. int rowsPerPage,
  75. double labelWidthPt = 200.0,
  76. double labelHeightPt = 120.0)
  77. {
  78. if (data == null || data.Rows.Count == 0)
  79. throw new ArgumentException("Data table cannot be null or empty");
  80. if (columnsPerPage <= 0 || rowsPerPage <= 0)
  81. throw new ArgumentException("Columns and rows per page must be greater than zero");
  82. try
  83. {
  84. // Create the Excel document
  85. using (SpreadsheetDocument document = SpreadsheetDocument.Create(outputPath, SpreadsheetDocumentType.Workbook))
  86. {
  87. // Add a WorkbookPart to the document
  88. WorkbookPart workbookPart = document.AddWorkbookPart();
  89. workbookPart.Workbook = new Workbook();
  90. // Add styles to the workbook
  91. WorkbookStylesPart stylesPart = workbookPart.AddNewPart<WorkbookStylesPart>();
  92. stylesPart.Stylesheet = CreateStylesheet();
  93. stylesPart.Stylesheet.Save();
  94. // Add a WorksheetPart to the WorkbookPart
  95. WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
  96. worksheetPart.Worksheet = new Worksheet(new SheetData());
  97. // Add Sheets to the Workbook
  98. Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());
  99. // Add a Sheet to the Sheets collection
  100. Sheet sheet = new Sheet()
  101. {
  102. Id = workbookPart.GetIdOfPart(worksheetPart),
  103. SheetId = 1,
  104. Name = "Labels"
  105. };
  106. sheets.Append(sheet);
  107. // Get the SheetData from the WorksheetPart
  108. var sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();
  109. // Configure page layout for A4
  110. PageSetup pageSetup = new PageSetup
  111. {
  112. PaperSize = (UInt32Value)9U, // A4 paper size
  113. Orientation = OrientationValues.Portrait,
  114. FitToWidth = 1,
  115. FitToHeight = 0 // Auto height
  116. };
  117. PageMargins pageMargins = new PageMargins
  118. {
  119. Top = 0.75D,
  120. Bottom = 0.75D,
  121. Left = 0.7D,
  122. Right = 0.7D,
  123. Header = 0.3D,
  124. Footer = 0.3D
  125. };
  126. // Add page setup and margins
  127. worksheetPart.Worksheet.AppendChild(pageSetup);
  128. worksheetPart.Worksheet.AppendChild(pageMargins);
  129. // Set column widths based on the fixed label width
  130. Columns columns = new Columns();
  131. // Convert points to Excel column width
  132. // Excel column width units are based on the width of the "0" (zero) character in the default font
  133. // The conversion is approximate but works for most cases
  134. double excelColumnWidth = labelWidthPt / 7.0; // Approximate conversion
  135. for (int i = 1; i <= columnsPerPage; i++)
  136. {
  137. columns.Append(new Column
  138. {
  139. Min = (uint)i,
  140. Max = (uint)i,
  141. Width = excelColumnWidth,
  142. CustomWidth = true
  143. });
  144. }
  145. worksheetPart.Worksheet.InsertBefore(columns, sheetData);
  146. // Process each row of data
  147. Dictionary<uint, Row> rows = new Dictionary<uint, Row>();
  148. for (int dataIndex = 0; dataIndex < data.Rows.Count; dataIndex++)
  149. {
  150. // Calculate the label position on the page
  151. int pageCol = dataIndex % columnsPerPage + 1;
  152. int pageRow = dataIndex / columnsPerPage % rowsPerPage + 1;
  153. // Calculate actual row in Excel - one cell per label
  154. int excelRowIndex = dataIndex / columnsPerPage / rowsPerPage * rowsPerPage + pageRow;
  155. uint rowIndex = (uint)excelRowIndex;
  156. // Ensure row exists
  157. if (!rows.ContainsKey(rowIndex))
  158. {
  159. Row row = new Row
  160. {
  161. RowIndex = rowIndex,
  162. Height = labelHeightPt,
  163. CustomHeight = true
  164. };
  165. rows.Add(rowIndex, row);
  166. }
  167. string columnLetter = ColumnIndexToLetters(pageCol);
  168. // Create content for the label using the template with newlines
  169. string labelContent = GenerateLabelContent(data.Rows[dataIndex]);
  170. // Add cell with border style and content
  171. Cell cell = CreateCell(columnLetter, rowIndex, labelContent, BORDER_STYLE_ID);
  172. rows[rowIndex].Append(cell);
  173. }
  174. // Add all rows to the sheet
  175. foreach (var row in rows.Values.OrderBy(r => r.RowIndex))
  176. {
  177. sheetData?.Append(row);
  178. }
  179. // Save the worksheet
  180. worksheetPart.Worksheet.Save();
  181. // Save the workbook
  182. workbookPart.Workbook.Save();
  183. }
  184. return true;
  185. }
  186. catch (Exception ex)
  187. {
  188. Console.WriteLine($"Error generating Excel labels: {ex.Message}");
  189. return false;
  190. }
  191. }
  192. /// <summary>
  193. /// Gets or creates a style for a specific font size
  194. /// </summary>
  195. /// <param name="fontSize">Font size in points</param>
  196. /// <param name="withBorder">Whether to include a border</param>
  197. /// <returns>Style index</returns>
  198. private uint GetFontSizeStyle(int fontSize, bool withBorder)
  199. {
  200. // We'll use a simple formula to calculate the style index
  201. // STYLE_FONT_SIZE_START + (fontSize * 2) for no border
  202. // STYLE_FONT_SIZE_START + (fontSize * 2) + 1 for with border
  203. int styleOffset = fontSize * 2 + (withBorder ? 1 : 0);
  204. uint styleIndex = (uint)(STYLE_FONT_SIZE_START + styleOffset);
  205. // Return the style index - we'll ensure the style exists when creating the stylesheet
  206. return styleIndex;
  207. }
  208. /// <summary>
  209. /// Parses text for style markers and removes them from the text
  210. /// Handles nested formatting (e.g. _**text**_ for bold and underlined)
  211. /// </summary>
  212. /// <param name="text">Text to parse, will be modified to remove markers</param>
  213. /// <returns>TextStyle object with detected formatting</returns>
  214. private TextStyle ParseTextStyle(ref string text)
  215. {
  216. TextStyle style = new TextStyle();
  217. bool madeChanges;
  218. // We need to repeat the pattern matching until no more changes are made
  219. // This handles nested formatting like _**text**_
  220. do
  221. {
  222. madeChanges = false;
  223. // Check for bold
  224. if (BoldPattern.IsMatch(text))
  225. {
  226. style.IsBold = true;
  227. string newText = BoldPattern.Replace(text, "$1");
  228. if (newText != text)
  229. {
  230. text = newText;
  231. madeChanges = true;
  232. }
  233. }
  234. // Check for italic
  235. if (ItalicPattern.IsMatch(text))
  236. {
  237. style.IsItalic = true;
  238. string newText = ItalicPattern.Replace(text, "$1");
  239. if (newText != text)
  240. {
  241. text = newText;
  242. madeChanges = true;
  243. }
  244. }
  245. // Check for underline
  246. if (UnderlinePattern.IsMatch(text))
  247. {
  248. style.IsUnderlined = true;
  249. string newText = UnderlinePattern.Replace(text, "$1");
  250. if (newText != text)
  251. {
  252. text = newText;
  253. madeChanges = true;
  254. }
  255. }
  256. } while (madeChanges);
  257. return style;
  258. }
  259. /// <summary>
  260. /// Represents text formatting style
  261. /// </summary>
  262. private class TextStyle
  263. {
  264. public bool IsBold { get; set; }
  265. public bool IsItalic { get; set; }
  266. public bool IsUnderlined { get; set; }
  267. public bool HasFormatting => IsBold || IsItalic || IsUnderlined;
  268. }
  269. /// <summary>
  270. /// The key update is to the CreateCell method and ensuring all cell formats
  271. /// have WrapText = true properly set. Also ensuring the alignment is properly
  272. /// applied to each cell format.
  273. /// </summary>
  274. private Cell CreateCell(string columnName, uint rowIndex, string text, uint? styleIndex = null)
  275. {
  276. // Create the cell
  277. Cell cell = new Cell
  278. {
  279. CellReference = $"{columnName}{rowIndex}",
  280. DataType = CellValues.InlineString,
  281. StyleIndex = styleIndex ?? STYLE_NORMAL
  282. };
  283. // Create the inline string that will hold our text with preserved formatting
  284. InlineString inlineString = new InlineString();
  285. // We need to handle each line of text separately while preserving the newlines
  286. string[] lines = text.Split(new[] { '\n' }, StringSplitOptions.None);
  287. for (int lineIndex = 0; lineIndex < lines.Length; lineIndex++)
  288. {
  289. string line = lines[lineIndex];
  290. // Check if this is not the first line, we need to add a line break
  291. if (lineIndex > 0)
  292. {
  293. // For line breaks in Excel, we need to add a line feed character
  294. // within the same text run, not as a separate Break element
  295. Run lineBreakRun = new Run();
  296. lineBreakRun.AppendChild(new Text
  297. {
  298. Text = "\n",
  299. Space = SpaceProcessingModeValues.Preserve
  300. });
  301. inlineString.AppendChild(lineBreakRun);
  302. }
  303. // Process the current line
  304. // This can contain formatting markers like bold, italic, etc.
  305. string remainingText = line;
  306. // First check for font size formatting
  307. while (FontSizePattern.IsMatch(remainingText))
  308. {
  309. Match match = FontSizePattern.Match(remainingText);
  310. // Add text before the font size marker if any
  311. if (match.Index > 0)
  312. {
  313. string beforeText = remainingText.Substring(0, match.Index);
  314. TextStyle beforeStyle = ParseTextStyle(ref beforeText);
  315. AddFormattedRun(inlineString, beforeText, beforeStyle);
  316. }
  317. // Handle the font-sized text
  318. int fontSize;
  319. if (int.TryParse(match.Groups[1].Value, out fontSize))
  320. {
  321. string fontSizeText = match.Groups[2].Value;
  322. TextStyle fontSizeStyle = ParseTextStyle(ref fontSizeText);
  323. // Create a run with font size and any other formatting
  324. Run fontSizeRun = new Run();
  325. RunProperties fontSizeProps = new RunProperties();
  326. fontSizeProps.AppendChild(new FontSize { Val = fontSize });
  327. if (fontSizeStyle.IsBold)
  328. fontSizeProps.AppendChild(new Bold());
  329. if (fontSizeStyle.IsItalic)
  330. fontSizeProps.AppendChild(new Italic());
  331. if (fontSizeStyle.IsUnderlined)
  332. fontSizeProps.AppendChild(new Underline { Val = UnderlineValues.Single });
  333. fontSizeRun.AppendChild(fontSizeProps);
  334. fontSizeRun.AppendChild(new Text
  335. {
  336. Text = fontSizeText,
  337. Space = SpaceProcessingModeValues.Preserve
  338. });
  339. inlineString.AppendChild(fontSizeRun);
  340. }
  341. // Move past the processed part
  342. remainingText = remainingText.Substring(match.Index + match.Length);
  343. }
  344. // Process any remaining text in this line if any
  345. if (!string.IsNullOrEmpty(remainingText))
  346. {
  347. TextStyle remainingStyle = ParseTextStyle(ref remainingText);
  348. AddFormattedRun(inlineString, remainingText, remainingStyle);
  349. }
  350. }
  351. cell.AppendChild(inlineString);
  352. return cell;
  353. }
  354. /// <summary>
  355. /// Helper method to add a run with the specified formatting
  356. /// </summary>
  357. private void AddFormattedRun(InlineString inlineString, string text, TextStyle style)
  358. {
  359. if (string.IsNullOrEmpty(text))
  360. return;
  361. Run run = new Run();
  362. if (style.HasFormatting)
  363. {
  364. RunProperties props = new RunProperties();
  365. if (style.IsBold)
  366. props.AppendChild(new Bold());
  367. if (style.IsItalic)
  368. props.AppendChild(new Italic());
  369. if (style.IsUnderlined)
  370. props.AppendChild(new Underline { Val = UnderlineValues.Single });
  371. run.AppendChild(props);
  372. }
  373. run.AppendChild(new Text
  374. {
  375. Text = text,
  376. Space = SpaceProcessingModeValues.Preserve
  377. });
  378. inlineString.AppendChild(run);
  379. }
  380. /// <summary>
  381. /// This is a key modification to ensure ALL cell formats have WrapText=true
  382. /// </summary>
  383. private Stylesheet CreateStylesheet()
  384. {
  385. Stylesheet stylesheet = new Stylesheet();
  386. // Create Fonts
  387. Fonts fonts = new Fonts();
  388. // Font 0: Normal (11pt)
  389. Font normalFont = new Font();
  390. normalFont.AppendChild(new FontSize { Val = 11D });
  391. fonts.Append(normalFont);
  392. // Font 1: Bold (11pt)
  393. Font boldFont = new Font();
  394. boldFont.AppendChild(new FontSize { Val = 11D });
  395. boldFont.Append(new Bold());
  396. fonts.Append(boldFont);
  397. // Font 2: Italic (11pt)
  398. Font italicFont = new Font();
  399. italicFont.AppendChild(new FontSize { Val = 11D });
  400. italicFont.Append(new Italic());
  401. fonts.Append(italicFont);
  402. // Font 3: Underline (11pt)
  403. Font underlineFont = new Font();
  404. underlineFont.AppendChild(new FontSize { Val = 11D });
  405. underlineFont.Append(new Underline() { Val = UnderlineValues.Single });
  406. fonts.Append(underlineFont);
  407. // Add font sizes from 8pt to 72pt
  408. for (int size = 8; size <= 72; size++)
  409. {
  410. // Regular font with custom size
  411. Font sizedFont = new Font();
  412. sizedFont.AppendChild(new FontSize { Val = (double)size });
  413. fonts.Append(sizedFont);
  414. }
  415. fonts.Count = (uint)fonts.ChildElements.Count;
  416. // Create Fills
  417. Fills fills = new Fills();
  418. Fill fill1 = new Fill();
  419. fill1.PatternFill = new PatternFill { PatternType = PatternValues.None };
  420. Fill fill2 = new Fill();
  421. fill2.PatternFill = new PatternFill { PatternType = PatternValues.Gray125 };
  422. fills.Append(fill1);
  423. fills.Append(fill2);
  424. fills.Count = (uint)fills.ChildElements.Count;
  425. // Create Borders
  426. Borders borders = new Borders();
  427. // Border 0: No border
  428. Border noBorder = new Border();
  429. borders.Append(noBorder);
  430. // Border 1: All sides
  431. Border allBorder = new Border();
  432. // Left Border
  433. LeftBorder leftBorder = new LeftBorder { Style = BorderStyleValues.Thin };
  434. leftBorder.Append(new Color { Auto = true });
  435. allBorder.Append(leftBorder);
  436. // Right Border
  437. RightBorder rightBorder = new RightBorder { Style = BorderStyleValues.Thin };
  438. rightBorder.Append(new Color { Auto = true });
  439. allBorder.Append(rightBorder);
  440. // Top Border
  441. TopBorder topBorder = new TopBorder { Style = BorderStyleValues.Thin };
  442. topBorder.Append(new Color { Auto = true });
  443. allBorder.Append(topBorder);
  444. // Bottom Border
  445. BottomBorder bottomBorder = new BottomBorder { Style = BorderStyleValues.Thin };
  446. bottomBorder.Append(new Color { Auto = true });
  447. allBorder.Append(bottomBorder);
  448. // Diagonal Border (blank)
  449. allBorder.Append(new DiagonalBorder());
  450. borders.Append(allBorder);
  451. borders.Count = (uint)borders.ChildElements.Count;
  452. // Create CellFormats
  453. CellFormats cellFormats = new CellFormats();
  454. // *** CRITICALLY IMPORTANT: All cell formats must have WrapText=true ***
  455. // Format 0: Default / Normal
  456. CellFormat format0 = new CellFormat
  457. {
  458. NumberFormatId = 0,
  459. FontId = 0,
  460. FillId = 0,
  461. BorderId = 0,
  462. FormatId = 0,
  463. ApplyAlignment = true
  464. };
  465. format0.AppendChild(new Alignment
  466. {
  467. Vertical = VerticalAlignmentValues.Center,
  468. Horizontal = HorizontalAlignmentValues.Left,
  469. WrapText = true,
  470. Indent = 1, // Left indent for text margin
  471. JustifyLastLine = false
  472. });
  473. cellFormats.Append(format0);
  474. // Format 1: Border only
  475. CellFormat format1 = new CellFormat
  476. {
  477. NumberFormatId = 0,
  478. FontId = 0,
  479. FillId = 0,
  480. BorderId = 1,
  481. FormatId = 0,
  482. ApplyBorder = true,
  483. ApplyAlignment = true
  484. };
  485. format1.AppendChild(new Alignment
  486. {
  487. Vertical = VerticalAlignmentValues.Center,
  488. Horizontal = HorizontalAlignmentValues.Left,
  489. WrapText = true,
  490. Indent = 1, // Left indent for text margin
  491. JustifyLastLine = false
  492. });
  493. cellFormats.Append(format1);
  494. // Format 2: Bold
  495. CellFormat format2 = new CellFormat
  496. {
  497. NumberFormatId = 0,
  498. FontId = 1,
  499. FillId = 0,
  500. BorderId = 0,
  501. FormatId = 0,
  502. ApplyFont = true,
  503. ApplyAlignment = true
  504. };
  505. format2.AppendChild(new Alignment
  506. {
  507. Vertical = VerticalAlignmentValues.Center,
  508. Horizontal = HorizontalAlignmentValues.Left,
  509. WrapText = true,
  510. Indent = 1, // Left indent for text margin
  511. JustifyLastLine = false
  512. });
  513. cellFormats.Append(format2);
  514. // Format 3: Italic
  515. CellFormat format3 = new CellFormat
  516. {
  517. NumberFormatId = 0,
  518. FontId = 2,
  519. FillId = 0,
  520. BorderId = 0,
  521. FormatId = 0,
  522. ApplyFont = true,
  523. ApplyAlignment = true
  524. };
  525. format3.AppendChild(new Alignment
  526. {
  527. Vertical = VerticalAlignmentValues.Center,
  528. Horizontal = HorizontalAlignmentValues.Left,
  529. WrapText = true,
  530. Indent = 1, // Left indent for text margin
  531. JustifyLastLine = false
  532. });
  533. cellFormats.Append(format3);
  534. // Format 4: Underline
  535. CellFormat format4 = new CellFormat
  536. {
  537. NumberFormatId = 0,
  538. FontId = 3,
  539. FillId = 0,
  540. BorderId = 0,
  541. FormatId = 0,
  542. ApplyFont = true,
  543. ApplyAlignment = true
  544. };
  545. format4.AppendChild(new Alignment
  546. {
  547. Vertical = VerticalAlignmentValues.Center,
  548. Horizontal = HorizontalAlignmentValues.Left,
  549. WrapText = true,
  550. Indent = 1, // Left indent for text margin
  551. JustifyLastLine = false
  552. });
  553. cellFormats.Append(format4);
  554. // Format 5: Bold + Border
  555. CellFormat format5 = new CellFormat
  556. {
  557. NumberFormatId = 0,
  558. FontId = 1,
  559. FillId = 0,
  560. BorderId = 1,
  561. FormatId = 0,
  562. ApplyFont = true,
  563. ApplyBorder = true,
  564. ApplyAlignment = true
  565. };
  566. format5.AppendChild(new Alignment
  567. {
  568. Vertical = VerticalAlignmentValues.Center,
  569. Horizontal = HorizontalAlignmentValues.Left,
  570. WrapText = true,
  571. Indent = 1, // Left indent for text margin
  572. JustifyLastLine = false
  573. });
  574. cellFormats.Append(format5);
  575. // Format 6: Italic + Border
  576. CellFormat format6 = new CellFormat
  577. {
  578. NumberFormatId = 0,
  579. FontId = 2,
  580. FillId = 0,
  581. BorderId = 1,
  582. FormatId = 0,
  583. ApplyFont = true,
  584. ApplyBorder = true,
  585. ApplyAlignment = true
  586. };
  587. format6.AppendChild(new Alignment
  588. {
  589. Vertical = VerticalAlignmentValues.Center,
  590. Horizontal = HorizontalAlignmentValues.Left,
  591. WrapText = true,
  592. Indent = 1, // Left indent for text margin
  593. JustifyLastLine = false
  594. });
  595. cellFormats.Append(format6);
  596. // Format 7: Underline + Border
  597. CellFormat format7 = new CellFormat
  598. {
  599. NumberFormatId = 0,
  600. FontId = 3,
  601. FillId = 0,
  602. BorderId = 1,
  603. FormatId = 0,
  604. ApplyFont = true,
  605. ApplyBorder = true,
  606. ApplyAlignment = true
  607. };
  608. format7.AppendChild(new Alignment
  609. {
  610. Vertical = VerticalAlignmentValues.Center,
  611. Horizontal = HorizontalAlignmentValues.Left,
  612. WrapText = true,
  613. Indent = 1, // Left indent for text margin
  614. JustifyLastLine = false
  615. });
  616. cellFormats.Append(format7);
  617. cellFormats.Count = (uint)cellFormats.ChildElements.Count;
  618. // Add additional cell formats for font sizes 8pt to 72pt
  619. // Both with and without borders
  620. for (int size = 8; size <= 72; size++)
  621. {
  622. int fontIndex = 4 + (size - 8); // Font indexes start after our 4 basic fonts
  623. // Create style without border
  624. CellFormat formatSize = new CellFormat
  625. {
  626. NumberFormatId = 0,
  627. FontId = (uint)fontIndex,
  628. FillId = 0,
  629. BorderId = 0,
  630. FormatId = 0,
  631. ApplyFont = true,
  632. ApplyAlignment = true
  633. };
  634. formatSize.AppendChild(new Alignment
  635. {
  636. Vertical = VerticalAlignmentValues.Center,
  637. Horizontal = HorizontalAlignmentValues.Left,
  638. WrapText = true,
  639. Indent = 1,
  640. JustifyLastLine = false
  641. });
  642. cellFormats.Append(formatSize);
  643. // Create style with border
  644. CellFormat formatSizeBorder = new CellFormat
  645. {
  646. NumberFormatId = 0,
  647. FontId = (uint)fontIndex,
  648. FillId = 0,
  649. BorderId = 1,
  650. FormatId = 0,
  651. ApplyFont = true,
  652. ApplyBorder = true,
  653. ApplyAlignment = true
  654. };
  655. formatSizeBorder.AppendChild(new Alignment
  656. {
  657. Vertical = VerticalAlignmentValues.Center,
  658. Horizontal = HorizontalAlignmentValues.Left,
  659. WrapText = true,
  660. Indent = 1,
  661. JustifyLastLine = false
  662. });
  663. cellFormats.Append(formatSizeBorder);
  664. }
  665. cellFormats.Count = (uint)cellFormats.ChildElements.Count;
  666. stylesheet.Append(fonts);
  667. stylesheet.Append(fills);
  668. stylesheet.Append(borders);
  669. stylesheet.Append(cellFormats);
  670. return stylesheet;
  671. }
  672. /// <summary>
  673. /// Generates the content for a label based on the template and data row
  674. /// </summary>
  675. /// <param name="dataRow">The DataRow containing the label data</param>
  676. /// <returns>String representing the label content with newlines</returns>
  677. private string GenerateLabelContent(DataRow dataRow)
  678. {
  679. string result;
  680. if (string.IsNullOrEmpty(_labelTemplate))
  681. {
  682. // Default behavior: use column name and value for each column
  683. List<string> lines = new List<string>();
  684. foreach (DataColumn column in dataRow.Table.Columns)
  685. {
  686. object value = dataRow[column.ColumnName];
  687. string lineContent = $"{column.ColumnName}: {(value != DBNull.Value ? value?.ToString() : string.Empty)}";
  688. lines.Add(lineContent);
  689. }
  690. result = string.Join("\n", lines);
  691. }
  692. else
  693. {
  694. // Use the specified template
  695. result = _labelTemplate;
  696. // Replace all column placeholders with actual values
  697. result = TemplatePattern.Replace(result, match =>
  698. {
  699. string columnName = match.Groups[1].Value;
  700. if (dataRow.Table.Columns.Contains(columnName))
  701. {
  702. object value = dataRow[columnName];
  703. return value != DBNull.Value ? value?.ToString() ?? string.Empty : string.Empty;
  704. }
  705. return match.Value; // Keep the placeholder if column not found
  706. });
  707. }
  708. return result;
  709. }
  710. /// <summary>
  711. /// Converts a column index to Excel column letters (e.g., 1 = A, 27 = AA)
  712. /// </summary>
  713. private string ColumnIndexToLetters(int columnIndex)
  714. {
  715. string columnLetters = string.Empty;
  716. while (columnIndex > 0)
  717. {
  718. int remainder = (columnIndex - 1) % 26;
  719. columnLetters = (char)('A' + remainder) + columnLetters;
  720. columnIndex = (columnIndex - 1) / 26;
  721. }
  722. return columnLetters;
  723. }
  724. }
  725. }