/* * Copyright (c) 2023, Nico Weber * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include // 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> create(Document*, Vector domain, Optional> range, NonnullRefPtr); virtual PDFErrorOr> evaluate(ReadonlySpan) const override; private: SampledFunction(NonnullRefPtr); Vector m_domain; Vector m_range; Vector m_sizes; int m_bits_per_sample { 0 }; enum class Order { Linear = 1, Cubic = 3, }; Order m_order { Order::Linear }; Vector m_encode; Vector m_decode; NonnullRefPtr m_stream; ReadonlyBytes m_sample_data; Vector mutable m_outputs; }; SampledFunction::SampledFunction(NonnullRefPtr stream) : m_stream(move(stream)) , m_sample_data(m_stream->bytes()) { } PDFErrorOr> SampledFunction::create(Document* document, Vector domain, Optional> range, NonnullRefPtr 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 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(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(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(TRY(document->resolve_to(dict->get_value(CommonNames::Order)))); if (order != Order::Linear && order != Order::Cubic) return Error { Error::Type::MalformedPDF, "Function type 0 has invalid /Order" }; Vector 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(size - 1) }); } if (encode.size() != sizes.size()) return Error { Error::Type::MalformedPDF, "Function type 0 /Encode array has invalid size" }; Vector 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_outputs.resize(function->m_range.size()); return function; } PDFErrorOr> SampledFunction::evaluate(ReadonlySpan x) const { if (x.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_domain.size() != 1) return Error { Error::Type::RenderingUnsupported, "Sample function with m > 1 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 y_min + (x - x_min) * (y_max - y_min) / (x_max - x_min); }; float xc = clamp(x[0], m_domain[0].lower, m_domain[0].upper); float e = interpolate(xc, m_domain[0].lower, m_domain[0].upper, m_encode[0].lower, m_encode[0].upper); float ec = clamp(e, 0.0f, static_cast(m_sizes[0] - 1)); float e0 = floor(ec); float e1 = ceil(ec); if (e0 == e1) { if (e0 == 0.0f) e1 = 1.0f; else e0 = e1 - 1.0f; } size_t plane_size = m_range.size(); for (size_t i = 0; i < m_range.size(); ++i) { float s0 = m_sample_data[(size_t)e0 * plane_size + i]; float s1 = m_sample_data[(size_t)e1 * plane_size + i]; float r0 = interpolate(ec, e0, e1, s0, s1); r0 = interpolate(r0, 0.0f, 255.0f, m_decode[i].lower, m_decode[i].upper); m_outputs[i] = clamp(r0, m_range[i].lower, m_range[i].upper); } return m_outputs; } // 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::create(Document* document, Vector domain, Optional> range, NonnullRefPtr 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 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 { public: static PDFErrorOr> create(Document*, Vector domain, Optional> range, NonnullRefPtr); virtual PDFErrorOr> evaluate(ReadonlySpan) const override; private: StitchingFunction(Vector>); Bound m_domain; Optional> m_range; Vector> m_functions; Vector m_bounds; Vector m_encode; Vector mutable m_result; }; StitchingFunction::StitchingFunction(Vector> functions) : m_functions(move(functions)) { } PDFErrorOr> StitchingFunction::create(Document* document, Vector domain, Optional> range, NonnullRefPtr 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> 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 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 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> StitchingFunction::evaluate(ReadonlySpan 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> create(Vector domain, Optional> range, NonnullRefPtr); virtual PDFErrorOr> evaluate(ReadonlySpan) 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 parse_operator(Reader&); struct IfElse; struct Token { // FIXME: Could nan-box this. OperatorType type; Variant value {}; }; struct IfElse { Vector if_true; Vector if_false; }; static PDFErrorOr> parse_postscript_calculator_function(Reader&, Vector>&); struct Stack { Array stack; size_t top { 0 }; PDFErrorOr push(float value) { if (top == stack.size()) return Error { Error::Type::RenderingUnsupported, "PostScript stack overflow"_string }; stack[top++] = value; return {}; } PDFErrorOr pop() { if (top == 0) return Error { Error::Type::RenderingUnsupported, "PostScript stack underflow"_string }; return stack[--top]; } }; PDFErrorOr execute(Vector const&, Stack&) const; Vector m_domain; Vector m_range; Vector m_tokens; Vector> m_if_elses; Vector mutable m_result; }; Optional 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> PostScriptCalculatorFunction::parse_postscript_calculator_function(Reader& reader, Vector>& if_elses) { // Assumes valid syntax. reader.consume_whitespace(); if (!reader.consume('{')) return Error { Error::Type::MalformedPDF, "PostScript expected '{'" }; Vector 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(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 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> PostScriptCalculatorFunction::create(Vector domain, Optional> range, NonnullRefPtr stream) { if (!range.has_value()) return Error { Error::Type::MalformedPDF, "Function type 4 requires /Range" }; Vector> 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 PostScriptCalculatorFunction::execute(Vector const& tokens, Stack& stack) const { for (auto const& token : tokens) { switch (token.type) { case OperatorType::Operand: TRY(stack.push(token.value.get())); 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()]; 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()]; 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> PostScriptCalculatorFunction::evaluate(ReadonlySpan 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> Function::create(Document* document, NonnullRefPtr object) { if (!object->is() && !object->is()) return Error { Error::Type::MalformedPDF, "Function object must be dict or stream" }; auto function_dict = object->is() ? object->cast() : object->cast()->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(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 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> 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 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()) return Error { Error::Type::MalformedPDF, "Function type 0 requires stream object" }; return SampledFunction::create(document, move(domain), move(optional_range), object->cast()); // 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()) return Error { Error::Type::MalformedPDF, "Function type 4 requires stream object" }; return PostScriptCalculatorFunction::create(move(domain), move(optional_range), object->cast()); default: dbgln("invalid function type {}", function_type); return Error(Error::Type::MalformedPDF, "Function has unkonwn type"_string); } } }