ElementExtensions.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. using System;
  2. using System.Net.Mime;
  3. using System.Runtime.CompilerServices;
  4. using QuestPDF.Drawing.Exceptions;
  5. using QuestPDF.Elements;
  6. using QuestPDF.Helpers;
  7. using QuestPDF.Infrastructure;
  8. namespace QuestPDF.Fluent
  9. {
  10. public static class ElementExtensions
  11. {
  12. static ElementExtensions()
  13. {
  14. NativeDependencyCompatibilityChecker.Test();
  15. }
  16. internal static Container Create(Action<IContainer> factory)
  17. {
  18. var container = new Container();
  19. factory(container);
  20. return container;
  21. }
  22. internal static T Element<T>(this IContainer element, T child) where T : IElement
  23. {
  24. if (element?.Child != null && element.Child is Empty == false)
  25. {
  26. var message = "You should not assign multiple child elements to a single-child container. " +
  27. "This may happen when a container variable is used outside of its scope/closure OR the container is used in multiple fluent API chains OR the container is used incorrectly in a loop. " +
  28. "This exception is thrown to help you detect that some part of the code is overriding fragments of the document layout with a new content - essentially destroying existing content.";
  29. throw new DocumentComposeException(message);
  30. }
  31. if (element != child as Element)
  32. element.Child = child as Element;
  33. return child;
  34. }
  35. /// <summary>
  36. /// Passes the Fluent API chain to the provided <paramref name="handler"/> method.
  37. /// <a href="https://www.questpdf.com/api-reference/element.html">Learn more</a>
  38. /// </summary>
  39. /// <remarks>
  40. /// <para>This method is particularly useful for code refactoring, improving its structure and readability.</para>
  41. /// <para>Extracting implementation of certain layout structures into separate methods, allows you to accurately describe their purpose and reuse them code in various parts of the application.</para>
  42. /// </remarks>
  43. /// <param name="handler">A delegate that takes the current container and populates it with content.</param>
  44. public static void Element(
  45. this IContainer parent,
  46. Action<IContainer> handler,
  47. #if NETCOREAPP3_0_OR_GREATER
  48. [CallerArgumentExpression("handler")] string handlerName = null,
  49. #endif
  50. [CallerMemberName] string parentName = "",
  51. [CallerFilePath] string sourceFilePath = "",
  52. [CallerLineNumber] int sourceLineNumber = 0)
  53. {
  54. #if !NETCOREAPP3_0_OR_GREATER
  55. const string handlerName = "Unknown function";
  56. #endif
  57. var handlerContainer = parent
  58. .Container()
  59. .Element(new SourceCodePointer
  60. {
  61. HandlerName = handlerName,
  62. ParentName = parentName,
  63. SourceFilePath = sourceFilePath,
  64. SourceLineNumber = sourceLineNumber
  65. });
  66. handler(handlerContainer);
  67. }
  68. /// <summary>
  69. /// Passes the Fluent API chain to the provided <paramref name="handler"/> method.
  70. /// <a href="https://www.questpdf.com/api-reference/element.html">Learn more</a>
  71. /// </summary>
  72. /// <remarks>
  73. /// <para>This method is particularly useful for code refactoring, improving its structure and readability.</para>
  74. /// <para>Extracting implementation of certain layout structures into separate methods, allows you to accurately describe their purpose and reuse them code in various parts of the application.</para>
  75. /// </remarks>
  76. /// <param name="handler">A method that accepts the current container, optionally populates it with content, and returns a subsequent container to continue the Fluent API chain.</param>
  77. /// <returns>The container returned by the <paramref name="handler"/> method.</returns>
  78. public static IContainer Element(
  79. this IContainer parent,
  80. Func<IContainer, IContainer> handler,
  81. #if NETCOREAPP3_0_OR_GREATER
  82. [CallerArgumentExpression("handler")] string handlerName = null,
  83. #endif
  84. [CallerMemberName] string parentName = "",
  85. [CallerFilePath] string sourceFilePath = "",
  86. [CallerLineNumber] int sourceLineNumber = 0)
  87. {
  88. #if !NETCOREAPP3_0_OR_GREATER
  89. const string handlerName = "Unknown function";
  90. #endif
  91. var handlerContainer = parent
  92. .Element(new SourceCodePointer
  93. {
  94. HandlerName = handlerName,
  95. ParentName = parentName,
  96. SourceFilePath = sourceFilePath,
  97. SourceLineNumber = sourceLineNumber
  98. });
  99. return handler(handlerContainer);
  100. }
  101. internal static IContainer NonTrackingElement(this IContainer parent, Func<IContainer, IContainer> handler)
  102. {
  103. return handler(parent.Container());
  104. }
  105. /// <summary>
  106. /// Constrains its content to maintain a given aspect ratio.
  107. /// <a href="https://www.questpdf.com/api-reference/aspect-ratio.html">Learn more</a>
  108. /// </summary>
  109. /// <remarks>
  110. /// This container enforces strict space constraints. The <see cref="DocumentLayoutException" /> may be thrown if these constraints can't be satisfied.
  111. /// </remarks>
  112. /// <param name="ratio">Represents the aspect ratio as a width-to-height division. For instance, a container with a width of 250 points and a height of 200 points has an aspect ratio of 1.25.</param>
  113. /// <param name="option">Determines the approach the component should adopt when maintaining the specified aspect ratio.</param>
  114. public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth)
  115. {
  116. return element.Element(new AspectRatio
  117. {
  118. Ratio = ratio,
  119. Option = option
  120. });
  121. }
  122. /// <summary>
  123. /// Sets a solid background color behind its content.
  124. /// <a href="https://www.questpdf.com/api-reference/background.html">Learn more</a>
  125. /// </summary>
  126. /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="colorParam"]/*' />
  127. public static IContainer Background(this IContainer element, Color color)
  128. {
  129. return element.Element(new Background
  130. {
  131. Color = color
  132. });
  133. }
  134. /// <summary>
  135. /// Draws a basic placeholder useful for prototyping.
  136. /// <a href="https://www.questpdf.com/api-reference/placeholder.html">Learn more</a>
  137. /// </summary>
  138. /// <remarks>
  139. /// You can control the size of the Placeholder by chaining other elements before its invocation, e.g.:
  140. /// <code>
  141. /// .Width(200)
  142. /// .Height(100)
  143. /// .Placeholder("Sample text");
  144. /// </code>
  145. /// </remarks>
  146. /// <param name="text">When provided, the placeholder displays this text. If omitted, a simple image icon is shown instead.</param>
  147. public static void Placeholder(this IContainer element, string? text = null)
  148. {
  149. element.Component(new Placeholder
  150. {
  151. Text = text ?? string.Empty
  152. });
  153. }
  154. /// <summary>
  155. /// If the container spans multiple pages, its content appears only on the first one.
  156. /// <a href="https://www.questpdf.com/api-reference/show-once.html">Learn more</a>
  157. /// </summary>
  158. /// <remarks>
  159. /// <para>This element is useful if you wish to display a header on every page but want certain elements visible only on the first page.</para>
  160. /// <para>Depending on the content, certain elements (such as Row or Table) may repeatedly draw their items across multiple pages. Use the ShowOnce element to modify this behavior if it's not desired.</para>
  161. /// </remarks>
  162. /// <example>
  163. /// <para>Combine this element with SkipOnce to achieve more complex behaviors, e.g.:</para>
  164. /// <para><c>.SkipOnce().ShowOnce()</c> ensures the child element is displayed only on the second page.</para>
  165. /// <para><c>.SkipOnce().SkipOnce()</c> starts displaying the child element from the third page onwards.</para>
  166. /// <para><c>.ShowOnce().SkipOnce()</c> draws nothing, as the order of invocation is important.</para>
  167. /// </example>
  168. public static IContainer ShowOnce(this IContainer element)
  169. {
  170. return element.Element(new ShowOnce());
  171. }
  172. /// <summary>
  173. /// If the container spans multiple pages, its content is omitted on the first page and then displayed on the second and subsequent pages.
  174. /// <a href="https://www.questpdf.com/api-reference/skip-once.html">Learn more</a>
  175. /// </summary>
  176. /// <remarks>
  177. /// A common use-case for this element is when displaying a consistent header across pages but needing to conditionally show/hide specific fragments on the first page.
  178. /// </remarks>
  179. /// <example>
  180. /// <para>Combine this element with ShowOnce to achieve more complex behaviors, e.g.:</para>
  181. /// <para><c>.SkipOnce().ShowOnce()</c> ensures the child element is displayed only on the second page.</para>
  182. /// <para><c>.SkipOnce().SkipOnce()</c> starts displaying the child element from the third page onwards.</para>
  183. /// <para><c>.ShowOnce().SkipOnce()</c> draws nothing, as the order of invocation is important.</para>
  184. /// </example>
  185. public static IContainer SkipOnce(this IContainer element)
  186. {
  187. return element.Element(new SkipOnce());
  188. }
  189. /// <summary>
  190. /// Ensures its content is displayed entirely on a single page by disabling the default paging capability.
  191. /// <a href="https://www.questpdf.com/api-reference/show-entire.html">Learn more</a>
  192. /// </summary>
  193. /// <remarks>
  194. /// <para>While many library elements inherently support paging, allowing content to span multiple pages, this element restricts that behavior.</para>
  195. /// <para>Employ this when a single-page display is crucial.</para>
  196. /// <para>Be cautious: its strict space constraints can trigger the <see cref="DocumentLayoutException" /> if content exceeds the page's capacity.</para>
  197. /// </remarks>
  198. public static IContainer ShowEntire(this IContainer element)
  199. {
  200. return element.Element(new ShowEntire());
  201. }
  202. /// <summary>
  203. /// <para>Serves as a less-strict approach compared to the <see cref="ElementExtensions.ShowEntire">ShowEntire</see> element.</para>
  204. /// <para>
  205. /// It impacts only the very first page of its content's occurence.
  206. /// If the element fits within its first page, it's rendered as usual.
  207. /// However, if the element doesn't fit and the available space has less vertical space than required height, the content is entirely shifted to the next page.
  208. /// </para>
  209. /// <br />
  210. /// <a href="https://www.questpdf.com/api-reference/ensure-space.html">Learn more</a>
  211. /// </summary>
  212. /// <remarks>
  213. /// This is especially useful for elements like tables, where you'd want to display several rows together. By setting the minHeight, you can avoid scenarios where only a single row appears at the page's end, ensuring a more cohesive presentation.
  214. /// </remarks>
  215. public static IContainer EnsureSpace(this IContainer element, float minHeight = Elements.EnsureSpace.DefaultMinHeight)
  216. {
  217. return element.Element(new EnsureSpace
  218. {
  219. MinHeight = minHeight
  220. });
  221. }
  222. /// <summary>
  223. /// Inserts a break that pushes the subsequent content to start on a new page.
  224. /// <a href="https://www.questpdf.com/api-reference/page-break.html">Learn more</a>
  225. /// </summary>
  226. public static void PageBreak(this IContainer element)
  227. {
  228. element.Element(new PageBreak());
  229. }
  230. /// <summary>
  231. /// A neutral layout structure that neither contributes to nor alters its content.
  232. /// </summary>
  233. /// <remarks>
  234. /// By default, certain FluentAPI calls may be batched together for optimized performance. Introduce this element if you wish to separate and prevent such optimizations.
  235. /// </remarks>
  236. public static IContainer Container(this IContainer element)
  237. {
  238. return element.Element(new Container());
  239. }
  240. [Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
  241. public static IContainer ExternalLink(this IContainer element, string url)
  242. {
  243. return element.Hyperlink(url);
  244. }
  245. /// <summary>
  246. /// Creates a clickable area that redirects the user to a designated webpage.
  247. /// <a href="https://www.questpdf.com/api-reference/hyperlink.html">Learn more</a>
  248. /// </summary>
  249. /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="param.url"]/*' />
  250. public static IContainer Hyperlink(this IContainer element, string url)
  251. {
  252. return element.Element(new Hyperlink
  253. {
  254. Url = url
  255. });
  256. }
  257. [Obsolete("This element has been renamed since version 2022.3. Please use the Section method.")]
  258. public static IContainer Location(this IContainer element, string locationName)
  259. {
  260. return element.Section(locationName);
  261. }
  262. /// <summary>
  263. /// Defines a named fragment of the document that can span multiple pages.
  264. /// <a href="https://www.questpdf.com/api-reference/section.html">Learn more</a>
  265. /// </summary>
  266. /// <remarks>
  267. /// <para>Several other elements interact with sections:</para>
  268. /// <para>Use <see cref="ElementExtensions.SectionLink">SectionLink</see> to create a clickable area redirecting user to the first page of the associated section.</para>
  269. /// <para>The <see cref="TextExtensions.Text(IContainer, Action&lt;TextDescriptor&gt;)">Text</see> element can display section properties, such as the starting page, ending page, and length.</para>
  270. /// </remarks>
  271. /// <param name="sectionName">An internal text key representing the section. It should be unique and won't appear in the final document.</param>
  272. public static IContainer Section(this IContainer element, string sectionName)
  273. {
  274. return element.Element(new Section
  275. {
  276. SectionName = sectionName
  277. });
  278. }
  279. [Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
  280. public static IContainer InternalLink(this IContainer element, string locationName)
  281. {
  282. return element.SectionLink(locationName);
  283. }
  284. /// <summary>
  285. /// Creates a clickable area that navigates the user to a designated section.
  286. /// <a href="https://www.questpdf.com/api-reference/section-link.html">Learn more</a>
  287. /// </summary>
  288. /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="param.sectionName"]/*' />
  289. public static IContainer SectionLink(this IContainer element, string sectionName)
  290. {
  291. return element.Element(new SectionLink
  292. {
  293. SectionName = sectionName
  294. });
  295. }
  296. /// <summary>
  297. /// Conditionally draws or hides its inner content.
  298. /// <a href="https://www.questpdf.com/api-reference/show-if.html">Learn more</a>
  299. /// </summary>
  300. /// <param name="condition">If the value is <see langword="true"/>, its content is visible. Otherwise, it's hidden.</param>
  301. public static IContainer ShowIf(this IContainer element, bool condition)
  302. {
  303. return condition ? element : new Container();
  304. }
  305. /// <summary>
  306. /// Conditionally draws or hides its inner content depending on drawing context.
  307. /// Please use carefully as certain predicates may produce unstable layouts resulting with unexpected content or exceptions.
  308. /// <a href="https://www.questpdf.com/api-reference/show-if.html">Learn more</a>
  309. /// </summary>
  310. /// <param name="predicate">If the predicate returns <see langword="true"/>, its content is visible. Otherwise, it's hidden.</param>
  311. public static IContainer ShowIf(this IContainer element, Predicate<ShowIfContext> predicate)
  312. {
  313. return element.Element(new ShowIf
  314. {
  315. VisibilityPredicate = predicate
  316. });
  317. }
  318. /// <summary>
  319. /// Removes size constraints and grants its content virtually unlimited space.
  320. /// <a href="https://www.questpdf.com/api-reference/unconstrained.html">Learn more</a>
  321. /// </summary>
  322. public static IContainer Unconstrained(this IContainer element)
  323. {
  324. return element.Element(new Unconstrained());
  325. }
  326. /// <summary>
  327. /// Applies a default text style to all nested <see cref="TextExtensions.Text">Text</see> elements.
  328. /// <a href="https://www.questpdf.com/api-reference/default-text-style.html">Learn more</a>
  329. /// </summary>
  330. /// <remarks>
  331. /// If multiple text elements have a similar style, using this element can help simplify your code.
  332. /// </remarks>
  333. /// <param name="textStyle">A TextStyle object used to override specific properties.</param>
  334. public static IContainer DefaultTextStyle(this IContainer element, TextStyle textStyle)
  335. {
  336. return element.Element(new DefaultTextStyle
  337. {
  338. TextStyle = textStyle
  339. });
  340. }
  341. /// <summary>
  342. /// Applies a default text style to all nested <see cref="TextExtensions.Text">Text</see> elements.
  343. /// <a href="https://www.questpdf.com/api-reference/default-text-style.html">Learn more</a>
  344. /// </summary>
  345. /// <remarks>
  346. /// If multiple text elements have a similar style, using this element can help simplify your code.
  347. /// </remarks>
  348. /// <param name="handler">A handler to modify the default text style.</param>
  349. public static IContainer DefaultTextStyle(this IContainer element, Func<TextStyle, TextStyle> handler)
  350. {
  351. return element.Element(new DefaultTextStyle
  352. {
  353. TextStyle = handler(TextStyle.Default)
  354. });
  355. }
  356. /// <summary>
  357. /// Renders the element exclusively on the first page. Any portion of the element that doesn't fit is omitted.
  358. /// <a href="https://www.questpdf.com/api-reference/stop-paging.html">Learn more</a>
  359. /// </summary>
  360. public static IContainer StopPaging(this IContainer element)
  361. {
  362. return element.Element(new StopPaging());
  363. }
  364. /// <summary>
  365. /// Adjusts its content to fit within the available space by scaling it down proportionally if necessary.
  366. /// <a href="https://www.questpdf.com/api-reference/scale-to-fit.html">Learn more</a>
  367. /// </summary>
  368. /// <remarks>
  369. /// <para>This container determines the best scale value through multiple scaling operations. With complex content, this may impact performance.</para>
  370. /// <para>Pairing with certain elements, such as <see cref="ElementExtensions.AspectRatio">AspectRatio</see>, might still lead to a <see cref="DocumentLayoutException" />.</para>
  371. /// </remarks>
  372. public static IContainer ScaleToFit(this IContainer element)
  373. {
  374. return element.Element(new ScaleToFit());
  375. }
  376. /// <summary>
  377. /// Repeats its content across multiple pages.
  378. /// </summary>
  379. /// <remarks>
  380. /// In certain layout structures, the content visibility may depend on other elements.
  381. /// By default, most elements are rendered only once.
  382. /// Use this element to repeat the content across multiple pages.
  383. /// </remarks>
  384. public static IContainer Repeat(this IContainer element)
  385. {
  386. return element.Element(new RepeatContent());
  387. }
  388. #region Canvas [Obsolete]
  389. private const string CanvasDeprecatedMessage = "The Canvas API has been deprecated since version 2024.3.0. Please use the .Svg(stringContent) API to provide custom content, and consult documentation webpage regarding integrating SkiaSharp with QuestPDF: https://www.questpdf.com/concepts/skia-sharp-integration.html";
  390. [Obsolete(CanvasDeprecatedMessage)]
  391. public delegate void DrawOnCanvas(object canvas, Size availableSpace);
  392. [Obsolete(CanvasDeprecatedMessage)]
  393. public static void Canvas(this IContainer element, DrawOnCanvas handler)
  394. {
  395. throw new NotImplementedException(CanvasDeprecatedMessage);
  396. }
  397. #endregion
  398. }
  399. }