diff --git a/Userland/Libraries/LibPDF/CommonNames.h b/Userland/Libraries/LibPDF/CommonNames.h index df13ab1c1d..941109e3d1 100644 --- a/Userland/Libraries/LibPDF/CommonNames.h +++ b/Userland/Libraries/LibPDF/CommonNames.h @@ -22,6 +22,7 @@ X(BaseFont) \ X(BitsPerComponent) \ X(BlackPoint) \ + X(Bounds) \ X(C) \ X(C0) \ X(C1) \ @@ -59,6 +60,7 @@ X(Differences) \ X(Domain) \ X(E) \ + X(Encode) \ X(Encoding) \ X(Encrypt) \ X(EncryptMetadata) \ @@ -83,6 +85,7 @@ X(FontFile2) \ X(FontFile3) \ X(FunctionType) \ + X(Functions) \ X(Gamma) \ X(H) \ X(Height) \ diff --git a/Userland/Libraries/LibPDF/Function.cpp b/Userland/Libraries/LibPDF/Function.cpp index 88a1313a19..7bbede7ecf 100644 --- a/Userland/Libraries/LibPDF/Function.cpp +++ b/Userland/Libraries/LibPDF/Function.cpp @@ -122,9 +122,92 @@ PDFErrorOr> ExponentialInterpolationFunction::evaluate(Reado 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) const { return Error(Error::Type::RenderingUnsupported, "StitchingFunction not yet implemented"_string); @@ -715,7 +798,8 @@ PDFErrorOr> Function::create(Document* document, Nonnull // 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()); + // 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" };