// // PDF stream functions for pdfio. // // Copyright © 2021 by Michael R Sweet. // // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. // // // Include necessary headers... // #include "pdfio-private.h" // // Local functions... // static ssize_t stream_read(pdfio_stream_t *st, char *buffer, size_t bytes); // // 'pdfioStreamClose()' - Close a (data) stream in a PDF file. // bool // O - `true` on success, `false` on failure pdfioStreamClose(pdfio_stream_t *st) // I - Stream { // TODO: Implement me (void)st; return (false); } // // '_pdfioStreamCreate()' - Create a stream for writing. // // Note: pdfioObjCreateStream handles writing the object and its dictionary. // pdfio_stream_t * // O - Stream or `NULL` on error _pdfioStreamCreate( pdfio_obj_t *obj, // I - Object pdfio_filter_t compression) // I - Compression to apply { // TODO: Implement me (void)obj; (void)compression; return (NULL); } // // 'pdfioStreamConsume()' - Consume bytes from the stream. // bool // O - `true` on success, `false` on EOF pdfioStreamConsume(pdfio_stream_t *st, // I - Stream size_t bytes)// I - Number of bytes to consume { size_t remaining; // Remaining bytes in buffer ssize_t rbytes; // Bytes read // Range check input... if (!st || st->pdf->mode != _PDFIO_MODE_READ || !bytes) return (false); // Skip bytes in the stream buffer until we've consumed the requested number // or get to the end of the stream... while ((remaining = (size_t)(st->bufend - st->bufptr)) < bytes) { bytes -= remaining; if ((rbytes = stream_read(st, st->buffer, sizeof(st->buffer))) > 0) { st->bufptr = st->buffer; st->bufend = st->buffer + rbytes; } else { st->bufptr = st->bufend = st->buffer; return (false); } } st->bufptr += bytes; return (true); } // // '_pdfioStreamDelete()' - Free all memory used by a stream. // void _pdfioStreamDelete(pdfio_stream_t *st) // I - Stream { if (st->filter == PDFIO_FILTER_FLATE) { // Free memory used for flate compression/decompression... if (st->pdf->mode == _PDFIO_MODE_READ) inflateEnd(&st->flate); else // mode == _PDFIO_MODE_WRITE deflateEnd(&st->flate); } free(st); } // // 'pdfioStreamGetToken()' - Read a single PDF token from a stream. // bool // O - `true` on success, `false` on EOF pdfioStreamGetToken( pdfio_stream_t *st, // I - Stream char *buffer, // I - String buffer size_t bufsize) // I - Size of string buffer { return (_pdfioTokenRead(st->pdf, buffer, bufsize, (_pdfio_tpeek_cb_t)pdfioStreamPeek, (_pdfio_tconsume_cb_t)pdfioStreamConsume, st)); } // // '_pdfioStreamOpen()' - Create a stream for reading. // // Note: pdfioObjOpenStream handles loading the object's dictionary and // getting the start of the stream data. // pdfio_stream_t * // O - Stream or `NULL` on error _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object bool decode) // I - Decode/decompress the stream? { pdfio_stream_t *st; // Stream pdfio_dict_t *dict = pdfioObjGetDict(obj); // Object dictionary size_t length; // Length of stream // Allocate a new stream object... if ((st = (pdfio_stream_t *)calloc(1, sizeof(pdfio_stream_t))) == NULL) { _pdfioFileError(obj->pdf, "Unable to allocate memory for a stream."); return (NULL); } st->pdf = obj->pdf; st->obj = obj; _pdfioFileSeek(st->pdf, obj->stream_offset, SEEK_SET); if ((length = (size_t)pdfioDictGetNumber(dict, "Length")) == 0) { // Length must be an indirect reference... pdfio_obj_t *lenobj; // Length object if ((lenobj = pdfioDictGetObject(dict, "Length")) == NULL) { _pdfioFileError(obj->pdf, "Unable to get length of stream."); free(st); return (NULL); } if (lenobj->value.type == PDFIO_VALTYPE_NONE) _pdfioObjLoad(lenobj); if (lenobj->value.type != PDFIO_VALTYPE_NUMBER || lenobj->value.value.number <= 0.0f) { _pdfioFileError(obj->pdf, "Unable to get length of stream."); free(st); return (NULL); } length = (size_t)lenobj->value.value.number; } st->remaining = length; if (decode) { // Try to decode/decompress the contents of this object... const char *filter = pdfioDictGetName(dict, "Filter"); // Filter value if (!filter) { // No single filter name, do we have a compound filter? if (pdfioDictGetArray(dict, "Filter")) { // TODO: Implement compound filters... _pdfioFileError(st->pdf, "Unsupported compound stream filter."); free(st); return (NULL); } // No filter, read as-is... st->filter = PDFIO_FILTER_NONE; } else if (!strcmp(filter, "FlateDecode")) { // Flate compression #if 0 // TODO: Determine whether we need to implement support for predictors int bpc = (int)pdfioDictGetNumber(dict, "BitsPerComponent"); // Bits per component int colors = (int)pdfioDictGetNumber(dict, "Colors"); // Number of colors int columns = (int)pdfioDictGetNumber(dict, "Columns"); // Number of columns int predictor = (int)pdfioDictGetNumber(dict, "Predictor"); // Predictory value, if any #endif // 0 st->filter = PDFIO_FILTER_FLATE; st->flate.zalloc = (alloc_func)0; st->flate.zfree = (free_func)0; st->flate.opaque = (voidpf)0; st->flate.next_in = (Bytef *)st->cbuffer; st->flate.next_out = NULL; st->flate.avail_in = (uInt)_pdfioFileRead(st->pdf, st->cbuffer, sizeof(st->cbuffer)); st->flate.avail_out = 0; if (inflateInit(&(st->flate)) != Z_OK) { _pdfioFileError(st->pdf, "Unable to start Flate filter."); free(st); return (NULL); } st->remaining -= st->flate.avail_in; } else if (!strcmp(filter, "LZWDecode")) { // LZW compression st->filter = PDFIO_FILTER_LZW; } else { // Something else we don't support _pdfioFileError(st->pdf, "Unsupported stream filter '/%s'.", filter); free(st); return (NULL); } } else { // Just return the stream data as-is... st->filter = PDFIO_FILTER_NONE; } return (st); } // // 'pdfioStreamPeek()' - Peek at data in a stream. // ssize_t // O - Bytes returned or `-1` on error pdfioStreamPeek(pdfio_stream_t *st, // I - Stream void *buffer, // I - Buffer size_t bytes) // I - Size of buffer { size_t remaining; // Remaining bytes in buffer // Range check input... if (!st || st->pdf->mode != _PDFIO_MODE_READ || !buffer || !bytes) return (-1); // See if we have enough bytes in the buffer... if ((remaining = (size_t)(st->bufend - st->bufptr)) < bytes) { // No, shift the buffer and read more ssize_t rbytes; // Bytes read if (remaining > 0) memmove(st->buffer, st->bufptr, remaining); st->bufptr = st->buffer; st->bufend = st->buffer + remaining; if ((rbytes = stream_read(st, st->bufptr, sizeof(st->buffer) - remaining)) > 0) { st->bufend += rbytes; remaining += (size_t)rbytes; } } // Copy bytes from the buffer... if (bytes > remaining) bytes = remaining; memcpy(buffer, st->bufptr, bytes); // Return the number of bytes that were copied... return ((ssize_t)bytes); } // // 'pdfioStreamPrintf()' - Write a formatted string to a stream. // bool // O - `true` on success, `false` on failure pdfioStreamPrintf( pdfio_stream_t *st, // I - Stream const char *format, // I - `printf`-style format string ...) // I - Additional arguments as needed { char buffer[8192]; // String buffer va_list ap; // Argument pointer // Format the string... va_start(ap, format); vsnprintf(buffer, sizeof(buffer), format, ap); va_end(ap); // Write the string... return (pdfioStreamWrite(st, buffer, strlen(buffer))); } // // 'pdfioStreamPuts()' - Write a literal string to a stream. // bool // O - `true` on success, `false` on failure pdfioStreamPuts(pdfio_stream_t *st, // I - Stream const char *s) // I - Literal string { if (!st || !s) return (false); else return (pdfioStreamWrite(st, s, strlen(s))); } // // 'pdfioStreamRead()' - Read data from a stream. // ssize_t // O - Number of bytes read or `-1` on error pdfioStreamRead( pdfio_stream_t *st, // I - Stream void *buffer, // I - Buffer size_t bytes) // I - Bytes to read { char *bufptr = (char *)buffer; // Pointer into buffer size_t remaining; // Remaining bytes in buffer ssize_t rbytes; // Bytes read // Range check input... if (!st || st->pdf->mode != _PDFIO_MODE_READ || !buffer || !bytes) return (-1); // Loop until we have the requested bytes or hit the end of the stream... while ((remaining = (size_t)(st->bufend - st->bufptr)) < bytes) { memcpy(bufptr, st->bufptr, remaining); bufptr += remaining; bytes -= remaining; if (bytes >= sizeof(st->buffer)) { // Read large amounts directly to caller's buffer... if ((rbytes = stream_read(st, bufptr, bytes)) > 0) { bufptr += rbytes; bytes = 0; } st->bufptr = st->bufend = st->buffer; break; } else if ((rbytes = stream_read(st, st->buffer, sizeof(st->buffer))) > 0) { st->bufptr = st->buffer; st->bufend = st->buffer + rbytes; } else { st->bufptr = st->bufend = st->buffer; break; } } // Copy any remaining bytes from the stream buffer... if (bytes > 0) { memcpy(bufptr, st->bufptr, bytes); bufptr += bytes; st->bufptr += bytes; } // Return the number of bytes that were read... return (bufptr - (char *)buffer); } // // 'pdfioStreamWrite()' - Write data to a stream. // bool // O - `true` on success or `false` on failure pdfioStreamWrite( pdfio_stream_t *st, // I - Stream const void *buffer, // I - Data to write size_t bytes) // I - Number of bytes to write { // TODO: Implement me (void)st; (void)buffer; (void)bytes; return (false); } // // 'stream_read()' - Read data from a stream, including filters. // static ssize_t // O - Number of bytes read or `-1` on error stream_read(pdfio_stream_t *st, // I - Stream char *buffer, // I - Buffer size_t bytes) // I - Number of bytes to read { ssize_t rbytes; // Bytes read if (st->filter == PDFIO_FILTER_NONE) { // No filtering, but limit reads to the length of the stream... if (bytes > st->remaining) rbytes = _pdfioFileRead(st->pdf, buffer, st->remaining); else rbytes = _pdfioFileRead(st->pdf, buffer, bytes); if (rbytes > 0) st->remaining -= (size_t)rbytes; return (rbytes); } else if (st->filter == PDFIO_FILTER_FLATE) { // Deflate compression... int status; // Status of decompression if (st->flate.avail_in == 0) { // Read more from the file... if (sizeof(st->cbuffer) > st->remaining) rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining); else rbytes = _pdfioFileRead(st->pdf, st->cbuffer, sizeof(st->cbuffer)); if (rbytes <= 0) return (-1); // End of file... st->remaining -= (size_t)rbytes; st->flate.next_in = (Bytef *)st->cbuffer; st->flate.avail_in = (uInt)rbytes; } // Decompress into the buffer... st->flate.next_out = (Bytef *)buffer; st->flate.avail_out = (uInt)bytes; if ((status = inflate(&(st->flate), Z_NO_FLUSH)) < Z_OK) { _pdfioFileError(st->pdf, "Unable to decompress stream data: %d", status); return (-1); } return (st->flate.next_out - (Bytef *)buffer); } // If we get here something bad happened... return (-1); }