mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 21:52:45 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1041 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1041 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2023, Nico Weber <thakis@chromium.org>
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #include <AK/NonnullOwnPtr.h>
 | |
| #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;
 | |
| };
 | |
| 
 | |
| // 3.9.1 Type 0 (Sampled) Functions
 | |
| class SampledFunction final : public Function {
 | |
| public:
 | |
|     static PDFErrorOr<NonnullRefPtr<SampledFunction>> create(Document*, Vector<Bound> domain, Optional<Vector<Bound>> range, NonnullRefPtr<StreamObject>);
 | |
|     virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
 | |
| 
 | |
| private:
 | |
|     SampledFunction(NonnullRefPtr<StreamObject>);
 | |
| 
 | |
|     float sample(Vector<int> const& coordinates, size_t r) const
 | |
|     {
 | |
|         // "For a function with multidimensional input (more than one input variable),
 | |
|         //  the sample values in the first dimension vary fastest,
 | |
|         //  and the values in the last dimension vary slowest.
 | |
|         //  For example, for a function f(a, b, c), where a, b, and c vary from 0 to 9 in steps of 1,
 | |
|         //  the sample values would appear in this order:
 | |
|         //  f(0,0,0), f(1,0,0), ..., f(9,0,0),
 | |
|         //  f(0,1,0), f(1,1,0), ..., f(9,1,0),
 | |
|         //  f(0,2,0), f(1, 2, 0), ..., f(9, 9, 0),
 | |
|         //  f(0, 0, 1), f(1, 0, 1), and so on."
 | |
|         // Implied is that functions with multiple outputs store all outputs next to each other.
 | |
|         size_t stride = 1;
 | |
|         size_t offset = 0;
 | |
|         for (size_t i = 0; i < coordinates.size(); ++i) {
 | |
|             offset += coordinates[i] * stride;
 | |
|             stride *= m_sizes[i];
 | |
|         }
 | |
|         return m_sample_data[offset * m_range.size() + r];
 | |
|     }
 | |
| 
 | |
|     Vector<Bound> m_domain;
 | |
|     Vector<Bound> m_range;
 | |
| 
 | |
|     Vector<unsigned> m_sizes;
 | |
|     int m_bits_per_sample { 0 };
 | |
| 
 | |
|     enum class Order {
 | |
|         Linear = 1,
 | |
|         Cubic = 3,
 | |
|     };
 | |
|     Order m_order { Order::Linear };
 | |
| 
 | |
|     Vector<Bound> m_encode;
 | |
|     Vector<Bound> m_decode;
 | |
| 
 | |
|     NonnullRefPtr<StreamObject> m_stream;
 | |
|     ReadonlyBytes m_sample_data;
 | |
| 
 | |
|     Vector<float> mutable m_inputs;
 | |
|     Vector<unsigned> mutable m_left_index;
 | |
|     Vector<float> mutable m_outputs;
 | |
| };
 | |
| 
 | |
| SampledFunction::SampledFunction(NonnullRefPtr<StreamObject> stream)
 | |
|     : m_stream(move(stream))
 | |
|     , m_sample_data(m_stream->bytes())
 | |
| {
 | |
| }
 | |
| 
 | |
| PDFErrorOr<NonnullRefPtr<SampledFunction>>
 | |
| SampledFunction::create(Document* document, Vector<Bound> domain, Optional<Vector<Bound>> range, NonnullRefPtr<StreamObject> stream)
 | |
