QPDF::replaceForeignIndirectObjects(
    QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top)
{
    QPDFObjectHandle result;
    if ((! top) && foreign.isIndirect())
    {
        QTC::TC("qpdf", "QPDF replace indirect");
        QPDFObjGen foreign_og(foreign.getObjGen());
        std::map<QPDFObjGen, QPDFObjectHandle>::iterator mapping =
            obj_copier.object_map.find(foreign_og);
        if (mapping == obj_copier.object_map.end())
        {
            // This case would occur if this is a reference to a Page
            // or Pages object that we didn't traverse into.
            QTC::TC("qpdf", "QPDF replace foreign indirect with null");
            result = QPDFObjectHandle::newNull();
        }
        else
        {
            result = obj_copier.object_map[foreign_og];
        }
    }
    else if (foreign.isArray())
    {
        QTC::TC("qpdf", "QPDF replace array");
        result = QPDFObjectHandle::newArray();
	int n = foreign.getArrayNItems();
	for (int i = 0; i < n; ++i)
	{
            result.appendItem(
                replaceForeignIndirectObjects(
                    foreign.getArrayItem(i), obj_copier, false));
	}
    }
    else if (foreign.isDictionary())
    {
        QTC::TC("qpdf", "QPDF replace dictionary");
        result = QPDFObjectHandle::newDictionary();
	std::set<std::string> keys = foreign.getKeys();
	for (std::set<std::string>::iterator iter = keys.begin();
	     iter != keys.end(); ++iter)
	{
            result.replaceKey(
                *iter,
                replaceForeignIndirectObjects(
                    foreign.getKey(*iter), obj_copier, false));
	}
    }
    else if (foreign.isStream())
    {
        QTC::TC("qpdf", "QPDF replace stream");
        QPDFObjGen foreign_og(foreign.getObjGen());
        result = obj_copier.object_map[foreign_og];
        result.assertStream();
        QPDFObjectHandle dict = result.getDict();
        QPDFObjectHandle old_dict = foreign.getDict();
        std::set<std::string> keys = old_dict.getKeys();
        for (std::set<std::string>::iterator iter = keys.begin();
	     iter != keys.end(); ++iter)
	{
            dict.replaceKey(
                *iter,
                replaceForeignIndirectObjects(
                    old_dict.getKey(*iter), obj_copier, false));
	}
        if (this->m->copied_stream_data_provider == 0)
        {
            this->m->copied_stream_data_provider =
                new CopiedStreamDataProvider(*this);
            this->m->copied_streams = this->m->copied_stream_data_provider;
        }
        QPDFObjGen local_og(result.getObjGen());
        // Copy information from the foreign stream so we can pipe its
        // data later without keeping the original QPDF object around.
        QPDF* foreign_stream_qpdf = foreign.getOwningQPDF();
        if (! foreign_stream_qpdf)
        {
            throw std::logic_error("unable to retrieve owning qpdf"
                                   " from foreign stream");
        }
        QPDF_Stream* stream =
            dynamic_cast<QPDF_Stream*>(
                QPDFObjectHandle::ObjAccessor::getObject(
                    foreign).getPointer());
        if (! stream)
        {
            throw std::logic_error("unable to retrieve underlying"
                                   " stream object from foreign stream");
        }
        PointerHolder<Buffer> stream_buffer =
            stream->getStreamDataBuffer();
        if ((foreign_stream_qpdf->m->immediate_copy_from) &&
            (stream_buffer.getPointer() == 0))
        {
            // Pull the stream data into a buffer before attempting
            // the copy operation. Do it on the source stream so that
            // if the source stream is copied multiple times, we don't
            // have to keep duplicating the memory.
            QTC::TC("qpdf", "QPDF immediate copy stream data");
            foreign.replaceStreamData(foreign.getRawStreamData(),
                                      dict.getKey("/Filter"),
                                      dict.getKey("/DecodeParms"));
            stream_buffer = stream->getStreamDataBuffer();
        }
        PointerHolder<QPDFObjectHandle::StreamDataProvider> stream_provider =
            stream->getStreamDataProvider();
        if (stream_buffer.getPointer())
        {
            QTC::TC("qpdf", "QPDF copy foreign stream with buffer");
            result.replaceStreamData(stream_buffer,
                                     dict.getKey("/Filter"),
                                     dict.getKey("/DecodeParms"));
        }
        else if (stream_provider.getPointer())
        {
            // In this case, the remote stream's QPDF must stay in scope.
            QTC::TC("qpdf", "QPDF copy foreign stream with provider");
            this->m->copied_stream_data_provider->registerForeignStream(
                local_og, foreign);
            result.replaceStreamData(this->m->copied_streams,
                                     dict.getKey("/Filter"),
                                     dict.getKey("/DecodeParms"));
        }
        else
        {
            PointerHolder<ForeignStreamData> foreign_stream_data =
                new ForeignStreamData(
                    foreign_stream_qpdf->m->encp,
                    foreign_stream_qpdf->m->file,
                    foreign.getObjectID(),
                    foreign.getGeneration(),
                    stream->getOffset(),
                    stream->getLength(),
                    (foreign_stream_qpdf->m->attachment_streams.count(
                        foreign.getObjGen()) > 0),
                    dict);
            this->m->copied_stream_data_provider->registerForeignStream(
                local_og, foreign_stream_data);
            result.replaceStreamData(this->m->copied_streams,
                                     dict.getKey("/Filter"),
                                     dict.getKey("/DecodeParms"));
        }
    }
    else
    {
        foreign.assertScalar();
        result = foreign;
        result.makeDirect();
    }

    if (top && (! result.isStream()) && result.isIndirect())
    {
        throw std::logic_error("replacement for foreign object is indirect");
    }

    return result;
}