lib/pdf/src/filter/decode.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const deflate = @import("deflate");
3
4 const model = @import("model.zig");
5 const storage_mod = @import("storage.zig");
6
7 const PredictorCase = struct {
8 params: model.Predictor,
9 source: []const u8,
10 expected: []const u8,
11 };
12
13 pub fn bytesFromStream(
14 storage: *storage_mod.Storage,
15 filter_name: ?[]const u8,
16 params: model.Predictor,
17 data: []const u8,
18 ) model.Error![]u8 {
19 const regions = try storage.acquire(data.len);
20 errdefer storage.reset();
21 const name = filter_name orelse return copy(regions.output, data);
22 if (!std.mem.eql(u8, name, "FlateDecode")) return error.UnsupportedFilter;
23 const inflated = try inflate(regions, data);
24 return unpredict(params, inflated);
25 }
26
27 fn copy(output: []u8, data: []const u8) model.Exhaustion![]u8 {
28 if (data.len > output.len) return error.DecodedByteCapacityExceeded;
29 @memcpy(output[0..data.len], data);
30 return output[0..data.len];
31 }
32
33 fn inflate(regions: storage_mod.Regions, compressed: []const u8) model.Error![]u8 {
34 const result = deflate.decompress(
35 compressed,
36 regions.output,
37 .zlib,
38 .{},
39 ) catch |err| switch (err) {
40 error.OutputTooSmall => return error.DecodedByteCapacityExceeded,
41 else => return error.DecodeFailed,
42 };
43 return regions.output[0..result.written];
44 }
45
46 fn unpredict(params: model.Predictor, data: []u8) model.DecodeError![]u8 {
47 if (params.predictor <= 1) return data;
48 const row_len = try params.rowLength();
49 const sample = try params.sampleLength();
50 if (params.predictor == 2) return untiff(params, data, row_len, sample);
51 if (params.predictor < 10 or params.predictor > 15) return error.UnsupportedFilter;
52 const stride = std.math.add(usize, row_len, 1) catch return error.DecodeFailed;
53 if (data.len == 0 or data.len % stride != 0) return error.DecodeFailed;
54 const rows = data.len / stride;
55 const output_len = std.math.mul(usize, rows, row_len) catch return error.DecodeFailed;
56 var row: usize = 0;
57 while (row < rows) : (row += 1) {
58 const source_start = row * stride;
59 const destination_start = row * row_len;
60 const tag = data[source_start];
61 const source = data[source_start + 1 ..][0..row_len];
62 const destination = data[destination_start..][0..row_len];
63 const prior = if (row == 0)
64 data[0..0]
65 else
66 data[destination_start - row_len .. destination_start];
67 try reconstructRow(tag, source, destination, prior, sample);
68 }
69 return data[0..output_len];
70 }
71
72 fn untiff(
73 params: model.Predictor,
74 data: []u8,
75 row_len: usize,
76 sample: usize,
77 ) model.DecodeError![]u8 {
78 if (params.bits != 8) return error.UnsupportedFilter;
79 if (row_len == 0 or data.len % row_len != 0) return error.DecodeFailed;
80 var row_start: usize = 0;
81 while (row_start < data.len) : (row_start += row_len) {
82 var index: usize = sample;
83 while (index < row_len) : (index += 1) {
84 data[row_start + index] +%= data[row_start + index - sample];
85 }
86 }
87 return data;
88 }
89
90 fn reconstructRow(
91 tag: u8,
92 source: []const u8,
93 destination: []u8,
94 prior: []const u8,
95 sample: usize,
96 ) model.DecodeError!void {
97 for (source, 0..) |byte, index| {
98 const left: u8 = if (index >= sample) destination[index - sample] else 0;
99 const up: u8 = if (prior.len > index) prior[index] else 0;
100 const corner: u8 = if (prior.len > index and index >= sample)
101 prior[index - sample]
102 else
103 0;
104 destination[index] = switch (tag) {
105 0 => byte,
106 1 => byte +% left,
107 2 => byte +% up,
108 3 => byte +% @as(u8, @intCast((@as(u16, left) + up) / 2)),
109 4 => byte +% paeth(left, up, corner),
110 else => return error.DecodeFailed,
111 };
112 }
113 }
114
115 fn paeth(left: u8, up: u8, corner: u8) u8 {
116 const estimate = @as(i32, left) + up - corner;
117 const to_left = @abs(estimate - left);
118 const to_up = @abs(estimate - up);
119 const to_corner = @abs(estimate - corner);
120 if (to_left <= to_up and to_left <= to_corner) return left;
121 if (to_up <= to_corner) return up;
122 return corner;
123 }
124
125 test "PDF filter reconstructs PNG predictors in place" {
126 const cases = [_]PredictorCase{
127 .{
128 .params = .{ .predictor = 12, .columns = 3 },
129 .source = &.{ 2, 1, 2, 3, 2, 1, 1, 1 },
130 .expected = &.{ 1, 2, 3, 2, 3, 4 },
131 },
132 .{
133 .params = .{ .predictor = 10, .columns = 4 },
134 .source = &.{ 1, 5, 3, 2, 1 },
135 .expected = &.{ 5, 8, 10, 11 },
136 },
137 .{
138 .params = .{ .predictor = 15, .columns = 2 },
139 .source = &.{ 3, 10, 4, 3, 4, 4 },
140 .expected = &.{ 10, 9, 9, 13 },
141 },
142 .{
143 .params = .{ .predictor = 11, .columns = 2 },
144 .source = &.{ 2, 9, 4, 4, 1, 2 },
145 .expected = &.{ 9, 4, 10, 6 },
146 },
147 };
148 for (cases) |case| {
149 var source: [16]u8 = undefined;
150 @memcpy(source[0..case.source.len], case.source);
151 const decoded = try unpredict(case.params, source[0..case.source.len]);
152 try std.testing.expectEqualSlices(u8, case.expected, decoded);
153 }
154 }
155
156 test "PDF filter reconstructs TIFF predictors in place" {
157 var source = [_]u8{ 1, 1, 1, 5, 0, 250 };
158 const output = try unpredict(.{ .predictor = 2, .columns = 3 }, &source);
159 try std.testing.expectEqualSlices(u8, &.{ 1, 2, 3, 5, 5, 255 }, output);
160 }
161
162 test "PDF filter predictor failures stay typed" {
163 var short = [_]u8{ 2, 1, 2 };
164 try std.testing.expectError(
165 error.DecodeFailed,
166 unpredict(.{ .predictor = 12, .columns = 3 }, &short),
167 );
168 var invalid = [_]u8{ 9, 1, 2 };
169 try std.testing.expectError(
170 error.DecodeFailed,
171 unpredict(.{ .predictor = 12, .columns = 2 }, &invalid),
172 );
173 var overflow = [_]u8{0};
174 try std.testing.expectError(error.DecodeFailed, unpredict(.{
175 .predictor = 12,
176 .columns = std.math.maxInt(u32),
177 .colors = std.math.maxInt(u32),
178 .bits = 16,
179 }, &overflow));
180 try std.testing.expectError(
181 error.UnsupportedFilter,
182 unpredict(.{ .predictor = 5, .columns = 2 }, &overflow),
183 );
184 }