File Coverage

src/pdfmake_decode_dct.c
Criterion Covered Total %
statement 0 143 0.0
branch 0 90 0.0
condition n/a
subroutine n/a
pod n/a
total 0 233 0.0


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_decode_dct.c - JPEG (DCT) decoder for image rendering
3             *
4             * Uses stb_image for JPEG decoding. This module provides:
5             * - DCTDecode filter for PDF streams
6             * - Decoding to decoded_image_t for rendering
7             * - Support for CMYK JPEG (with Adobe marker)
8             *
9             * Note: stb_image.h must be available in include path.
10             * Download from: https://github.com/nothings/stb/blob/master/stb_image.h
11             *
12             * Reference: PDF 32000-1:2008 ยง7.4.8 DCTDecode Filter
13             */
14              
15             /* Define this in ONE source file before including stb_image.h */
16             #define STB_IMAGE_IMPLEMENTATION
17             #define STBI_NO_FAILURE_STRINGS
18             #define STBI_ONLY_JPEG /* We only need JPEG support */
19              
20             /* Forward declaration - stb_image will be included if available */
21             #ifdef PDFMAKE_HAS_STB_IMAGE
22             #include "stb_image.h"
23             #endif
24              
25             #include "pdfmake_image_render.h"
26             #include "pdfmake_arena.h"
27             #include
28             #include
29              
30             /*============================================================================
31             * Built-in minimal JPEG decoder (fallback)
32             *
33             * This is a simplified decoder for baseline JPEG only.
34             * For production use, link against stb_image or libjpeg.
35             *==========================================================================*/
36              
37             #ifndef PDFMAKE_HAS_STB_IMAGE
38              
39             /* JPEG marker definitions */
40             #define JPEG_MARKER_SOI 0xD8
41             #define JPEG_MARKER_EOI 0xD9
42             #define JPEG_MARKER_SOF0 0xC0 /* Baseline DCT */
43             #define JPEG_MARKER_SOF2 0xC2 /* Progressive DCT */
44             #define JPEG_MARKER_DHT 0xC4 /* Huffman table */
45             #define JPEG_MARKER_DQT 0xDB /* Quantization table */
46             #define JPEG_MARKER_DRI 0xDD /* Restart interval */
47             #define JPEG_MARKER_SOS 0xDA /* Start of scan */
48             #define JPEG_MARKER_APP0 0xE0 /* JFIF marker */
49             #define JPEG_MARKER_APP14 0xEE /* Adobe marker */
50             #define JPEG_MARKER_COM 0xFE /* Comment */
51              
52             typedef struct jpeg_reader {
53             const uint8_t *data;
54             size_t len;
55             size_t pos;
56            
57             int width;
58             int height;
59             int components;
60             int adobe_transform; /* 0=unknown, 1=YCbCr, 2=YCCK */
61             } jpeg_reader_t;
62              
63 0           static uint8_t jpeg_read_byte(jpeg_reader_t *r) {
64 0 0         if (r->pos >= r->len) return 0;
65 0           return r->data[r->pos++];
66             }
67              
68 0           static uint16_t jpeg_read_word(jpeg_reader_t *r) {
69 0           uint8_t hi = jpeg_read_byte(r);
70 0           uint8_t lo = jpeg_read_byte(r);
71 0           return (hi << 8) | lo;
72             }
73              
74 0           static int jpeg_skip(jpeg_reader_t *r, size_t n) {
75 0 0         if (r->pos + n > r->len) return -1;
76 0           r->pos += n;
77 0           return 0;
78             }
79              
80             /*
81             * Parse JPEG header to extract dimensions and component count.
82             * Does NOT decode the image data.
83             */
84 0           static int jpeg_parse_header(jpeg_reader_t *r) {
85 0           r->pos = 0;
86 0           r->adobe_transform = 0;
87            
88             /* Check SOI marker */
89 0 0         if (jpeg_read_byte(r) != 0xFF || jpeg_read_byte(r) != JPEG_MARKER_SOI) {
    0          
90 0           return -1; /* Not a JPEG */
91             }
92            
93 0 0         while (r->pos < r->len) {
94             uint8_t marker;
95             uint16_t seg_len;
96              
97             /* Find next marker */
98 0 0         if (jpeg_read_byte(r) != 0xFF) {
99 0           return -1;
100             }
101              
102             do {
103 0           marker = jpeg_read_byte(r);
104 0 0         } while (marker == 0xFF && r->pos < r->len);
    0          
105              
106 0 0         if (marker == JPEG_MARKER_EOI) {
107 0           break;
108             }
109              
110             /* Markers without length */
111 0 0         if (marker == 0 || marker == JPEG_MARKER_SOI ||
    0          
    0          
112 0 0         (marker >= 0xD0 && marker <= 0xD7)) {
113 0           continue;
114             }
115              
116             /* Read segment length */
117 0           seg_len = jpeg_read_word(r);
118 0 0         if (seg_len < 2) return -1;
119 0           seg_len -= 2;
120              
121 0           switch (marker) {
122 0           case JPEG_MARKER_SOF0:
123             case JPEG_MARKER_SOF2: {
124             /* Frame header */
125 0 0         if (seg_len < 6) return -1;
126 0           jpeg_read_byte(r); /* precision */
127 0           r->height = jpeg_read_word(r);
128 0           r->width = jpeg_read_word(r);
129 0           r->components = jpeg_read_byte(r);
130 0           jpeg_skip(r, seg_len - 6);
131             /* Found dimensions, could stop here */
132 0           break;
133             }
134            
135 0           case JPEG_MARKER_APP14: {
136             /* Adobe marker */
137 0 0         if (seg_len >= 12) {
138             char sig[5];
139             int i;
140 0 0         for (i = 0; i < 5; i++) {
141 0           sig[i] = jpeg_read_byte(r);
142             }
143 0 0         if (memcmp(sig, "Adobe", 5) == 0) {
144 0           jpeg_skip(r, 6); /* Skip version, flags */
145 0           r->adobe_transform = jpeg_read_byte(r);
146 0           jpeg_skip(r, seg_len - 12);
147             } else {
148 0           jpeg_skip(r, seg_len - 5);
149             }
150             } else {
151 0           jpeg_skip(r, seg_len);
152             }
153 0           break;
154             }
155            
156 0           case JPEG_MARKER_SOS:
157             /* Start of scan - we've parsed enough headers */
158 0           return 0;
159            
160 0           default:
161             /* Skip unknown segment */
162 0           jpeg_skip(r, seg_len);
163 0           break;
164             }
165             }
166            
167 0 0         return (r->width > 0 && r->height > 0) ? 0 : -1;
    0          
168             }
169              
170             #endif /* !PDFMAKE_HAS_STB_IMAGE */
171              
172             /*============================================================================
173             * Public API
174             *==========================================================================*/
175              
176 0           pdfmake_imgr_err_t pdfmake_decode_jpeg_raw(
177             const uint8_t *data, size_t len,
178             uint8_t **pixels_out, size_t *pixels_len,
179             int *width, int *height, int *components)
180             {
181             #ifndef PDFMAKE_HAS_STB_IMAGE
182             jpeg_reader_t reader;
183             #endif
184              
185 0 0         if (!data || !pixels_out || !width || !height || !components) {
    0          
    0          
    0          
    0          
186 0           return PDFMAKE_IMGR_ERR_NULL;
187             }
188            
189             #ifdef PDFMAKE_HAS_STB_IMAGE
190             /* Use stb_image */
191             {
192             int w, h, comp;
193             uint8_t *pixels = stbi_load_from_memory(data, len, &w, &h, &comp, 0);
194              
195             if (!pixels) {
196             return PDFMAKE_IMGR_ERR_DECODE_FAILED;
197             }
198              
199             *pixels_out = pixels;
200             *pixels_len = (size_t)w * h * comp;
201             *width = w;
202             *height = h;
203             *components = comp;
204              
205             return PDFMAKE_IMGR_OK;
206             }
207             #else
208             /* Fallback: parse header only, return raw DCT data */
209 0           reader.data = data;
210 0           reader.len = len;
211 0           reader.pos = 0;
212 0           reader.width = 0;
213 0           reader.height = 0;
214 0           reader.components = 0;
215 0           reader.adobe_transform = 0;
216              
217 0 0         if (jpeg_parse_header(&reader) != 0) {
218 0           return PDFMAKE_IMGR_ERR_DECODE_FAILED;
219             }
220            
221             /* For now, without stb_image, we can't actually decode.
222             * Return an error indicating we need the library.
223             * In production, you would link libjpeg or include stb_image. */
224             (void)pixels_len;
225            
226 0           *width = reader.width;
227 0           *height = reader.height;
228 0           *components = reader.components;
229 0           *pixels_out = NULL;
230            
231 0           return PDFMAKE_IMGR_ERR_UNSUPPORTED;
232             #endif
233             }
234              
235 0           pdfmake_imgr_err_t pdfmake_decode_jpeg_to_image(
236             const uint8_t *data, size_t len,
237             pdfmake_decoded_image_t **out,
238             pdfmake_arena_t *arena)
239             {
240             int width, height, components;
241 0           uint8_t *pixels = NULL;
242 0           size_t pixels_len = 0;
243             pdfmake_imgr_err_t err;
244             pdfmake_decoded_image_t *img;
245              
246 0 0         if (!data || !out) return PDFMAKE_IMGR_ERR_NULL;
    0          
247              
248 0           err = pdfmake_decode_jpeg_raw(
249             data, len, &pixels, &pixels_len, &width, &height, &components);
250              
251 0 0         if (err != PDFMAKE_IMGR_OK) {
252 0           return err;
253             }
254              
255             /* Create decoded image */
256 0           img = pdfmake_decoded_image_create(arena);
257 0 0         if (!img) {
258 0           free(pixels);
259 0           return PDFMAKE_IMGR_ERR_MEMORY;
260             }
261            
262 0           img->width = width;
263 0           img->height = height;
264 0           img->bits_per_component = 8;
265 0           img->components = components;
266 0           img->row_stride = width * components;
267 0           img->pixels = pixels;
268 0           img->pixels_len = pixels_len;
269            
270             /* Set colorspace based on component count */
271 0           switch (components) {
272 0           case 1:
273 0           img->colorspace = PDFMAKE_RCS_GRAY;
274 0           break;
275 0           case 3:
276 0           img->colorspace = PDFMAKE_RCS_RGB;
277 0           break;
278 0           case 4:
279 0           img->colorspace = PDFMAKE_RCS_CMYK;
280 0           break;
281 0           default:
282 0           img->colorspace = PDFMAKE_RCS_RGB;
283 0           break;
284             }
285            
286             /* If using arena, we need to copy pixels to arena memory */
287 0 0         if (arena) {
288 0           uint8_t *arena_pixels = pdfmake_arena_alloc(arena, pixels_len);
289 0 0         if (!arena_pixels) {
290 0           free(pixels);
291 0           return PDFMAKE_IMGR_ERR_MEMORY;
292             }
293 0           memcpy(arena_pixels, pixels, pixels_len);
294 0           free(pixels);
295 0           img->pixels = arena_pixels;
296 0           img->owns_data = 0;
297             } else {
298 0           img->owns_data = 1;
299             }
300            
301 0           *out = img;
302 0           return PDFMAKE_IMGR_OK;
303             }
304              
305             /*============================================================================
306             * DCTDecode filter for PDF streams
307             *==========================================================================*/
308              
309             /*
310             * Decode DCT-compressed stream data.
311             * This is the filter interface for PDF stream processing.
312             */
313 0           pdfmake_imgr_err_t pdfmake_dct_decode(
314             const uint8_t *src, size_t src_len,
315             uint8_t **dst, size_t *dst_len,
316             int *width, int *height, int *components)
317             {
318 0           return pdfmake_decode_jpeg_raw(src, src_len, dst, dst_len,
319             width, height, components);
320             }
321              
322             /*
323             * Check if data starts with JPEG signature.
324             */
325 0           int pdfmake_is_jpeg(const uint8_t *data, size_t len) {
326 0 0         if (!data || len < 2) return 0;
    0          
327 0 0         return (data[0] == 0xFF && data[1] == 0xD8);
    0          
328             }
329              
330             /*
331             * Get JPEG dimensions without full decode.
332             */
333 0           int pdfmake_jpeg_get_dimensions(
334             const uint8_t *data, size_t len,
335             int *width, int *height, int *components)
336             {
337             #ifndef PDFMAKE_HAS_STB_IMAGE
338             jpeg_reader_t reader;
339             #endif
340              
341 0 0         if (!data || !width || !height) return -1;
    0          
    0          
342            
343             #ifdef PDFMAKE_HAS_STB_IMAGE
344             {
345             int w, h, comp;
346             if (!stbi_info_from_memory(data, len, &w, &h, &comp)) {
347             return -1;
348             }
349             *width = w;
350             *height = h;
351             if (components) *components = comp;
352             return 0;
353             }
354             #else
355 0           reader.data = data;
356 0           reader.len = len;
357 0           reader.pos = 0;
358 0           reader.width = 0;
359 0           reader.height = 0;
360 0           reader.components = 0;
361 0           reader.adobe_transform = 0;
362 0 0         if (jpeg_parse_header(&reader) != 0) {
363 0           return -1;
364             }
365 0           *width = reader.width;
366 0           *height = reader.height;
367 0 0         if (components) *components = reader.components;
368 0           return 0;
369             #endif
370             }
371              
372             /*============================================================================
373             * CMYK JPEG handling
374             *
375             * Some PDF JPEG streams are CMYK. Adobe uses a marker to indicate
376             * the color transform:
377             * 0 = No transform (CMYK)
378             * 1 = YCbCr
379             * 2 = YCCK (CMYK with YCbCr encoding)
380             *==========================================================================*/
381              
382             /*
383             * Decode CMYK JPEG (inverts colors per Adobe convention).
384             */
385 0           pdfmake_imgr_err_t pdfmake_decode_cmyk_jpeg(
386             const uint8_t *data, size_t len,
387             pdfmake_decoded_image_t **out,
388             pdfmake_arena_t *arena)
389             {
390             pdfmake_imgr_err_t err;
391             pdfmake_decoded_image_t *img;
392              
393             /* First decode normally */
394 0           err = pdfmake_decode_jpeg_to_image(data, len, out, arena);
395 0 0         if (err != PDFMAKE_IMGR_OK) return err;
396              
397 0           img = *out;
398              
399             /* If CMYK, invert all values (Adobe convention) */
400 0 0         if (img->colorspace == PDFMAKE_RCS_CMYK && img->pixels) {
    0          
401             size_t i;
402 0 0         for (i = 0; i < img->pixels_len; i++) {
403 0           img->pixels[i] = 255 - img->pixels[i];
404             }
405             }
406              
407 0           return PDFMAKE_IMGR_OK;
408             }