| {
 | |
|     if (!range.has_value())
 | |
|         return Error { Error::Type::MalformedPDF, "Function type 0 requires range" };
 | |
| 
 | |
|     // "TABLE 3.36 Additional entries specific to a type 0 function dictionary"
 | |
|     auto const& dict = stream->dict();
 | |
| 
 | |
|     if (!dict->contains(CommonNames::Size))
 | |
|         return Error { Error::Type::MalformedPDF, "Function type 0 requires /Size" };
 | |
|     auto size_array = TRY(dict->get_array(document, CommonNames::Size));
 | |
|     Vector<unsigned> sizes;
 | |
|     for (auto const& size_value : *size_array) {
 | |
|         if (size_value.to_int() <= 0)
 | |
|             return Error { Error::Type::MalformedPDF, "Function type 0 /Size entry not positive" };
 | |
|         sizes.append(static_cast<unsigned>(size_value.to_int()));
 | |
|     }
 | |
|     if (sizes.size() != domain.size())
 | |
|         return Error { Error::Type::MalformedPDF, "Function type 0 /Size array has invalid size" };
 | |
| 
 | |
|     if (!dict->contains(CommonNames::BitsPerSample))
 | |
|         return Error { Error::Type::MalformedPDF, "Function type 0 requires /BitsPerSample" };
 | |
|     auto bits_per_sample = TRY(document->resolve_to<int>(dict->get_value(CommonNames::BitsPerSample)));
 | |
|     switch (bits_per_sample) {
 | |
|     case 1:
 | |
|     case 2:
 | |
|     case 4:
 | |
|     case 8:
 | |
|     case 12:
 | |
|     case 16:
 | |
|     case 24:
 | |
|     case 32:
 | |
|         // Ok!
 | |
|         break;
 | |
|     default:
 | |
|         dbgln("invalid /BitsPerSample {}", bits_per_sample);
 | |
|         return Error { Error::Type::MalformedPDF, "Function type 0 has invalid /BitsPerSample" };
 | |
|     }
 | |
| 
 | |
|     Order order = Order::Linear;
 | |
|     if (dict->contains(CommonNames::Order))
 | |
|         order = static_cast<Order>(TRY(document->resolve_to<int>(dict->get_value(CommonNames::Order))));
 | |
|     if (order != Order::Linear && order != Order::Cubic)
 | |
|         return Error { Error::Type::MalformedPDF, "Function type 0 has invalid /Order" };
 | |
| 
 | |
|     Vector<Bound> encode;
 | |
|     if (dict->contains(CommonNames::Encode)) {
 | |
|         auto encode_array = TRY(dict->get_array(document, CommonNames::Encode));
 | |
|         if (encode_array->size() % 2 != 0)
 | |
|             return Error { Error::Type::MalformedPDF, "Function type 0 /Encode size not multiple of 2" };
 | |
|         for (size_t i = 0; i < encode_array->size(); i += 2)
 | |
|             encode.append({ encode_array->at(i).to_float(), encode_array->at(i + 1).to_float() });
 | |
|     } else {
 | |
|         for (unsigned const size : sizes)
 | |
|             encode.append({ 0, static_cast<float>(size - 1) });
 | |
|     }
 | |
|     if (encode.size() != sizes.size())
 | |
|         return Error { Error::Type::MalformedPDF, "Function type 0 /Encode array has invalid size" };
 | |
| 
 | |
|     Vector<Bound> decode;
 | |
|     if (dict->contains(CommonNames::Decode)) {
 | |
|         auto decode_array = TRY(dict->get_array(document, CommonNames::Decode));
 | |
|         if (decode_array->size() % 2 != 0)
 | |
|             return Error { Error::Type::MalformedPDF, "Function type 0 /Decode size not multiple of 2" };
 | |
|         for (size_t i = 0; i < decode_array->size(); i += 2)
 | |
|             decode.append({ decode_array->at(i).to_float(), decode_array->at(i + 1).to_float() });
 | |
|     } else {
 | |
|         decode = range.value();
 | |
|     }
 | |
|     if (decode.size() != range.value().size())
 | |
|         return Error { Error::Type::MalformedPDF, "Function type 0 /Decode array has invalid size" };
 | |
| 
 | |
|     size_t size_product = 1;
 | |
|     for (unsigned const size : sizes)
 | |
|         size_product *= size;
 | |
|     size_t bits_per_plane = size_product * bits_per_sample;
 | |
|     size_t total_bits = bits_per_plane * decode.size();
 | |
|     if (stream->bytes().size() < ceil_div(total_bits, 8ull))
 | |
|         return Error { Error::Type::MalformedPDF, "Function type 0 stream too small" };
 | |
| 
 | |
|     auto function = adopt_ref(*new SampledFunction(stream));
 | |
|     function->m_domain = move(domain);
 | |
|     function->m_range = move(range.value());
 | |
|     function->m_sizes = move(sizes);
 | |
|     function->m_bits_per_sample = bits_per_sample;
 | |
|     function->m_order = order;
 | |
|     function->m_encode = move(encode);
 | |
|     function->m_decode = move(decode);
 | |
|     function->m_inputs.resize(function->m_domain.size());
 | |
|     function->m_left_index.resize(function->m_domain.size());
 | |
|     function->m_outputs.resize(function->m_range.size());
 | |
|     return function;
 | |
| }
 | |
| 
 | |
| PDFErrorOr<ReadonlySpan<float>> SampledFunction::evaluate(ReadonlySpan<float> xs) const
 | |
