From bbd86ee4f395e23ebc5a8d5b1488f0baaf51a64b Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Sat, 4 Nov 2023 22:14:29 +0100 Subject: [PATCH] LibPDF: Implement ExponentialInterpolationFunction --- Userland/Libraries/LibPDF/CommonNames.h | 2 + Userland/Libraries/LibPDF/Function.cpp | 88 ++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/Userland/Libraries/LibPDF/CommonNames.h b/Userland/Libraries/LibPDF/CommonNames.h index 66e1e71ae9..df13ab1c1d 100644 --- a/Userland/Libraries/LibPDF/CommonNames.h +++ b/Userland/Libraries/LibPDF/CommonNames.h @@ -23,6 +23,8 @@ X(BitsPerComponent) \ X(BlackPoint) \ X(C) \ + X(C0) \ + X(C1) \ X(CA) \ X(CCITTFaxDecode) \ X(CF) \ diff --git a/Userland/Libraries/LibPDF/Function.cpp b/Userland/Libraries/LibPDF/Function.cpp index be1e3b8bda..e06e63b592 100644 --- a/Userland/Libraries/LibPDF/Function.cpp +++ b/Userland/Libraries/LibPDF/Function.cpp @@ -28,14 +28,95 @@ PDFErrorOr> SampledFunction::evaluate(ReadonlySpan) c return Error(Error::Type::RenderingUnsupported, "SampledFunction not yet implemented"_string); } +// 3.9.2 Type 2 (Exponential Interpolation) Functions class ExponentialInterpolationFunction final : public Function { public: + static PDFErrorOr> create(Document*, Vector domain, Optional> range, NonnullRefPtr); virtual PDFErrorOr> evaluate(ReadonlySpan) const override; + +private: + Bound m_domain; + Optional> m_range; + + Vector m_c0; + Vector m_c1; + float m_n; + + Vector mutable m_values; }; -PDFErrorOr> ExponentialInterpolationFunction::evaluate(ReadonlySpan) const +PDFErrorOr> +ExponentialInterpolationFunction::create(Document* document, Vector domain, Optional> range, NonnullRefPtr function_dict) { - return Error(Error::Type::RenderingUnsupported, "ExponentialInterpolationFunction not yet implemented"_string); + if (domain.size() != 1) + return Error { Error::Type::MalformedPDF, "Function exponential requires domain with 1 entry" }; + + // "TABLE 3.37 Additional entries specific to a type 2 function dictionary" + + if (!function_dict->contains(CommonNames::N)) + return Error { Error::Type::MalformedPDF, "Function exponential requires /N" }; + + auto n = TRY(document->resolve(function_dict->get_value(CommonNames::N))).to_float(); + + Vector c0; + if (function_dict->contains(CommonNames::C0)) { + auto c0_array = TRY(function_dict->get_array(document, CommonNames::C0)); + for (size_t i = 0; i < c0_array->size(); i++) + c0.append(c0_array->at(i).to_float()); + } else { + c0.append(0.0f); + } + + Vector c1; + if (function_dict->contains(CommonNames::C1)) { + auto c1_array = TRY(function_dict->get_array(document, CommonNames::C1)); + for (size_t i = 0; i < c1_array->size(); i++) + c1.append(c1_array->at(i).to_float()); + } else { + c1.append(1.0f); + } + + if (c0.size() != c1.size()) + return Error { Error::Type::MalformedPDF, "Function exponential mismatching C0 and C1 arrays" }; + + if (range.has_value()) { + if (range->size() != c0.size()) + return Error { Error::Type::MalformedPDF, "Function exponential mismatching Range and C arrays" }; + } + + // "Values of Domain must constrain x in such a way that if N is not an integer, + // all values of x must be non-negative, and if N is negative, no value of x may be zero." + if (n != (int)n && domain[0].lower < 0) + return Error { Error::Type::MalformedPDF, "Function exponential requires non-negative bound for non-integer N" }; + if (n < 0 && (domain[0].lower <= 0 && domain[0].upper >= 0)) + return Error { Error::Type::MalformedPDF, "Function exponential with negative N requires non-zero domain" }; + + auto function = adopt_ref(*new ExponentialInterpolationFunction()); + function->m_domain = domain[0]; + function->m_range = move(range); + function->m_c0 = move(c0); + function->m_c1 = move(c1); + function->m_n = n; + function->m_values.resize(function->m_c0.size()); + return function; +} + +PDFErrorOr> ExponentialInterpolationFunction::evaluate(ReadonlySpan xs) const +{ + if (xs.size() != 1) + return Error { Error::Type::MalformedPDF, "Function argument size does not match domain size" }; + + float const x = clamp(xs[0], m_domain.lower, m_domain.upper); + + for (size_t i = 0; i < m_c0.size(); ++i) + m_values[i] = m_c0[i] + pow(x, m_n) * (m_c1[i] - m_c0[i]); + + if (m_range.has_value()) { + for (size_t i = 0; i < m_c0.size(); ++i) + m_values[i] = clamp(m_values[i], m_range.value()[i].lower, m_range.value()[i].upper); + } + + return m_values; } class StitchingFunction final : public Function { @@ -105,7 +186,8 @@ PDFErrorOr> Function::create(Document* document, Nonnull return adopt_ref(*new SampledFunction()); // The spec has no entry for `1`. case 2: - return adopt_ref(*new ExponentialInterpolationFunction()); + // FIXME: spec is not clear on if this should work with a StreamObject. + return ExponentialInterpolationFunction::create(document, move(domain), move(optional_range), function_dict); case 3: return adopt_ref(*new StitchingFunction()); case 4: