diff --git a/Userland/Libraries/LibPDF/CommonNames.h b/Userland/Libraries/LibPDF/CommonNames.h index 15148d9b07..66e1e71ae9 100644 --- a/Userland/Libraries/LibPDF/CommonNames.h +++ b/Userland/Libraries/LibPDF/CommonNames.h @@ -55,6 +55,7 @@ X(DeviceN) \ X(DeviceRGB) \ X(Differences) \ + X(Domain) \ X(E) \ X(Encoding) \ X(Encrypt) \ @@ -79,6 +80,7 @@ X(FontFile) \ X(FontFile2) \ X(FontFile3) \ + X(FunctionType) \ X(Gamma) \ X(H) \ X(Height) \ diff --git a/Userland/Libraries/LibPDF/Function.cpp b/Userland/Libraries/LibPDF/Function.cpp index 7262839eda..be1e3b8bda 100644 --- a/Userland/Libraries/LibPDF/Function.cpp +++ b/Userland/Libraries/LibPDF/Function.cpp @@ -4,12 +4,20 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include +#include // PDF 1.7 spec, 3.9 Functions namespace PDF { +struct Bound { + float lower; + float upper; +}; + class SampledFunction final : public Function { public: virtual PDFErrorOr> evaluate(ReadonlySpan) const override; @@ -50,9 +58,62 @@ PDFErrorOr> PostScriptCalculatorFunction::evaluate(ReadonlyS return Error(Error::Type::RenderingUnsupported, "PostScriptCalculatorFunction not yet implemented"_string); } -PDFErrorOr> Function::create(Document*, NonnullRefPtr) +PDFErrorOr> Function::create(Document* document, NonnullRefPtr object) { - return Error(Error::Type::RenderingUnsupported, "Function creation not yet implemented"_string); + 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: + return adopt_ref(*new SampledFunction()); + // The spec has no entry for `1`. + case 2: + return adopt_ref(*new ExponentialInterpolationFunction()); + 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); + } } }