| {
 | |
|     if (xs.size() != m_domain.size())
 | |
|         return Error { Error::Type::MalformedPDF, "Function argument size does not match domain size" };
 | |
| 
 | |
|     if (m_order != Order::Linear)
 | |
|         return Error { Error::Type::RenderingUnsupported, "Sample function with cubic order not yet implemented" };
 | |
| 
 | |
|     if (m_bits_per_sample != 8)
 | |
|         return Error { Error::Type::RenderingUnsupported, "Sample function with bits per sample != 8 not yet implemented" };
 | |
| 
 | |
|     auto interpolate = [](float x, float x_min, float x_max, float y_min, float y_max) {
 | |
|         return mix(y_min, y_max, (x - x_min) / (x_max - x_min));
 | |
|     };
 | |
| 
 | |
|     for (size_t i = 0; i < m_domain.size(); ++i) {
 | |
|         float x = clamp(xs[i], m_domain[i].lower, m_domain[i].upper);
 | |
|         float e = interpolate(x, m_domain[i].lower, m_domain[i].upper, m_encode[i].lower, m_encode[i].upper);
 | |
| 
 | |
|         unsigned n = m_sizes[i] - 1;
 | |
|         float ec = clamp(e, 0.0f, static_cast<float>(n));
 | |
|         m_left_index[i] = min(ec, n - 1);
 | |
|         m_inputs[i] = ec - m_left_index[i];
 | |
|     }
 | |
| 
 | |
|     for (size_t r = 0; r < m_range.size(); ++r) {
 | |
|         // For 1-D input data, we need to sample 2 points, one to the left and one to the right, and then interpolate between them.
 | |
|         // For 2-D input data, we need to sample 4 points (top-left, top-right, bottom-left, bottom-right),
 | |
|         // then reduce them to 2 points by interpolating along y, and then to 1 by interpolating along x.
 | |
|         // For 3-D input data, it's 8 points in a cube around the point, then reduce to 4 points by interpolating along z,
 | |
|         // then 2 by interpolating along y, then 1 by interpolating along x.
 | |
|         // So for the general case, we create 2**N samples, and then for each coordinate, we cut the number of samples in half
 | |
|         // by interpolating along that coordinate.
 | |
|         Vector<float> samples;
 | |
|         samples.resize(1 << m_domain.size());
 | |
|         // The i'th bit of mask indicates if the i'th coordinate is rounded up or down.
 | |
|         Vector<int> coordinates;
 | |
|         coordinates.resize(m_domain.size());
 | |
|         for (size_t mask = 0; mask < (1u << m_domain.size()); ++mask) {
 | |
|             for (size_t i = 0; i < m_domain.size(); ++i)
 | |
|                 coordinates[i] = m_left_index[i] + ((mask >> i) & 1u);
 | |
|             samples[mask] = sample(coordinates, r);
 | |
|         }
 | |
| 
 | |
|         for (int i = static_cast<int>(m_domain.size() - 1); i >= 0; --i) {
 | |
|             for (size_t mask = 0; mask < (1u << i); ++mask)
 | |
|                 samples[mask] = mix(samples[mask], samples[mask | (1 << i)], m_inputs[i]);
 | |
|         }
 | |
| 
 | |
|         float result = interpolate(samples[0], 0.0f, 255.0f, m_decode[r].lower, m_decode[r].upper);
 | |
|         m_outputs[r] = clamp(result, m_range[r].lower, m_range[r].upper);
 | |
|     }
 | |
| 
 | |
|     return m_outputs;
 | |
| }
 | |
| 
 | |
| // 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:
 | |
|     static PDFErrorOr<NonnullRefPtr<StitchingFunction>> create(Document*, Vector<Bound> domain, Optional<Vector<Bound>> range, NonnullRefPtr<DictObject>);
 | |
|     virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
 | |
| 
 | |
| private:
 | |
|     StitchingFunction(Vector<NonnullRefPtr<Function>>);
 | |
| 
 | |
|     Bound m_domain;
 | |
|     Optional<Vector<Bound>> m_range;
 | |
| 
 | |
|     Vector<NonnullRefPtr<Function>> m_functions;
 | |
|     Vector<float> m_bounds;
 | |
|     Vector<Bound> m_encode;
 | |
|     Vector<float> mutable m_result;
 | |
| };
 | |
| 
 | |
| StitchingFunction::StitchingFunction(Vector<NonnullRefPtr<Function>> functions)
 | |
|     : m_functions(move(functions))
 | |
| {
 | |
| }
 | |
| 
 | |
| PDFErrorOr<NonnullRefPtr<StitchingFunction>>
 | |
| StitchingFunction::create(Document* document, Vector<Bound> domain, Optional<Vector<Bound>> range, NonnullRefPtr<DictObject> dict)
 | |
