123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- /*
- * Copyright (c) 2023, Nico Weber <thakis@chromium.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include <LibPDF/CommonNames.h>
- #include <LibPDF/Document.h>
- #include <LibPDF/Function.h>
- #include <LibPDF/ObjectDerivatives.h>
- // PDF 1.7 spec, 3.9 Functions
- namespace PDF {
- struct Bound {
- float lower;
- float upper;
- };
- class SampledFunction final : public Function {
- public:
- virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
- };
- PDFErrorOr<ReadonlySpan<float>> SampledFunction::evaluate(ReadonlySpan<float>) const
- {
- 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<NonnullRefPtr<ExponentialInterpolationFunction>> create(Document*, Vector<Bound> domain, Optional<Vector<Bound>> range, NonnullRefPtr<DictObject>);
- virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
- private:
- Bound m_domain;
- Optional<Vector<Bound>> m_range;
- Vector<float> m_c0;
- Vector<float> m_c1;
- float m_n;
- Vector<float> mutable m_values;
- };
- PDFErrorOr<NonnullRefPtr<ExponentialInterpolationFunction>>
- ExponentialInterpolationFunction::create(Document* document, Vector<Bound> domain, Optional<Vector<Bound>> range, NonnullRefPtr<DictObject> function_dict)
- {
- 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<float> 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<float> 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<ReadonlySpan<float>> ExponentialInterpolationFunction::evaluate(ReadonlySpan<float> 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 {
- public:
- virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
- };
- PDFErrorOr<ReadonlySpan<float>> StitchingFunction::evaluate(ReadonlySpan<float>) const
- {
- return Error(Error::Type::RenderingUnsupported, "StitchingFunction not yet implemented"_string);
- }
- class PostScriptCalculatorFunction final : public Function {
- public:
- virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
- };
- PDFErrorOr<ReadonlySpan<float>> PostScriptCalculatorFunction::evaluate(ReadonlySpan<float>) const
- {
- return Error(Error::Type::RenderingUnsupported, "PostScriptCalculatorFunction not yet implemented"_string);
- }
- PDFErrorOr<NonnullRefPtr<Function>> Function::create(Document* document, NonnullRefPtr<Object> object)
- {
- if (!object->is<DictObject>() && !object->is<StreamObject>())
- return Error { Error::Type::MalformedPDF, "Function object must be dict or stream" };
- auto function_dict = object->is<DictObject>() ? object->cast<DictObject>() : object->cast<StreamObject>()->dict();
- // "TABLE 3.35 Entries common to all function dictionaries"
- if (!function_dict->contains(CommonNames::FunctionType))
- return Error { Error::Type::MalformedPDF, "Function requires /FunctionType" };
- auto function_type = TRY(document->resolve_to<int>(function_dict->get_value(CommonNames::FunctionType)));
- if (!function_dict->contains(CommonNames::Domain))
- return Error { Error::Type::MalformedPDF, "Function requires /Domain" };
- auto domain_array = TRY(function_dict->get_array(document, CommonNames::Domain));
- if (domain_array->size() % 2 != 0)
- return Error { Error::Type::MalformedPDF, "Function /Domain size not multiple of 2" };
- Vector<Bound> domain;
- for (size_t i = 0; i < domain_array->size(); i += 2) {
- domain.append({ domain_array->at(i).to_float(), domain_array->at(i + 1).to_float() });
- if (domain.last().lower > domain.last().upper)
- return Error { Error::Type::MalformedPDF, "Function /Domain lower bound > upper bound" };
- }
- // Can't use PDFErrorOr with Optional::map()
- Optional<Vector<Bound>> optional_range;
- if (function_dict->contains(CommonNames::Range)) {
- auto range_array = TRY(function_dict->get_array(document, CommonNames::Range));
- if (range_array->size() % 2 != 0)
- return Error { Error::Type::MalformedPDF, "Function /Range size not multiple of 2" };
- Vector<Bound> range;
- for (size_t i = 0; i < range_array->size(); i += 2) {
- range.append({ range_array->at(i).to_float(), range_array->at(i + 1).to_float() });
- if (range.last().lower > range.last().upper)
- return Error { Error::Type::MalformedPDF, "Function /Range lower bound > upper bound" };
- }
- optional_range = move(range);
- }
- switch (function_type) {
- case 0:
- return adopt_ref(*new SampledFunction());
- // The spec has no entry for `1`.
- case 2:
- // 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:
- return adopt_ref(*new PostScriptCalculatorFunction());
- default:
- dbgln("invalid function type {}", function_type);
- return Error(Error::Type::MalformedPDF, "Function has unkonwn type"_string);
- }
- }
- }
|