Helpers.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Linq.Expressions;
  5. using System.Reflection;
  6. using System.Text.RegularExpressions;
  7. using QuestPDF.Drawing;
  8. using QuestPDF.Infrastructure;
  9. using QuestPDF.Skia;
  10. using static QuestPDF.Skia.SkSvgImageSize.Unit;
  11. namespace QuestPDF.Helpers
  12. {
  13. internal static class Helpers
  14. {
  15. static Helpers()
  16. {
  17. SkNativeDependencyCompatibilityChecker.Test();
  18. }
  19. internal static byte[] LoadEmbeddedResource(string resourceName)
  20. {
  21. using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
  22. using var reader = new BinaryReader(stream);
  23. return reader.ReadBytes((int) stream.Length);
  24. }
  25. private static PropertyInfo? ToPropertyInfo<T, TValue>(this Expression<Func<T, TValue>> selector)
  26. {
  27. return (selector.Body as MemberExpression)?.Member as PropertyInfo;
  28. }
  29. internal static string? GetPropertyName<T, TValue>(this Expression<Func<T, TValue>> selector) where TValue : class
  30. {
  31. return selector.ToPropertyInfo()?.Name;
  32. }
  33. internal static TValue? GetPropertyValue<T, TValue>(this T target, Expression<Func<T, TValue>> selector) where TValue : class
  34. {
  35. return selector.ToPropertyInfo()?.GetValue(target) as TValue;
  36. }
  37. internal static void SetPropertyValue<T, TValue>(this T target, Expression<Func<T, TValue>> selector, TValue value)
  38. {
  39. var property = selector.ToPropertyInfo() ?? throw new Exception("Expected property with getter and setter.");
  40. property?.SetValue(target, value);
  41. }
  42. internal static string PrettifyName(this string text)
  43. {
  44. return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2", RegexOptions.Compiled);
  45. }
  46. internal static void VisitChildren(this Element? root, Action<Element?> handler)
  47. {
  48. Traverse(root);
  49. void Traverse(Element? element)
  50. {
  51. if (element == null)
  52. return;
  53. if (element is ContainerElement containerElement)
  54. {
  55. Traverse(containerElement.Child);
  56. }
  57. else
  58. {
  59. foreach (var child in element.GetChildren())
  60. Traverse(child);
  61. }
  62. handler(element);
  63. }
  64. }
  65. internal static void ReleaseDisposableChildren(this Element? element)
  66. {
  67. element.VisitChildren(x => (x as IDisposable)?.Dispose());
  68. }
  69. internal static bool IsGreaterThan(this float first, float second)
  70. {
  71. return first > second + Size.Epsilon;
  72. }
  73. internal static bool IsLessThan(this float first, float second)
  74. {
  75. return first < second - Size.Epsilon;
  76. }
  77. public static bool AreClose(double a, double b)
  78. {
  79. return Math.Abs(a - b) <= Size.Epsilon;
  80. }
  81. internal static bool IsNegative(this Size size)
  82. {
  83. return size.Width < -Size.Epsilon || size.Height < -Size.Epsilon;
  84. }
  85. internal static bool IsCloseToZero(this Size size)
  86. {
  87. return Math.Abs(size.Width) < Size.Epsilon && Math.Abs(size.Height) < Size.Epsilon;
  88. }
  89. internal static bool IsEmpty(this Element element)
  90. {
  91. return element.Measure(Size.Zero).Type == SpacePlanType.Empty;
  92. }
  93. internal static int ToQualityValue(this ImageCompressionQuality quality)
  94. {
  95. return quality switch
  96. {
  97. ImageCompressionQuality.Best => 100,
  98. ImageCompressionQuality.VeryHigh => 90,
  99. ImageCompressionQuality.High => 75,
  100. ImageCompressionQuality.Medium => 50,
  101. ImageCompressionQuality.Low => 25,
  102. ImageCompressionQuality.VeryLow => 10,
  103. _ => throw new ArgumentOutOfRangeException(nameof(quality), quality, null)
  104. };
  105. }
  106. internal static bool ToDownsamplingStrategy(this ImageCompressionQuality quality)
  107. {
  108. return quality switch
  109. {
  110. ImageCompressionQuality.Best => false,
  111. ImageCompressionQuality.VeryHigh => false,
  112. ImageCompressionQuality.High => true,
  113. ImageCompressionQuality.Medium => true,
  114. ImageCompressionQuality.Low => true,
  115. ImageCompressionQuality.VeryLow => true,
  116. _ => throw new ArgumentOutOfRangeException(nameof(quality), quality, null)
  117. };
  118. }
  119. internal static SkImage CompressImage(this SkImage image, ImageCompressionQuality compressionQuality)
  120. {
  121. return image.ResizeAndCompress(image.Width, image.Height, compressionQuality.ToQualityValue(), compressionQuality.ToDownsamplingStrategy());
  122. }
  123. internal static SkImage ResizeAndCompressImage(this SkImage image, ImageSize targetResolution, ImageCompressionQuality compressionQuality)
  124. {
  125. if (targetResolution.Width == 0 || targetResolution.Height == 0)
  126. targetResolution = new ImageSize(1, 1);
  127. return image.ResizeAndCompress(targetResolution.Width, targetResolution.Height, compressionQuality.ToQualityValue(), compressionQuality.ToDownsamplingStrategy());
  128. }
  129. internal static SkImage GetImageWithSmallerSize(SkImage one, SkImage second)
  130. {
  131. return one.EncodedDataSize < second.EncodedDataSize
  132. ? one
  133. : second;
  134. }
  135. internal static void OpenFileUsingDefaultProgram(string filePath)
  136. {
  137. var process = new Process
  138. {
  139. StartInfo = new ProcessStartInfo(filePath)
  140. {
  141. UseShellExecute = true
  142. }
  143. };
  144. process.Start();
  145. process.WaitForExit();
  146. }
  147. internal static string ApplicationFilesPath
  148. {
  149. get
  150. {
  151. var baseDirectory = AppContext.BaseDirectory;
  152. if (string.IsNullOrWhiteSpace(baseDirectory) || baseDirectory == "/")
  153. return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
  154. return baseDirectory;
  155. }
  156. }
  157. internal static (float widthScale, float heightScale) CalculateSpaceScale(this SkSvgImage image, Size availableSpace)
  158. {
  159. var widthScale = CalculateDimensionScale(availableSpace.Width, image.Size.Width, image.Size.WidthUnit);
  160. var heightScale = CalculateDimensionScale(availableSpace.Height, image.Size.Height, image.Size.HeightUnit);
  161. return (widthScale, heightScale);
  162. float CalculateDimensionScale(float availableSize, float imageSize, SkSvgImageSize.Unit unit)
  163. {
  164. if (unit == Percentage)
  165. return 100f / imageSize;
  166. if (unit is Centimeters or Millimeters or Inches or Points or Picas)
  167. return availableSize / ConvertToPoints(imageSize, unit);
  168. return availableSize / imageSize;
  169. }
  170. float ConvertToPoints(float value, SkSvgImageSize.Unit unit)
  171. {
  172. const float InchToCentimetre = 2.54f;
  173. const float InchToPoints = 72;
  174. // in CSS dpi is set to 96, but Skia uses more traditional 90
  175. const float PointToPixel = 90f / 72;
  176. var points = unit switch
  177. {
  178. Centimeters => value / InchToCentimetre * InchToPoints,
  179. Millimeters => value / 10 / InchToCentimetre * InchToPoints,
  180. Inches => value * InchToPoints,
  181. Points => value,
  182. Picas => value * 12,
  183. _ => throw new ArgumentOutOfRangeException()
  184. };
  185. // different naming schema: SVG pixel = PDF point
  186. return points * PointToPixel;
  187. }
  188. }
  189. }
  190. }