| {
 | |
|     if (domain.size() != 1)
 | |
|         return Error { Error::Type::MalformedPDF, "Function stitching requires domain with 1 entry" };
 | |
| 
 | |
|     // "TABLE 3.38 Additional entries specific to a type 3 function dictionary"
 | |
| 
 | |
|     if (!dict->contains(CommonNames::Functions))
 | |
|         return Error { Error::Type::MalformedPDF, "Function stitching requires /Functions" };
 | |
|     auto functions_array = TRY(dict->get_array(document, CommonNames::Functions));
 | |
| 
 | |
|     Vector<NonnullRefPtr<Function>> functions;
 | |
|     for (size_t i = 0; i < functions_array->size(); i++) {
 | |
|         auto function = TRY(Function::create(document, functions_array->get_object_at(i)));
 | |
|         functions.append(move(function));
 | |
|     }
 | |
| 
 | |
|     if (functions.is_empty())
 | |
|         return Error { Error::Type::MalformedPDF, "Function stitching requires at least one function" };
 | |
| 
 | |
|     if (!dict->contains(CommonNames::Bounds))
 | |
|         return Error { Error::Type::MalformedPDF, "Function stitching requires /Bounds" };
 | |
|     auto bounds_array = TRY(dict->get_array(document, CommonNames::Bounds));
 | |
| 
 | |
|     if (bounds_array->size() != functions.size() - 1)
 | |
|         return Error { Error::Type::MalformedPDF, "Function stitching /Bounds size does not match /Functions size" };
 | |
| 
 | |
|     Vector<float> bounds;
 | |
|     for (size_t i = 0; i < bounds_array->size(); i++) {
 | |
|         bounds.append(bounds_array->at(i).to_float());
 | |
|         if (i > 0 && bounds[i - 1] >= bounds[i])
 | |
|             return Error { Error::Type::MalformedPDF, "Function stitching /Bounds not strictly increasing" };
 | |
|     }
 | |
| 
 | |
|     if (!bounds.is_empty()) {
 | |
|         if (domain[0].lower == domain[0].upper)
 | |
|             return Error { Error::Type::MalformedPDF, "Function stitching /Bounds requires non-zero domain" };
 | |
|         if (domain[0].lower >= bounds[0] || bounds.last() >= domain[0].upper)
 | |
|             return Error { Error::Type::MalformedPDF, "Function stitching /Bounds out of domain" };
 | |
|     }
 | |
| 
 | |
|     if (!dict->contains(CommonNames::Encode))
 | |
|         return Error { Error::Type::MalformedPDF, "Function stitching requires /Encode" };
 | |
|     auto encode_array = TRY(dict->get_array(document, CommonNames::Encode));
 | |
| 
 | |
|     if (encode_array->size() != functions.size() * 2)
 | |
|         return Error { Error::Type::MalformedPDF, "Function stitching /Encode size does not match /Functions size" };
 | |
| 
 | |
|     Vector<Bound> encode;
 | |
|     for (size_t i = 0; i < encode_array->size(); i += 2) {
 | |
|         encode.append({ encode_array->at(i).to_float(), encode_array->at(i + 1).to_float() });
 | |
|         if (encode.last().lower > encode.last().upper)
 | |
|             return Error { Error::Type::MalformedPDF, "Function stitching /Encode lower bound > upper bound" };
 | |
|     }
 | |
| 
 | |
|     auto function = adopt_ref(*new StitchingFunction(move(functions)));
 | |
|     function->m_domain = domain[0];
 | |
|     function->m_range = move(range);
 | |
|     function->m_bounds = move(bounds);
 | |
|     function->m_encode = move(encode);
 | |
|     if (function->m_range.has_value())
 | |
|         function->m_result.resize(function->m_range.value().size());
 | |
|     return function;
 | |
| }
 | |
| 
 | |
| PDFErrorOr<ReadonlySpan<float>> StitchingFunction::evaluate(ReadonlySpan<float> xs) const
 | |
| {
 | |
|     if (xs.size() != 1)
 | |
|         return Error { Error::Type::MalformedPDF, "Function argument size does not match domain size" };
 | |
| 
 | |
|     float x = clamp(xs[0], m_domain.lower, m_domain.upper);
 | |
| 
 | |
|     // FIXME: binary search
 | |
|     size_t i = 0;
 | |
|     for (; i < m_bounds.size(); ++i) {
 | |
|         if (x < m_bounds[i])
 | |
|             break;
 | |
|     }
 | |
|     float left_bound = i == 0 ? m_domain.lower : m_bounds[i - 1];
 | |
|     float right_bound = i == m_bounds.size() ? m_domain.upper : m_bounds[i];
 | |
| 
 | |
|     auto interpolate = [](float x, float x_min, float x_max, float y_min, float y_max) {
 | |
|         return y_min + (x - x_min) * (y_max - y_min) / (x_max - x_min);
 | |
|     };
 | |
|     x = interpolate(x, left_bound, right_bound, m_encode[i].lower, m_encode[i].upper);
 | |
|     auto result = TRY(m_functions[i]->evaluate({ &x, 1 }));
 | |
|     if (!m_range.has_value())
 | |
|         return result;
 | |
| 
 | |
|     if (result.size() != m_range.value().size())
 | |
|         return Error { Error::Type::MalformedPDF, "Function stitching result size does not match range size" };
 | |
|     for (size_t i = 0; i < result.size(); ++i)
 | |
|         m_result[i] = clamp(result[i], m_range.value()[i].lower, m_range.value()[i].upper);
 | |
|     return m_result;
 | |
| }
 | |
| 
 | |
