Function.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /*
  2. * Copyright (c) 2023, Nico Weber <thakis@chromium.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibPDF/CommonNames.h>
  7. #include <LibPDF/Document.h>
  8. #include <LibPDF/Function.h>
  9. #include <LibPDF/ObjectDerivatives.h>
  10. // PDF 1.7 spec, 3.9 Functions
  11. namespace PDF {
  12. struct Bound {
  13. float lower;
  14. float upper;
  15. };
  16. class SampledFunction final : public Function {
  17. public:
  18. virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
  19. };
  20. PDFErrorOr<ReadonlySpan<float>> SampledFunction::evaluate(ReadonlySpan<float>) const
  21. {
  22. return Error(Error::Type::RenderingUnsupported, "SampledFunction not yet implemented"_string);
  23. }
  24. // 3.9.2 Type 2 (Exponential Interpolation) Functions
  25. class ExponentialInterpolationFunction final : public Function {
  26. public:
  27. static PDFErrorOr<NonnullRefPtr<ExponentialInterpolationFunction>> create(Document*, Vector<Bound> domain, Optional<Vector<Bound>> range, NonnullRefPtr<DictObject>);
  28. virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
  29. private:
  30. Bound m_domain;
  31. Optional<Vector<Bound>> m_range;
  32. Vector<float> m_c0;
  33. Vector<float> m_c1;
  34. float m_n;
  35. Vector<float> mutable m_values;
  36. };
  37. PDFErrorOr<NonnullRefPtr<ExponentialInterpolationFunction>>
  38. ExponentialInterpolationFunction::create(Document* document, Vector<Bound> domain, Optional<Vector<Bound>> range, NonnullRefPtr<DictObject> function_dict)
  39. {
  40. if (domain.size() != 1)
  41. return Error { Error::Type::MalformedPDF, "Function exponential requires domain with 1 entry" };
  42. // "TABLE 3.37 Additional entries specific to a type 2 function dictionary"
  43. if (!function_dict->contains(CommonNames::N))
  44. return Error { Error::Type::MalformedPDF, "Function exponential requires /N" };
  45. auto n = TRY(document->resolve(function_dict->get_value(CommonNames::N))).to_float();
  46. Vector<float> c0;
  47. if (function_dict->contains(CommonNames::C0)) {
  48. auto c0_array = TRY(function_dict->get_array(document, CommonNames::C0));
  49. for (size_t i = 0; i < c0_array->size(); i++)
  50. c0.append(c0_array->at(i).to_float());
  51. } else {
  52. c0.append(0.0f);
  53. }
  54. Vector<float> c1;
  55. if (function_dict->contains(CommonNames::C1)) {
  56. auto c1_array = TRY(function_dict->get_array(document, CommonNames::C1));
  57. for (size_t i = 0; i < c1_array->size(); i++)
  58. c1.append(c1_array->at(i).to_float());
  59. } else {
  60. c1.append(1.0f);
  61. }
  62. if (c0.size() != c1.size())
  63. return Error { Error::Type::MalformedPDF, "Function exponential mismatching C0 and C1 arrays" };
  64. if (range.has_value()) {
  65. if (range->size() != c0.size())
  66. return Error { Error::Type::MalformedPDF, "Function exponential mismatching Range and C arrays" };
  67. }
  68. // "Values of Domain must constrain x in such a way that if N is not an integer,
  69. // all values of x must be non-negative, and if N is negative, no value of x may be zero."
  70. if (n != (int)n && domain[0].lower < 0)
  71. return Error { Error::Type::MalformedPDF, "Function exponential requires non-negative bound for non-integer N" };
  72. if (n < 0 && (domain[0].lower <= 0 && domain[0].upper >= 0))
  73. return Error { Error::Type::MalformedPDF, "Function exponential with negative N requires non-zero domain" };
  74. auto function = adopt_ref(*new ExponentialInterpolationFunction());
  75. function->m_domain = domain[0];
  76. function->m_range = move(range);
  77. function->m_c0 = move(c0);
  78. function->m_c1 = move(c1);
  79. function->m_n = n;
  80. function->m_values.resize(function->m_c0.size());
  81. return function;
  82. }
  83. PDFErrorOr<ReadonlySpan<float>> ExponentialInterpolationFunction::evaluate(ReadonlySpan<float> xs) const
  84. {
  85. if (xs.size() != 1)
  86. return Error { Error::Type::MalformedPDF, "Function argument size does not match domain size" };
  87. float const x = clamp(xs[0], m_domain.lower, m_domain.upper);
  88. for (size_t i = 0; i < m_c0.size(); ++i)
  89. m_values[i] = m_c0[i] + pow(x, m_n) * (m_c1[i] - m_c0[i]);
  90. if (m_range.has_value()) {
  91. for (size_t i = 0; i < m_c0.size(); ++i)
  92. m_values[i] = clamp(m_values[i], m_range.value()[i].lower, m_range.value()[i].upper);
  93. }
  94. return m_values;
  95. }
  96. class StitchingFunction final : public Function {
  97. public:
  98. virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
  99. };
  100. PDFErrorOr<ReadonlySpan<float>> StitchingFunction::evaluate(ReadonlySpan<float>) const
  101. {
  102. return Error(Error::Type::RenderingUnsupported, "StitchingFunction not yet implemented"_string);
  103. }
  104. class PostScriptCalculatorFunction final : public Function {
  105. public:
  106. virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
  107. };
  108. PDFErrorOr<ReadonlySpan<float>> PostScriptCalculatorFunction::evaluate(ReadonlySpan<float>) const
  109. {
  110. return Error(Error::Type::RenderingUnsupported, "PostScriptCalculatorFunction not yet implemented"_string);
  111. }
  112. PDFErrorOr<NonnullRefPtr<Function>> Function::create(Document* document, NonnullRefPtr<Object> object)
  113. {
  114. if (!object->is<DictObject>() && !object->is<StreamObject>())
  115. return Error { Error::Type::MalformedPDF, "Function object must be dict or stream" };
  116. auto function_dict = object->is<DictObject>() ? object->cast<DictObject>() : object->cast<StreamObject>()->dict();
  117. // "TABLE 3.35 Entries common to all function dictionaries"
  118. if (!function_dict->contains(CommonNames::FunctionType))
  119. return Error { Error::Type::MalformedPDF, "Function requires /FunctionType" };
  120. auto function_type = TRY(document->resolve_to<int>(function_dict->get_value(CommonNames::FunctionType)));
  121. if (!function_dict->contains(CommonNames::Domain))
  122. return Error { Error::Type::MalformedPDF, "Function requires /Domain" };
  123. auto domain_array = TRY(function_dict->get_array(document, CommonNames::Domain));
  124. if (domain_array->size() % 2 != 0)
  125. return Error { Error::Type::MalformedPDF, "Function /Domain size not multiple of 2" };
  126. Vector<Bound> domain;
  127. for (size_t i = 0; i < domain_array->size(); i += 2) {
  128. domain.append({ domain_array->at(i).to_float(), domain_array->at(i + 1).to_float() });
  129. if (domain.last().lower > domain.last().upper)
  130. return Error { Error::Type::MalformedPDF, "Function /Domain lower bound > upper bound" };
  131. }
  132. // Can't use PDFErrorOr with Optional::map()
  133. Optional<Vector<Bound>> optional_range;
  134. if (function_dict->contains(CommonNames::Range)) {
  135. auto range_array = TRY(function_dict->get_array(document, CommonNames::Range));
  136. if (range_array->size() % 2 != 0)
  137. return Error { Error::Type::MalformedPDF, "Function /Range size not multiple of 2" };
  138. Vector<Bound> range;
  139. for (size_t i = 0; i < range_array->size(); i += 2) {
  140. range.append({ range_array->at(i).to_float(), range_array->at(i + 1).to_float() });
  141. if (range.last().lower > range.last().upper)
  142. return Error { Error::Type::MalformedPDF, "Function /Range lower bound > upper bound" };
  143. }
  144. optional_range = move(range);
  145. }
  146. switch (function_type) {
  147. case 0:
  148. return adopt_ref(*new SampledFunction());
  149. // The spec has no entry for `1`.
  150. case 2:
  151. // FIXME: spec is not clear on if this should work with a StreamObject.
  152. return ExponentialInterpolationFunction::create(document, move(domain), move(optional_range), function_dict);
  153. case 3:
  154. return adopt_ref(*new StitchingFunction());
  155. case 4:
  156. return adopt_ref(*new PostScriptCalculatorFunction());
  157. default:
  158. dbgln("invalid function type {}", function_type);
  159. return Error(Error::Type::MalformedPDF, "Function has unkonwn type"_string);
  160. }
  161. }
  162. }