Format the binary file as a hexdump and return it as a string.

        >>> print(binfile.as_hexdump())
        00000100  21 46 01 36 01 21 47 01  36 00 7e fe 09 d2 19 01  |!F.6.!G.6.~.....|
        00000110  21 46 01 7e 17 c2 00 01  ff 5f 16 00 21 48 01 19  |!F.~....._..!H..|
        00000120  19 4e 79 23 46 23 96 57  78 23 9e da 3f 01 b2 ca  |.Ny#F#.Wx#..?...|
        00000130  3f 01 56 70 2b 5e 71 2b  72 2b 73 21 46 01 34 21  |?.Vp+^q+r+s!F.4!|

def as_hexdump(self):
        """Format the binary file as a hexdump and return it as a string.

        >>> print(binfile.as_hexdump())
        00000100  21 46 01 36 01 21 47 01  36 00 7e fe 09 d2 19 01  |!F.6.!G.6.~.....|
        00000110  21 46 01 7e 17 c2 00 01  ff 5f 16 00 21 48 01 19  |!F.~....._..!H..|
        00000120  19 4e 79 23 46 23 96 57  78 23 9e da 3f 01 b2 ca  |.Ny#F#.Wx#..?...|
        00000130  3f 01 56 70 2b 5e 71 2b  72 2b 73 21 46 01 34 21  |?.Vp+^q+r+s!F.4!|

        """

        # Empty file?
        if len(self) == 0:
            return '\n'

        non_dot_characters = set(string.printable)
        non_dot_characters -= set(string.whitespace)
        non_dot_characters |= set(' ')

        def align16(address):
            return address - (address % 16)

        def padding(length):
            return [None] * length

        def format_line(address, data):
            """`data` is a list of integers and None for unused elements.

            """

            data += padding(16 - len(data))
            hexdata = []

            for byte in data:
                if byte is not None:
                    elem = '{:02x}'.format(byte)
                else:
                    elem = '  '

                hexdata.append(elem)

            first_half = ' '.join(hexdata[0:8])
            second_half = ' '.join(hexdata[8:16])
            text = ''

            for byte in data:
                if byte is None:
                    text += ' '
                elif chr(byte) in non_dot_characters:
                    text += chr(byte)
                else:
                    text += '.'

            return '{:08x}  {:23s}  {:23s}  |{:16s}|'.format(
                address, first_half, second_half, text)

        # Format one line at a time.
        lines = []
        line_address = align16(self.minimum_address)
        line_data = []

        for chunk in self._segments.chunks(size=16, alignment=16):
            aligned_chunk_address = align16(chunk.address)

            if aligned_chunk_address > line_address:
                lines.append(format_line(line_address, line_data))

                if aligned_chunk_address > line_address + 16:
                    lines.append('...')

                line_address = aligned_chunk_address
                line_data = []

            line_data += padding(chunk.address - line_address - len(line_data))
            line_data += [byte for byte in chunk.data]

        lines.append(format_line(line_address, line_data))

        return '\n'.join(lines) + '\n'