| class PostScriptCalculatorFunction final : public Function {
 | |
| public:
 | |
|     static PDFErrorOr<NonnullRefPtr<PostScriptCalculatorFunction>> create(Vector<Bound> domain, Optional<Vector<Bound>> range, NonnullRefPtr<StreamObject>);
 | |
|     virtual PDFErrorOr<ReadonlySpan<float>> evaluate(ReadonlySpan<float>) const override;
 | |
| 
 | |
| private:
 | |
|     // TABLE 3.39 Operators in type 4 functions
 | |
|     enum class OperatorType {
 | |
|         Operand,
 | |
| 
 | |
|         // Arithmetic operators
 | |
|         Abs,
 | |
|         Add,
 | |
|         Atan,
 | |
|         Ceiling,
 | |
|         Cos,
 | |
|         Cvi,
 | |
|         Cvr,
 | |
|         Div,
 | |
|         Exp,
 | |
|         Floor,
 | |
|         Idiv,
 | |
|         Ln,
 | |
|         Log,
 | |
|         Mod,
 | |
|         Mul,
 | |
|         Neg,
 | |
|         Round,
 | |
|         Sin,
 | |
|         Sqrt,
 | |
|         Sub,
 | |
|         Truncate,
 | |
| 
 | |
|         // Relational, boolean, and bitwise operators
 | |
|         And,
 | |
|         Bitshift,
 | |
|         Eq,
 | |
|         False,
 | |
|         Ge,
 | |
|         Gt,
 | |
|         Le,
 | |
|         Lt,
 | |
|         Ne,
 | |
|         Not,
 | |
|         Or,
 | |
|         True,
 | |
|         Xor,
 | |
| 
 | |
|         // Conditional operators
 | |
|         If,
 | |
|         IfElse,
 | |
| 
 | |
|         // Stack operators
 | |
|         Copy,
 | |
|         Dup,
 | |
|         Exch,
 | |
|         Index,
 | |
|         Pop,
 | |
|         Roll,
 | |
|     };
 | |
|     static Optional<OperatorType> parse_operator(Reader&);
 | |
| 
 | |
|     struct IfElse;
 | |
|     struct Token {
 | |
|         // FIXME: Could nan-box this.
 | |
|         OperatorType type;
 | |
|         Variant<Empty, float, int> value {};
 | |
|     };
 | |
| 
 | |
|     struct IfElse {
 | |
|         Vector<Token> if_true;
 | |
|         Vector<Token> if_false;
 | |
|     };
 | |
| 
 | |
|     static PDFErrorOr<Vector<Token>> parse_postscript_calculator_function(Reader&, Vector<NonnullOwnPtr<IfElse>>&);
 | |
| 
 | |
|     struct Stack {
 | |
|         Array<float, 100> stack;
 | |
|         size_t top { 0 };
 | |
| 
 | |
|         PDFErrorOr<void> push(float value)
 | |
|         {
 | |
|             if (top == stack.size())
 | |
|                 return Error { Error::Type::RenderingUnsupported, "PostScript stack overflow"_string };
 | |
|             stack[top++] = value;
 | |
|             return {};
 | |
|         }
 | |
| 
 | |
|         PDFErrorOr<float> pop()
 | |
|         {
 | |
|             if (top == 0)
 | |
|                 return Error { Error::Type::RenderingUnsupported, "PostScript stack underflow"_string };
 | |
|             return stack[--top];
 | |
|         }
 | |
|     };
 | |
|     PDFErrorOr<void> execute(Vector<Token> const&, Stack&) const;
 | |
| 
 | |
|     Vector<Bound> m_domain;
 | |
|     Vector<Bound> m_range;
 | |
|     Vector<Token> m_tokens;
 | |
|     Vector<NonnullOwnPtr<IfElse>> m_if_elses;
 | |
| 
 | |
|     Vector<float> mutable m_result;
 | |
| };
 | |
| 
 | |
| Optional<PostScriptCalculatorFunction::OperatorType> PostScriptCalculatorFunction::parse_operator(Reader& reader)
 | |
| {
 | |
|     auto match_keyword = [&](char const* keyword) {
 | |
|         if (reader.matches(keyword)) {
 | |
|             reader.consume((int)strlen(keyword));
 | |
|             return true;
 | |
|         }
 | |
|         return false;
 | |
|     };
 | |
| 
 | |
|     if (match_keyword("abs"))
 | |
|         return OperatorType::Abs;
 | |
|     if (match_keyword("add"))
 | |
|         return OperatorType::Add;
 | |
|     if (match_keyword("atan"))
 | |
|         return OperatorType::Atan;
 | |
|     if (match_keyword("ceiling"))
 | |
|         return OperatorType::Ceiling;
 | |
|     if (match_keyword("cos"))
 | |
|         return OperatorType::Cos;
 | |
|     if (match_keyword("cvi"))
 | |
|         return OperatorType::Cvi;
 | |
|     if (match_keyword("cvr"))
 | |
|         return OperatorType::Cvr;
 | |
|     if (match_keyword("div"))
 | |
|         return OperatorType::Div;
 | |
|     if (match_keyword("exp"))
 | |
|         return OperatorType::Exp;
 | |
|     if (match_keyword("floor"))
 | |
|         return OperatorType::Floor;
 | |
|     if (match_keyword("idiv"))
 | |
|         return OperatorType::Idiv;
 | |
|     if (match_keyword("ln"))
 | |
|         return OperatorType::Ln;
 | |
|     if (match_keyword("log"))
 | |
|         return OperatorType::Log;
 | |
|     if (match_keyword("mod"))
 | |
|         return OperatorType::Mod;
 | |
|     if (match_keyword("mul"))
 | |
|         return OperatorType::Mul;
 | |
|     if (match_keyword("neg"))
 | |
|         return OperatorType::Neg;
 | |
|     if (match_keyword("round"))
 | |
|         return OperatorType::Round;
 | |
|     if (match_keyword("sin"))
 | |
|         return OperatorType::Sin;
 | |
|     if (match_keyword("sqrt"))
 | |
|         return OperatorType::Sqrt;
 | |
|     if (match_keyword("sub"))
 | |
|         return OperatorType::Sub;
 | |
|     if (match_keyword("truncate"))
 | |
|         return OperatorType::Truncate;
 | |
|     if (match_keyword("and"))
 | |
|         return OperatorType::And;
 | |
|     if (match_keyword("bitshift"))
 | |
|         return OperatorType::Bitshift;
 | |
|     if (match_keyword("eq"))
 | |
|         return OperatorType::Eq;
 | |
|     if (match_keyword("false"))
 | |
|         return OperatorType::False;
 | |
|     if (match_keyword("ge"))
 | |
|         return OperatorType::Ge;
 | |
|     if (match_keyword("gt"))
 | |
|         return OperatorType::Gt;
 | |
|     if (match_keyword("le"))
 | |
|         return OperatorType::Le;
 | |
|     if (match_keyword("lt"))
 | |
|         return OperatorType::Lt;
 | |
|     if (match_keyword("ne"))
 | |
|         return OperatorType::Ne;
 | |
|     if (match_keyword("not"))
 | |
|         return OperatorType::Not;
 | |
|     if (match_keyword("or"))
 | |
|         return OperatorType::Or;
 | |
|     if (match_keyword("true"))
 | |
|         return OperatorType::True;
 | |
|     if (match_keyword("xor"))
 | |
|         return OperatorType::Xor;
 | |
|     // If and Ifelse handled elsewhere.
 | |
|     if (match_keyword("copy"))
 | |
|         return OperatorType::Copy;
 | |
|     if (match_keyword("dup"))
 | |
|         return OperatorType::Dup;
 | |
|     if (match_keyword("exch"))
 | |
|         return OperatorType::Exch;
 | |
|     if (match_keyword("index"))
 | |
|         return OperatorType::Index;
 | |
|     if (match_keyword("pop"))
 | |
|         return OperatorType::Pop;
 | |
|     if (match_keyword("roll"))
 | |
|         return OperatorType::Roll;
 | |
|     return {};
 | |
| }
 | |
