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 }