Beispiel #1
0
def compare_output(writer: BasePdfFileWriter, expected_output_path):
    with tempfile.TemporaryDirectory() as working_dir:
        output_path = os.path.join(working_dir, 'output.pdf')
        with open(output_path, 'wb') as outf:
            writer.write(outf)
        expected_png = _render_pdf(
            expected_output_path, os.path.join(working_dir, 'expected')
        )
        actual_png = _render_pdf(
            output_path, os.path.join(working_dir, 'actual')
        )
        result = subprocess.run(
            # use the Absolute Error metric, since it's a single number
            # and hence very easy to process
            [
                compare_path, '-metric', 'ae',
                expected_png, actual_png, os.path.join(working_dir, 'diff.png')
            ],
            capture_output=True
        )
        # TODO maintain a directory of failed test outputs?
        if result.stderr != b'0':
            raise RuntimeError(
                f"Output compare test failed --- absolute error: "
                f"{result.stderr.decode('utf8')}"
            )
Beispiel #2
0
    def fill(self,
             writer: BasePdfFileWriter,
             md_algorithm,
             in_place=False,
             output=None,
             chunk_size=misc.DEFAULT_CHUNK_SIZE):
        """
        Generator coroutine that handles the document hash computation and
        the actual filling of the placeholder data.

        .. danger::
            This is internal API; you should use use :class:`.PdfSigner`
            wherever possible. If you *really* need fine-grained control,
            use :class:`~pyhanko.sign.signers.cms_embedder.PdfCMSEmbedder`
            instead.
        """

        if in_place:
            if not isinstance(writer, IncrementalPdfFileWriter):
                raise TypeError(
                    "in_place is only meaningful for incremental writers."
                )  # pragma: nocover
            output = writer.prev.stream
            writer.write_in_place()
        else:
            output = misc.prepare_rw_output_stream(output)

            writer.write(output)

        # retcon time: write the proper values of the /ByteRange entry
        #  in the signature object
        eof = output.tell()
        sig_start, sig_end = self.contents.offsets
        self.byte_range.fill_offsets(output, sig_start, sig_end, eof)

        # compute the digests
        md_spec = get_pyca_cryptography_hash(md_algorithm)
        md = hashes.Hash(md_spec)

        # attempt to get a memoryview for automatic buffering
        output_buffer = None
        if isinstance(output, BytesIO):
            output_buffer = output.getbuffer()
        else:
            try:
                output_buffer = memoryview(output)
            except (TypeError, IOError):
                pass

        if output_buffer is not None:
            # these are memoryviews, so slices should not copy stuff around
            #   (also, the interface files for pyca/cryptography don't specify
            #    that memoryviews are allowed, but they are)
            # noinspection PyTypeChecker
            md.update(output_buffer[:sig_start])
            # noinspection PyTypeChecker
            md.update(output_buffer[sig_end:eof])
            output_buffer.release()
        else:
            temp_buffer = bytearray(chunk_size)
            output.seek(0)
            misc.chunked_digest(temp_buffer, output, md, max_read=sig_start)
            output.seek(sig_end)
            misc.chunked_digest(temp_buffer,
                                output,
                                md,
                                max_read=eof - sig_end)

        digest_value = md.finalize()
        prepared_br_digest = PreparedByteRangeDigest(
            document_digest=digest_value,
            md_algorithm=md_algorithm,
            reserved_region_start=sig_start,
            reserved_region_end=sig_end)
        cms_data = yield prepared_br_digest, output
        yield prepared_br_digest.fill_with_cms(output, cms_data)