Coverage Report

Created: 2025-12-18 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibGfx/ImageFormats/PAMLoader.h
Line
Count
Source
1
/*
2
 * Copyright (c) 2024, the SerenityOS developers.
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#pragma once
8
9
#include <AK/StringView.h>
10
#include <LibGfx/ImageFormats/ImageDecoder.h>
11
#include <LibGfx/ImageFormats/PortableImageMapLoader.h>
12
13
namespace Gfx {
14
15
struct PAM {
16
    static constexpr auto binary_magic_number = '7';
17
    static constexpr StringView image_type = "PAM"sv;
18
    u16 max_val { 0 };
19
    u16 depth { 0 };
20
    String tupl_type {};
21
    Optional<NonnullRefPtr<CMYKBitmap>> cmyk_bitmap {};
22
};
23
24
using PAMLoadingContext = PortableImageMapLoadingContext<PAM>;
25
26
template<class Context>
27
ErrorOr<void> read_pam_header(Context& context)
28
1.40k
{
29
    // https://netpbm.sourceforge.net/doc/pam.html
30
1.40k
    TRY(read_magic_number(context));
31
32
1.37k
    Optional<u16> width;
33
1.37k
    Optional<u16> height;
34
1.37k
    Optional<u16> depth;
35
1.37k
    Optional<u16> max_val;
36
1.37k
    Optional<String> tupltype;
37
38
7.92k
    while (true) {
39
7.92k
        TRY(read_whitespace(context));
40
41
7.88k
        auto const token = TRY(read_token(*context.stream));
42
43
7.78k
        if (token == "ENDHDR") {
44
664
            auto newline = TRY(context.stream->template read_value<u8>());
45
663
            if (newline != '\n')
46
2
                return Error::from_string_view("PAM ENDHDR not followed by newline"sv);
47
661
            break;
48
663
        }
49
50
14.2k
        TRY(read_whitespace(context));
51
14.2k
        if (token == "WIDTH") {
52
784
            if (width.has_value())
53
1
                return Error::from_string_view("Duplicate PAM WIDTH field"sv);
54
783
            width = TRY(read_number(*context.stream));
55
6.32k
        } else if (token == "HEIGHT") {
56
707
            if (height.has_value())
57
1
                return Error::from_string_view("Duplicate PAM HEIGHT field"sv);
58
706
            height = TRY(read_number(*context.stream));
59
5.61k
        } else if (token == "DEPTH") {
60
813
            if (depth.has_value())
61
1
                return Error::from_string_view("Duplicate PAM DEPTH field"sv);
62
812
            depth = TRY(read_number(*context.stream));
63
4.80k
        } else if (token == "MAXVAL") {
64
717
            if (max_val.has_value())
65
2
                return Error::from_string_view("Duplicate PAM MAXVAL field"sv);
66
715
            max_val = TRY(read_number(*context.stream));
67
4.08k
        } else if (token == "TUPLTYPE") {
68
            // FIXME: tupltype should be all text until the next newline, with leading and trailing space stripped.
69
            // FIXME: If there are multiple TUPLTYPE lines, their values are all appended.
70
3.66k
            tupltype = TRY(read_token(*context.stream));
71
3.66k
        } else {
72
418
            return Error::from_string_view("Unknown PAM token"sv);
73
418
        }
74
14.2k
    }
75
76
661
    if (!width.has_value() || !height.has_value() || !depth.has_value() || !max_val.has_value())
77
5
        return Error::from_string_view("Missing PAM header fields"sv);
78
656
    context.width = *width;
79
656
    context.height = *height;
80
656
    context.format_details.depth = *depth;
81
656
    context.format_details.max_val = *max_val;
82
656
    if (tupltype.has_value())
83
630
        context.format_details.tupl_type = *tupltype;
84
85
656
    context.state = Context::State::HeaderDecoded;
86
87
656
    return {};
88
661
}
Unexecuted instantiation: ImageDecoder.cpp:AK::ErrorOr<void, AK::Error> Gfx::read_pam_header<Gfx::PortableImageMapLoadingContext<Gfx::PAM> >(Gfx::PortableImageMapLoadingContext<Gfx::PAM>&)
FuzzPAMLoader.cpp:AK::ErrorOr<void, AK::Error> Gfx::read_pam_header<Gfx::PortableImageMapLoadingContext<Gfx::PAM> >(Gfx::PortableImageMapLoadingContext<Gfx::PAM>&)
Line
Count
Source
28
1.40k
{
29
    // https://netpbm.sourceforge.net/doc/pam.html
30
1.40k
    TRY(read_magic_number(context));
31
32
1.37k
    Optional<u16> width;
33
1.37k
    Optional<u16> height;
34
1.37k
    Optional<u16> depth;
35
1.37k
    Optional<u16> max_val;
36
1.37k
    Optional<String> tupltype;
37
38
7.92k
    while (true) {
39
7.92k
        TRY(read_whitespace(context));
40
41
7.88k
        auto const token = TRY(read_token(*context.stream));
42
43
7.78k
        if (token == "ENDHDR") {
44
664
            auto newline = TRY(context.stream->template read_value<u8>());
45
663
            if (newline != '\n')
46
2
                return Error::from_string_view("PAM ENDHDR not followed by newline"sv);
47
661
            break;
48
663
        }
49
50
14.2k
        TRY(read_whitespace(context));
51
14.2k
        if (token == "WIDTH") {
52
784
            if (width.has_value())
53
1
                return Error::from_string_view("Duplicate PAM WIDTH field"sv);
54
783
            width = TRY(read_number(*context.stream));
55
6.32k
        } else if (token == "HEIGHT") {
56
707
            if (height.has_value())
57
1
                return Error::from_string_view("Duplicate PAM HEIGHT field"sv);
58
706
            height = TRY(read_number(*context.stream));
59
5.61k
        } else if (token == "DEPTH") {
60
813
            if (depth.has_value())
61
1
                return Error::from_string_view("Duplicate PAM DEPTH field"sv);
62
812
            depth = TRY(read_number(*context.stream));
63
4.80k
        } else if (token == "MAXVAL") {
64
717
            if (max_val.has_value())
65
2
                return Error::from_string_view("Duplicate PAM MAXVAL field"sv);
66
715
            max_val = TRY(read_number(*context.stream));
67
4.08k
        } else if (token == "TUPLTYPE") {
68
            // FIXME: tupltype should be all text until the next newline, with leading and trailing space stripped.
69
            // FIXME: If there are multiple TUPLTYPE lines, their values are all appended.
70
3.66k
            tupltype = TRY(read_token(*context.stream));
71
3.66k
        } else {
72
418
            return Error::from_string_view("Unknown PAM token"sv);
73
418
        }
74
14.2k
    }
75
76
661
    if (!width.has_value() || !height.has_value() || !depth.has_value() || !max_val.has_value())
77
5
        return Error::from_string_view("Missing PAM header fields"sv);
78
656
    context.width = *width;
79
656
    context.height = *height;
80
656
    context.format_details.depth = *depth;
81
656
    context.format_details.max_val = *max_val;
82
656
    if (tupltype.has_value())
83
630
        context.format_details.tupl_type = *tupltype;
84
85
656
    context.state = Context::State::HeaderDecoded;
86
87
656
    return {};
88
661
}
89
90
using PAMImageDecoderPlugin = PortableImageDecoderPlugin<PAMLoadingContext>;
91
92
ErrorOr<void> read_image_data(PAMLoadingContext& context);
93
}