| 
 | |
| PDFErrorOr<Vector<PostScriptCalculatorFunction::Token>>
 | |
| PostScriptCalculatorFunction::parse_postscript_calculator_function(Reader& reader, Vector<NonnullOwnPtr<IfElse>>& if_elses)
 | |
| {
 | |
|     // Assumes valid syntax.
 | |
|     reader.consume_whitespace();
 | |
|     if (!reader.consume('{'))
 | |
|         return Error { Error::Type::MalformedPDF, "PostScript expected '{'" };
 | |
| 
 | |
|     Vector<PostScriptCalculatorFunction::Token> tokens;
 | |
|     while (!reader.matches('}')) {
 | |
|         if (reader.consume_whitespace())
 | |
|             continue;
 | |
| 
 | |
|         if (reader.matches('{')) {
 | |
|             auto if_true = TRY(parse_postscript_calculator_function(reader, if_elses));
 | |
|             reader.consume_whitespace();
 | |
|             if (reader.matches("if")) {
 | |
|                 reader.consume(2);
 | |
|                 tokens.append({ OperatorType::If, (int)if_elses.size() });
 | |
|                 if_elses.append(adopt_own(*new IfElse { move(if_true), {} }));
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             VERIFY(reader.matches('{'));
 | |
|             auto if_false = TRY(parse_postscript_calculator_function(reader, if_elses));
 | |
|             reader.consume_whitespace();
 | |
| 
 | |
|             if (reader.matches("ifelse")) {
 | |
|                 reader.consume(6);
 | |
|                 tokens.append({ OperatorType::IfElse, (int)if_elses.size() });
 | |
|                 if_elses.append(adopt_own(*new IfElse { move(if_true), move(if_false) }));
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             return Error { Error::Type::MalformedPDF, "PostScript confused parsing {}-delimited expressions"_string };
 | |
|         }
 | |
| 
 | |
|         if (reader.matches_number()) {
 | |
|             // FIXME: Nicer float conversion.
 | |
|             char const* start = reinterpret_cast<char const*>(reader.bytes().slice(reader.offset()).data());
 | |
|             char* endptr;
 | |
|             float value = strtof(start, &endptr);
 | |
|             reader.move_by(endptr - start);
 | |
|             tokens.append({ OperatorType::Operand, value });
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         if (Optional<OperatorType> op = parse_operator(reader); op.has_value()) {
 | |
|             tokens.append({ op.value() });
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         return Error { Error::Type::MalformedPDF, "PostScript unknown operator"_string };
 | |
|     }
 | |
|     VERIFY(reader.consume('}'));
 | |
| 
 | |
|     return tokens;
 | |
| }
 | |
| 
 | |
| PDFErrorOr<NonnullRefPtr<PostScriptCalculatorFunction>>
 | |
| PostScriptCalculatorFunction::create(Vector<Bound> domain, Optional<Vector<Bound>> range, NonnullRefPtr<StreamObject> stream)
 | |
| {
 | |
|     if (!range.has_value())
 | |
|         return Error { Error::Type::MalformedPDF, "Function type 4 requires /Range" };
 | |
| 
 | |
|     Vector<NonnullOwnPtr<IfElse>> if_elses;
 | |
|     Reader reader { stream->bytes() };
 | |
|     auto tokens = TRY(parse_postscript_calculator_function(reader, if_elses));
 | |
| 
 | |
|     auto function = adopt_ref(*new PostScriptCalculatorFunction());
 | |
|     function->m_domain = move(domain);
 | |
|     function->m_range = move(range.value());
 | |
|     function->m_tokens = move(tokens);
 | |
|     function->m_if_elses = move(if_elses);
 | |
|     return function;
 | |
| }
 | |
| 
 | |
| PDFErrorOr<void> PostScriptCalculatorFunction::execute(Vector<Token> const& tokens, Stack& stack) const
 | |
| {
 | |
| 
 | |
|     for (auto const& token : tokens) {
 | |
|         switch (token.type) {
 | |
|         case OperatorType::Operand:
 | |
|             TRY(stack.push(token.value.get<float>()));
 | |
|             break;
 | |
|         case OperatorType::Abs:
 | |
|             TRY(stack.push(fabsf(TRY(stack.pop()))));
 | |
|             break;
 | |
|         case OperatorType::Add: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(a + b));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Atan: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(AK::to_degrees(atan2f(b, a))));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Ceiling:
 | |
|             TRY(stack.push(ceilf(TRY(stack.pop()))));
 | |
|             break;
 | |
|         case OperatorType::Cos:
 | |
|             TRY(stack.push(cosf(AK::to_radians(TRY(stack.pop())))));
 | |
|             break;
 | |
|         case OperatorType::Cvi:
 | |
|             TRY(stack.push((int)TRY(stack.pop())));
 | |
|             break;
 | |
|         case OperatorType::Cvr:
 | |
|             TRY(stack.push(TRY(stack.pop())));
 | |
|             break;
 | |
|         case OperatorType::Div: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(a / b));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Exp:
 | |
|             TRY(stack.push(expf(TRY(stack.pop()))));
 | |
|             break;
 | |
|         case OperatorType::Floor:
 | |
|             TRY(stack.push(floorf(TRY(stack.pop()))));
 | |
|             break;
 | |
|         case OperatorType::Idiv: {
 | |
|             int b = (int)TRY(stack.pop());
 | |
|             int a = (int)TRY(stack.pop());
 | |
|             TRY(stack.push(a / b));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Ln:
 | |
|             TRY(stack.push(logf(TRY(stack.pop()))));
 | |
|             break;
 | |
|         case OperatorType::Log:
 | |
|             TRY(stack.push(log10f(TRY(stack.pop()))));
 | |
|             break;
 | |
|         case OperatorType::Mod: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(fmodf(a, b)));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Mul: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(a * b));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Neg:
 | |
|             TRY(stack.push(-TRY(stack.pop())));
 | |
|             break;
 | |
|         case OperatorType::Round:
 | |
|             TRY(stack.push(roundf(TRY(stack.pop()))));
 | |
|             break;
 | |
|         case OperatorType::Sin:
 | |
|             TRY(stack.push(sinf(AK::to_radians(TRY(stack.pop())))));
 | |
|             break;
 | |
|         case OperatorType::Sqrt:
 | |
|             TRY(stack.push(sqrtf(TRY(stack.pop()))));
 | |
|             break;
 | |
|         case OperatorType::Sub: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(a - b));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Truncate:
 | |
|             TRY(stack.push(truncf(TRY(stack.pop()))));
 | |
|             break;
 | |
|         case OperatorType::And: {
 | |
|             int b = (int)TRY(stack.pop());
 | |
|             int a = (int)TRY(stack.pop());
 | |
|             TRY(stack.push(a & b));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Bitshift: {
 | |
|             int b = (int)TRY(stack.pop());
 | |
|             int a = (int)TRY(stack.pop());
 | |
|             if (b >= 0)
 | |
|                 TRY(stack.push(a << b));
 | |
|             else
 | |
|                 TRY(stack.push(a >> -b));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Eq: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(a == b ? 1.0f : 0.0f));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::False:
 | |
|             TRY(stack.push(0.0f));
 | |
|             break;
 | |
|         case OperatorType::Ge: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(a >= b ? 1.0f : 0.0f));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Gt: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(a > b ? 1.0f : 0.0f));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Le: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(a <= b ? 1.0f : 0.0f));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Lt: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(a < b ? 1.0f : 0.0f));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Ne: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(a != b ? 1.0f : 0.0f));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Not: {
 | |
|             TRY(stack.push(TRY(stack.pop()) == 0.0f ? 1.0f : 0.0f));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Or: {
 | |
|             int b = (int)TRY(stack.pop());
 | |
|             int a = (int)TRY(stack.pop());
 | |
|             TRY(stack.push(a | b));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::True:
 | |
|             TRY(stack.push(1.0f));
 | |
|             break;
 | |
|         case OperatorType::Xor: {
 | |
|             int b = (int)TRY(stack.pop());
 | |
|             int a = (int)TRY(stack.pop());
 | |
|             TRY(stack.push(a ^ b));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::If: {
 | |
|             auto const& if_else = m_if_elses[token.value.get<int>()];
 | |
|             VERIFY(if_else->if_false.is_empty());
 | |
|             if (TRY(stack.pop()) != 0.0f)
 | |
|                 TRY(execute(if_else->if_true, stack));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::IfElse: {
 | |
|             auto const& if_else = m_if_elses[token.value.get<int>()];
 | |
|             if (TRY(stack.pop()) != 0.0f)
 | |
|                 TRY(execute(if_else->if_true, stack));
 | |
|             else
 | |
|                 TRY(execute(if_else->if_false, stack));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Copy: {
 | |
|             int n = (int)TRY(stack.pop());
 | |
|             if (n < 0)
 | |
|                 return Error { Error::Type::RenderingUnsupported, "PostScript copy with negative argument"_string };
 | |
|             if ((size_t)n > stack.top)
 | |
|                 return Error { Error::Type::RenderingUnsupported, "PostScript copy with argument larger than stack"_string };
 | |
|             for (int i = 0; i < n; ++i)
 | |
|                 TRY(stack.push(stack.stack[stack.top - n]));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Dup:
 | |
|             TRY(stack.push(stack.stack[stack.top - 1]));
 | |
|             break;
 | |
|         case OperatorType::Exch: {
 | |
|             float b = TRY(stack.pop());
 | |
|             float a = TRY(stack.pop());
 | |
|             TRY(stack.push(b));
 | |
|             TRY(stack.push(a));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Index: {
 | |
|             int i = (int)TRY(stack.pop());
 | |
|             if (i < 0)
 | |
|                 return Error { Error::Type::RenderingUnsupported, "PostScript index with negative argument"_string };
 | |
|             if ((size_t)i >= stack.top)
 | |
|                 return Error { Error::Type::RenderingUnsupported, "PostScript index with argument larger than stack"_string };
 | |
|             TRY(stack.push(stack.stack[stack.top - 1 - i]));
 | |
|             break;
 | |
|         }
 | |
|         case OperatorType::Pop:
 | |
|             TRY(stack.pop());
 | |
|             break;
 | |
|         case OperatorType::Roll: {
 | |
|             int j = (int)TRY(stack.pop());
 | |
|             int n = (int)TRY(stack.pop());
 | |
|             if (n < 0)
 | |
|                 return Error { Error::Type::RenderingUnsupported, "PostScript roll with negative argument"_string };
 | |
|             if ((size_t)n > stack.top)
 | |
|                 return Error { Error::Type::RenderingUnsupported, "PostScript roll with argument larger than stack"_string };
 | |
|             if (j < 0)
 | |
|                 j += n;
 | |
|             if (j < 0)
 | |
|                 return Error { Error::Type::RenderingUnsupported, "PostScript roll with negative argument"_string };
 | |
|             if (j > n)
 | |
|                 return Error { Error::Type::RenderingUnsupported, "PostScript roll with argument larger than stack"_string };
 | |
|             // http://pointer-overloading.blogspot.com/2013/09/algorithms-rotating-one-dimensional.html
 | |
|             auto elements = stack.stack.span().slice(stack.top - n, n);
 | |
|             elements.reverse();
 | |
|             elements.slice(0, j).reverse();
 | |
|             elements.slice(j).reverse();
 | |
|             break;
 | |
|         }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return {};
 | |
| }
 | |
| 
 | |
| PDFErrorOr<ReadonlySpan<float>> PostScriptCalculatorFunction::evaluate(ReadonlySpan<float> xs) const
 | |
| {
 | |
|     if (xs.size() != m_domain.size())
 | |
|         return Error { Error::Type::MalformedPDF, "Function argument size does not match domain size" };
 | |
| 
 | |
|     Stack stack;
 | |
|     for (size_t i = 0; i < xs.size(); ++i)
 | |
|         TRY(stack.push(clamp(xs[i], m_domain[i].lower, m_domain[i].upper)));
 | |
| 
 | |
|     TRY(execute(m_tokens, stack));
 | |
| 
 | |
|     if (stack.top != m_range.size())
 | |
|         return Error { Error::Type::MalformedPDF, "Postscript result size does not match range size"_string };
 | |
| 
 | |
|     // FIXME: Does this need reversing?
 | |
|     m_result.resize(stack.top);
 | |
|     for (size_t i = 0; i < stack.top; ++i)
 | |
|         m_result[i] = clamp(stack.stack[i], m_range[i].lower, m_range[i].upper);
 | |
|     return m_result;
 | |
| }
 | |
| 
 | |
| 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:
 | |
|         if (!object->is<StreamObject>())
 | |
|             return Error { Error::Type::MalformedPDF, "Function type 0 requires stream object" };
 | |
|         return SampledFunction::create(document, move(domain), move(optional_range), object->cast<StreamObject>());
 | |
|     // 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:
 | |
|         // FIXME: spec is not clear on if this should work with a StreamObject.
 | |
|         return StitchingFunction::create(document, move(domain), move(optional_range), function_dict);
 | |
|     case 4:
 | |
|         if (!object->is<StreamObject>())
 | |
|             return Error { Error::Type::MalformedPDF, "Function type 4 requires stream object" };
 | |
|         return PostScriptCalculatorFunction::create(move(domain), move(optional_range), object->cast<StreamObject>());
 | |
|     default:
 | |
|         dbgln("invalid function type {}", function_type);
 | |
|         return Error(Error::Type::MalformedPDF, "Function has unkonwn type"_string);
 | |
|     }
 | |
| }
 | |
| 
 | |
| }
 | 
