Repository: SerenityOS/serenity
File: Userland/Libraries/LibPDF/Renderer.cpp (C++)

Code before:
}

PDFErrorOr<void> Renderer::show_text(ByteString const& string)
{
    if (!text_state().font)
        return Error::rendering_unsupported_error("Can't draw text because an invalid font was in use");

    OwnPtr<ClipRAII> clip_raii;
    if (m_rendering_preferences.clip_text)
        clip_raii = make<ClipRAII>(*this);

    auto start_position = Gfx::FloatPoint { 0.0f, 0.0f };
    auto end_position = TRY(text_state().font->draw_string(m_painter, start_position, string, *this));

    // Update text matrix.
    auto delta = end_position - start_position;
    m_text_rendering_matrix_is_dirty = true;
    m_text_matrix.translate(delta);
    return {};
}

enum UpsampleMode {
    StoreValuesUnchanged,
    UpsampleTo8Bit,
};
static Vector<u8> upsample_to_8_bit(ReadonlyBytes content, int samples_per_line, int bits_per_component, UpsampleMode mode)
{
    VERIFY(bits_per_component == 1 || bits_per_component == 2 || bits_per_component == 4);
    Vector<u8> upsampled_storage;
    upsampled_storage.ensure_capacity(content.size() * 8 / bits_per_component);
    u8 const mask = (1 << bits_per_component) - 1;

    int x = 0;
    for (auto byte : content) {
        for (int i = 0; i < 8; i += bits_per_component) {
            auto value = (byte >> (8 - bits_per_component - i)) & mask;
            if (mode == UpsampleMode::UpsampleTo8Bit)
                upsampled_storage.append(value * (255 / mask));
            else
                upsampled_storage.append(value);
            ++x;

            // "Byte boundaries are ignored, except that each row of sample data must begin on a byte boundary."
            if (x == samples_per_line) {
                x = 0;
                break;
            }
        }
    }
    return upsampled_storage;
}

Review comment (bug):
The error message for an invalid color space is vague; include the actual color space name or family to aid debugging.
```suggestion
            return Error::malformed_error("Invalid transparency group /CS: Color space family is {}", color_space->family());
```

Code after:
    m_text_matrix.translate(delta);
    return {};
}

PDFErrorOr<TransparencyGroupAttributes> Renderer::read_transparency_group_attributes(NonnullRefPtr<DictObject> group_dict)
{
    // TABLE 7.13 Additional entries specific to a transparency group attributes dictionary
    TransparencyGroupAttributes attributes;

    auto name = TRY(group_dict->get_name(m_document, CommonNames::S))->name();
    if (name != CommonNames::Transparency)
        return Error::malformed_error("Invalid transparency group /S: {}", name);

    if (group_dict->contains(CommonNames::CS)) {
        auto color_space_object = TRY(group_dict->get_object(m_document, CommonNames::CS));
        auto color_space = TRY(get_color_space_from_document(color_space_object));

        // "These restrictions exclude Lab and lightness-chromaticity ICCBased color spac-
        //  es, as well as the special color spaces Pattern, Indexed, Separation, and DeviceN."
        if (color_space->family() == ColorSpaceFamily::Lab
            || color_space->family() == ColorSpaceFamily::Pattern
            || color_space->family() == ColorSpaceFamily::Indexed
            || color_space->family() == ColorSpaceFamily::Separation
            || color_space->family() == ColorSpaceFamily::DeviceN) {
            // FIXME: Reject Lab and lightness-chromaticity ICCBased color spaces.
            return Error::malformed_error("Invalid transparency group /CS");
        }

        attributes.color_space = color_space;
    }

    if (group_dict->contains(CommonNames::I))
        attributes.is_isolated = TRY(m_document->resolve_to<bool>(group_dict->get_value(CommonNames::I)));

    if (group_dict->contains(CommonNames::K))
        attributes.is_knockout = TRY(m_document->resolve_to<bool>(group_dict->get_value(CommonNames::K)));

    return attributes;
}

PDFErrorOr<void> Renderer::paint_form_xobject(NonnullRefPtr<StreamObject> form)
{
    Optional<NonnullRefPtr<DictObject>> xobject_resources {};
    if (form->dict()->contains(CommonNames::Resources)) {
        xobject_resources = TRY(form->dict()->get_dict(m_document, CommonNames::Resources));
    }

    Optional<TransparencyGroupAttributes> transparency_group_attributes;
    if (form->dict()->contains(CommonNames::Group)) {
        auto group = TRY(form->dict()->get_dict(m_document, CommonNames::Group));
        transparency_group_attributes = TRY(read_transparency_group_attributes(group));