class PdfTextExtractor {

    /**
     * The PdfReader that holds the PDF file.
     */
    private final PdfReader reader;

    /**
     * The {@link TextAssembler} that will receive render notifications and provide resultant text
     */
    private final TextAssembler renderListener;

    /**
     * Creates a new Text Extractor object, using a {@link TextAssembler} as the render listener
     *
     * @param reader the reader with the PDF
     */
    public PdfTextExtractor(PdfReader reader) {
        this(reader, new MarkedUpTextAssembler(reader));
    }

    /**
     * Creates a new Text Extractor object, using a {@link TextAssembler} as the render listener
     *
     * @param reader               the reader with the PDF
     * @param usePdfMarkupElements should we use higher level tags for PDF markup entities?
     */
    public PdfTextExtractor(PdfReader reader, boolean usePdfMarkupElements) {
        this(reader, new MarkedUpTextAssembler(reader, usePdfMarkupElements));
    }

    /**
     * Creates a new Text Extractor object.
     *
     * @param reader         the reader with the PDF
     * @param renderListener the render listener that will be used to analyze renderText operations and provide
     *                       resultant text
     */
    public PdfTextExtractor(PdfReader reader, TextAssembler renderListener) {
        this.reader = reader;
        this.renderListener = renderListener;
    }

    /**
     * Gets the content bytes of a page.
     *
     * @param pageNum the 1-based page number of page you want get the content stream from
     * @return a byte array with the effective content stream of a page
     * @throws IOException
     */
    private byte[] getContentBytesForPage(int pageNum) throws IOException {
        try (RandomAccessFileOrArray ignored = reader.getSafeFile()) {
            PdfDictionary pageDictionary = reader.getPageN(pageNum);
            PdfObject contentObject = pageDictionary.get(PdfName.CONTENTS);
            return getContentBytesFromContentObject(contentObject);
        }
    }

    /**
     * Gets the content bytes from a content object, which may be a reference a stream or an array.
     *
     * @param contentObject the object to read bytes from
     * @return the content bytes
     * @throws IOException
     */
    private byte[] getContentBytesFromContentObject(PdfObject contentObject) throws IOException {<FILL_FUNCTION_BODY>}

    /**
     * Gets the text from a page.
     *
     * @param page the 1-based page number of page
     * @return a String with the content as plain text (without PDF syntax)
     * @throws IOException on error
     */
    public String getTextFromPage(int page) throws IOException {
        return getTextFromPage(page, false);
    }

    /**
     * get the text from the page
     *
     * @param page               page number we are interested in
     * @param useContainerMarkup should we put tags in for PDf markup container elements (not really HTML at the
     *                           moment).
     * @return result of extracting the text, with tags as requested.
     * @throws IOException on error
     */
    public String getTextFromPage(int page, boolean useContainerMarkup) throws IOException {
        PdfDictionary pageDict = reader.getPageN(page);
        if (pageDict == null) {
            return "";
        }
        PdfDictionary resources = pageDict.getAsDict(PdfName.RESOURCES);

        renderListener.reset();
        renderListener.setPage(page);
        PdfContentStreamHandler handler = new PdfContentStreamHandler(renderListener);
        processContent(getContentBytesForPage(page), resources, handler);
        return handler.getResultantText();
    }

    /**
     * Processes PDF syntax
     *
     * @param contentBytes the bytes of a content stream
     * @param resources    the resources that come with the content stream
     * @param handler      interprets events caused by recognition of operations in a content stream.
     */
    public void processContent(byte[] contentBytes, PdfDictionary resources,
            PdfContentStreamHandler handler) {
        handler.pushContext("div class='t-extracted-page'");
        try {
            PdfContentParser ps = new PdfContentParser(new PRTokeniser(contentBytes));
            List<PdfObject> operands = new ArrayList<>();
            while (ps.parse(operands).size() > 0) {
                PdfLiteral operator = (PdfLiteral) operands.get(operands.size() - 1);
                handler.invokeOperator(operator, operands, resources);
            }
        } catch (Exception e) {
            throw new ExceptionConverter(e);
        }
        handler.popContext();
    }
}

        final byte[] result;
        switch (contentObject.type()) {
            case PdfObject.INDIRECT:
                PRIndirectReference ref = (PRIndirectReference) contentObject;
                PdfObject directObject = PdfReader.getPdfObject(ref);
                result = getContentBytesFromContentObject(directObject);
                break;
            case PdfObject.STREAM:
                PRStream stream = (PRStream) PdfReader.getPdfObject(contentObject);
                result = PdfReader.getStreamBytes(stream);
                break;
            case PdfObject.ARRAY:
                // Stitch together all content before calling processContent(),
                // because
                // processContent() resets state.
                ByteArrayOutputStream allBytes = new ByteArrayOutputStream();
                PdfArray contentArray = (PdfArray) contentObject;
                for (PdfObject pdfObject : contentArray.getElements()) {
                    allBytes.write(getContentBytesFromContentObject(pdfObject));
                }
                result = allBytes.toByteArray();
                break;
            default:
                throw new IllegalStateException("Unable to handle Content of type " + contentObject.getClass());
        }
        return result;
    
<no_super_class>