TagTextLayout.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 GrapeCity.Documents.Pdf;
  9. using GrapeCity.Documents.Text;
  10. using GrapeCity.Documents.Pdf.Structure;
  11. using GrapeCity.Documents.Pdf.MarkedContent;
  12.  
  13. namespace DsPdfWeb.Demos.Basics
  14. {
  15. // This sample shows how to create tagged (structured) PDF and attach
  16. // tags to individual paragraphs in a TextLayout that is used to render
  17. // them together, splitting between pages.
  18. // The code generating the document is similar to that used in PaginatedText,
  19. // but adds tags.
  20. // To see/explore the tags, open the document in Adobe Acrobat Pro and go to
  21. // View | Navigation Panels | Tags.
  22. public class TagTextLayout
  23. {
  24. public int CreatePDF(Stream stream)
  25. {
  26. var doc = new GcPdfDocument();
  27.  
  28. // Create a Part element, it will contain P (paragraph) elements:
  29. var sePart = new StructElement("Part");
  30. doc.StructTreeRoot.Children.Add(sePart);
  31.  
  32. // Create and set up a TextLayout to render paragraphs:
  33. var tl = new TextLayout(72);
  34. tl.DefaultFormat.Font = StandardFonts.Times;
  35. tl.DefaultFormat.FontSize = 12;
  36. tl.FirstLineIndent = 72 / 2;
  37. tl.MaxWidth = doc.PageSize.Width;
  38. tl.MaxHeight = doc.PageSize.Height;
  39. tl.MarginAll = tl.Resolution;
  40. //
  41. // Append the text (20 paragraphs so they would not fit on a single page)
  42. // (note that TextLayout interprets "\r\n" as paragraph delimiter):
  43. //
  44. // Get the text (20 paragraphs):
  45. var text = Common.Util.LoremIpsum(20);
  46. // In order to tag the individual paragraphs, we need to split the text into paragraphs,
  47. // and use each paragraph format's Tag property (which is not related to PDF tags,
  48. // it is just an arbitrary data that can be associated with a TextFormat) to add the
  49. // paragraph's index to the paragraph:
  50. var pars = text.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
  51. for (int i = 0; i < pars.Length; ++i)
  52. {
  53. var tf = new TextFormat(tl.DefaultFormat) { Tag = i };
  54. tl.AppendLine(pars[i], tf);
  55. }
  56.  
  57. // Layout the text:
  58. tl.PerformLayout(true);
  59. // Use split options to provide widow/orphan control:
  60. var to = new TextSplitOptions(tl)
  61. {
  62. MinLinesInFirstParagraph = 2,
  63. MinLinesInLastParagraph = 2,
  64. };
  65. // TextLayoutHandler implements ITextLayoutHandler, which
  66. // allows tagging the text as it is rendered:
  67. var tlh = new TextLayoutHandler() { ParentElement = sePart };
  68.  
  69. // In a loop, split and render the text:
  70. while (true)
  71. {
  72. // 'rest' will accept the text that did not fit:
  73. var splitResult = tl.Split(to, out TextLayout rest);
  74. var page = doc.Pages.Add();
  75. var g = page.Graphics;
  76. // Tell the TextLayoutHandler which page we're on:
  77. tlh.Page = page;
  78. // ..and associate it with the graphics:
  79. g.TextLayoutHandler = tlh;
  80. // Draw the text that fits on the current page, and advance to next page unless we're done:
  81. g.DrawTextLayout(tl, PointF.Empty);
  82. if (splitResult != SplitResult.Split)
  83. break;
  84. tl = rest;
  85. }
  86. // Mark document as tagged:
  87. doc.MarkInfo.Marked = true;
  88.  
  89. // Done:
  90. doc.Save(stream);
  91. return doc.Pages.Count;
  92. }
  93.  
  94. // Custom class that allows tagging content as it is rendered by TextLayout:
  95. private class TextLayoutHandler : ITextLayoutHandler
  96. {
  97. private int _tagIndex;
  98. private int _currentParagraphIndex = -1;
  99. private StructElement _currentparagraphElement;
  100. public StructElement ParentElement;
  101. public Page Page;
  102.  
  103. public void TextTagBegin(GcPdfGraphics graphics, TextLayout textLayout, object tag)
  104. {
  105. int paragraphIndex;
  106. if (tag is int)
  107. paragraphIndex = (int)tag;
  108. else
  109. paragraphIndex = -1;
  110.  
  111. StructElement paragraphElement;
  112. if (_currentParagraphIndex == paragraphIndex)
  113. {
  114. paragraphElement = _currentparagraphElement;
  115. }
  116. else
  117. {
  118. if (paragraphIndex >= 0)
  119. {
  120. paragraphElement = new StructElement("P");
  121. ParentElement.Children.Add(paragraphElement);
  122. _currentparagraphElement = paragraphElement;
  123. _currentParagraphIndex = paragraphIndex;
  124. }
  125. else
  126. {
  127. paragraphElement = null;
  128. _currentparagraphElement = paragraphElement;
  129. _currentParagraphIndex = paragraphIndex;
  130. }
  131. }
  132.  
  133. //
  134. if (paragraphElement != null)
  135. {
  136. graphics.BeginMarkedContent(new TagMcid("P", _tagIndex));
  137. var mcil = new McrContentItemLink();
  138. mcil.MCID = _tagIndex;
  139. mcil.Page = Page;
  140. paragraphElement.ContentItems.Add(mcil);
  141. _tagIndex++;
  142. }
  143. }
  144.  
  145. public void TextTagEnd(GcPdfGraphics graphics, TextLayout textLayout, object tag)
  146. {
  147. if (_currentparagraphElement != null)
  148. graphics.EndMarkedContent();
  149. }
  150.  
  151. public void AddTextArea(RectangleF bounds)
  152. {
  153. }
  154. }
  155. }
  156. }
  157.