Coverage Report

Created: 2026-09-14 07:15

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.47k
{
29
    // https://netpbm.sourceforge.net/doc/pam.html
30
1.47k
    TRY(read_magic_number(context));
31
32
1.44k
    Optional<u16> width;
33
1.44k
    Optional<u16> height;
34
1.44k
    Optional<u16> depth;
35
1.44k
    Optional<u16> max_val;
36
1.44k
    Optional<String> tupltype;
37
38
8.09k
    while (true) {
39
8.09k
        TRY(read_whitespace(context));
40
41
8.04k
        auto const token = TRY(read_token(*context.stream));
42
43
7.92k
        if (token == "ENDHDR") {
44
714
            auto newline = TRY(context.stream->template read_value<u8>());
45
713
            if (newline != '\n')
46
2
                return Error::from_string_view("PAM ENDHDR not followed by newline"sv);
47
711
            break;
48
713
        }
49
50
14.4k
        TRY(read_whitespace(context));
51
14.4k
        if (token == "WIDTH") {
52
834
            if (width.has_value())
53
1
                return Error::from_string_view("Duplicate PAM WIDTH field"sv);
54
833
            width = TRY(read_number(*context.stream));
55
6.36k
        } else if (token == "HEIGHT") {
56
759
            if (height.has_value())
57
1
                return Error::from_string_view("Duplicate PAM HEIGHT field"sv);
58
758
            height = TRY(read_number(*context.stream));
59
5.60k
        } else if (token == "DEPTH") {
60
868
            if (depth.has_value())
61
1
                return Error::from_string_view("Duplicate PAM DEPTH field"sv);
62
867
            depth = TRY(read_number(*context.stream));
63
4.73k
        } else if (token == "MAXVAL") {
64
767
            if (max_val.has_value())
65
2
                return Error::from_string_view("Duplicate PAM MAXVAL field"sv);
66
765
            max_val = TRY(read_number(*context.stream));
67
3.96k
        } 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.55k
            tupltype = TRY(read_token(*context.stream));
71
3.54k
        } else {
72
419
            return Error::from_string_view("Unknown PAM token"sv);
73
419
        }
74
14.4k
    }
75
76
711
    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
706
    context.width = *width;
79
706
    context.height = *height;
80
706
    context.format_details.depth = *depth;
81
706
    context.format_details.max_val = *max_val;
82
706
    if (tupltype.has_value())
83
678
        context.format_details.tupl_type = *tupltype;
84
85
706
    context.state = Context::State::HeaderDecoded;
86
87
706
    return {};
88
711
}
89
90
using PAMImageDecoderPlugin = PortableImageDecoderPlugin<PAMLoadingContext>;
91
92
ErrorOr<void> read_image_data(PAMLoadingContext& context);
93
}