BrotliFilter.java
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2025 Apryse Group NV
Authors: Apryse Software.
This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itextpdf.kernel.pdf.filters;
import com.itextpdf.io.codec.brotli.dec.BrotliInputStream;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.pdf.MemoryLimitsAwareFilter;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfObject;
import com.itextpdf.kernel.pdf.PdfStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Filter implementation for decoding Brotli-compressed PDF streams.
* This filter supports optional Brotli dictionary streams and memory limits awareness.
*/
public class BrotliFilter extends MemoryLimitsAwareFilter {
private static final int DEFAULT_INTERNAL_BUFFER_SIZE = 16384;
/**
* Default buffer size for Brotli decompression (64 KiB).
*/
private static final int DEFAULT_BUFFER_SIZE = 65536;
/**
* Constructs an empty BrotliFilter instance.
*/
public BrotliFilter() {
//empty constructor
}
/**
* Decodes Brotli-compressed data from a PDF stream.
*
* @param b the bytes that need to be decoded
* @param filterName PdfName of the filter (unused)
* @param decodeParams decode parameters, may contain a Brotli dictionary stream under key 'D'
* @param streamDictionary the dictionary of the stream. Can contain additional information needed to decode the
* byte[]
*
* @return the decoded byte[]
*
* @throws PdfException if decompression fails
*/
@Override
public byte[] decode(byte[] b, PdfName filterName, PdfObject decodeParams, PdfDictionary streamDictionary) {
try {
final PdfStream brotliDictionary = getBrotliDictionaryStream(decodeParams);
final byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
final ByteArrayInputStream input = new ByteArrayInputStream(b);
final ByteArrayOutputStream output = enableMemoryLimitsAwareHandler(streamDictionary);
final BrotliInputStream brotliInput;
if (brotliDictionary != null) {
brotliInput = new BrotliInputStream(input, DEFAULT_INTERNAL_BUFFER_SIZE);
brotliInput.attachDictionaryChunk(brotliDictionary.getBytes());
} else {
brotliInput = new BrotliInputStream(input);
}
int len;
while ((len = brotliInput.read(buffer, 0, buffer.length)) > 0) {
output.write(buffer, 0, len);
}
brotliInput.close();
return FlateDecodeFilter.decodePredictor(output.toByteArray(), decodeParams);
} catch (IOException e) {
throw new PdfException(KernelExceptionMessageConstant.FAILED_TO_DECODE_BROTLI_STREAM, e);
}
}
/**
* Extracts the Brotli dictionary stream from decode parameters if present.
*
* @param decodeParams decode parameters, may contain a Brotli dictionary stream under key 'D'
*
* @return an Optional containing the Brotli dictionary stream if present, otherwise empty
*
* @throws RuntimeException if the dictionary is present but not a stream
*/
private static PdfStream getBrotliDictionaryStream(PdfObject decodeParams) {
if (!(decodeParams instanceof PdfDictionary)) {
return null;
}
PdfDictionary dict = (PdfDictionary) decodeParams;
PdfObject brotliDecompressionDictionary = dict.get(PdfName.D);
if (brotliDecompressionDictionary instanceof PdfStream) {
// Brotli dictionary stream found
return (PdfStream) brotliDecompressionDictionary;
} else if (brotliDecompressionDictionary != null) {
throw new PdfException(KernelExceptionMessageConstant.BROTLI_DICTIONARY_IS_NOT_A_STREAM);
} else {
return null;
}
}
}