Beispiel #1
0
    def _add_image_annotations(self, a, appearance, y1=120, y2=160):
        # Draw a row of image annotations for each type of image, with a label
        # on top of the image type
        x = 10
        text_appearance = Appearance(
            font_size=5,
            text_baseline=constants.TEXT_BASELINE_BOTTOM,
            fill=[0, 0, 0]
        )
        a.add_annotation(
            'text',
            Location(x1=x, y1=y2, x2=(x + 20), y2=(y2 + 10), page=0),
            text_appearance.copy(content='PNG'),
        )
        for png_file in PNG_FILES[:-1]:
            a.add_annotation(
                'image',
                Location(x1=x, y1=y1, x2=(x + 40), y2=y2, page=0),
                appearance.copy(image=png_file),
            )
            x += 50
        # The last PNG file has transparency, so let's draw a rectangle behind
        # so you can see that it's transparent.
        a.add_annotation(
            'square',
            Location(x1=x, y1=y1, x2=(x + 40), y2=y2, page=0),
            self.gaudy.copy(stroke_width=0),
        )
        a.add_annotation(
            'image',
            Location(x1=x, y1=y1, x2=(x + 40), y2=y2, page=0),
            appearance.copy(image=PNG_FILES[-1]),
        )
        x += 50

        a.add_annotation(
            'text',
            Location(x1=x, y1=y2, x2=(x + 20), y2=(y2 + 10), page=0),
            text_appearance.copy(content='JPEG'),
        )
        for jpeg_file in JPEG_FILES:
            a.add_annotation(
                'image',
                Location(x1=x, y1=y1, x2=(x + 40), y2=y2, page=0),
                appearance.copy(image=jpeg_file),
            )
            x += 50

        a.add_annotation(
            'text',
            Location(x1=x, y1=y2, x2=(x + 20), y2=(y2 + 10), page=0),
            text_appearance.copy(content='GIF'),
        )
        for gif_file in GIF_FILES:
            a.add_annotation(
                'image',
                Location(x1=x, y1=y1, x2=(x + 40), y2=y2, page=0),
                appearance.copy(image=gif_file),
            )
            x += 50
class EndToEndMixin(object):
    """End to end test of PdfAnnotator.

    To truly test PDF annotations end to end, you need to do some sort of
    visual inspection. There are several options, including rastering the PDF
    and doing pixel diffs. Until we end up doing something like this, the end-
    to-end test will be a tiny amount of validation in code + manual inspection
    of the output.
    """
    EXPECTED_ANNOTATIONS = 36

    def setUp(self):
        self.gaudy = Appearance(
            stroke_color=[1, 0, 0],
            stroke_width=3,
            fill=[0, 1, 0],
            content='Latin',
            font_size=12,
            wrap_text=True,
        )

        self.transparent = self.gaudy.copy(
            fill=[0, 0, 1, 0.5],
            stroke_color=[1, 0, 0, 0.25],
        )

        self.top_left = self.gaudy.copy(
            fill=[0, 0.5, 0.75],
            content=(r"Though yet of Hamlet, our dear brother's death \\ "
                     r"The memory be green"),
            font_size=6,
            text_align='left',
            text_baseline='top',
        )
        self.top_center = self.top_left.copy(text_align='center')
        self.top_right = self.top_left.copy(text_align='right')

        self.middle_left = self.top_left.copy(text_baseline='middle')
        self.middle_center = self.middle_left.copy(text_align='center')
        self.middle_right = self.middle_left.copy(text_align='right')

        self.bottom_left = self.top_left.copy(text_baseline='bottom')
        self.bottom_center = self.bottom_left.copy(text_align='center')
        # One text annotation is transparent grey
        self.bottom_right = self.bottom_left.copy(
            text_align='right',
            fill=[0, 0, 0, 0.25],
        )

        self.texts = [
            self.top_left,
            self.top_center,
            self.top_right,
            self.middle_left,
            self.middle_center,
            self.middle_right,
            self.bottom_left,
            self.bottom_center,
            self.bottom_right,
        ]

        self.image_appearance = Appearance(stroke_width=0)
        self.transparent_image_appearance = self.image_appearance.copy(
            fill_transparency=0.5,
            stroke_transparency=0.5,
        )

    def test_end_to_end(self):
        a = PdfAnnotator(self.INPUT_FILENAME)
        self._add_annotations(a)
        output_file = self._get_output_file()
        a.write(output_file)
        # self._check_num_annotations(output_file)

    def _check_num_annotations(self, output_file):
        f = pdfrw.PdfReader(output_file)
        assert len(f.pages[0].Annots) == self.EXPECTED_ANNOTATIONS

    def _get_output_file(self):
        dirname, _ = os.path.split(os.path.abspath(__file__))
        return os.path.join(dirname, 'pdfs', self.OUTPUT_FILENAME)

    def _add_annotations(self, a):
        # Original page size (612, 792)
        self._add_shape_annotations(a, self.gaudy)
        self._add_shape_annotations(a, self.transparent, y1=70, y2=110)
        self._add_image_annotations(a, self.image_appearance)
        self._add_image_annotations(
            a,
            self.transparent_image_appearance,
            y1=170,
            y2=210,
        )
        self._add_text_annotations(a)
        self._add_explicit_image_annotation(a)
        self._add_explicit_graphics_state_annotation(a)
        self._add_explicit_text_annotation(a)
        self._add_rounded_rectangles(a)

    def _add_shape_annotations(self, a, appearance, y1=20, y2=60):
        a.add_annotation(
            'square',
            Location(x1=10, y1=y1, x2=50, y2=y2, page=0),
            appearance,
        )
        a.add_annotation(
            'circle',
            Location(x1=60, y1=y1, x2=100, y2=y2, page=0),
            appearance,
        )
        a.add_annotation(
            'polygon',
            Location(points=[[110, y1], [150, y1], [130, y2]], page=0),
            appearance,
        )
        a.add_annotation(
            'polyline',
            Location(points=[[160, y1], [200, y1], [180, y2]], page=0),
            appearance,
        )
        a.add_annotation(
            'line',
            Location(points=[[210, y1], [250, y2]], page=0),
            appearance,
        )
        a.add_annotation(
            'ink',
            Location(points=[[260, y1], [300, y2]], page=0),
            appearance,
        )

        round = appearance.copy(line_cap=constants.LINE_CAP_ROUND)
        a.add_annotation(
            'line',
            location=Location(points=[[310, y1], [350, y2]], page=0),
            appearance=round,
        )

        dashed = appearance.copy(dash_array=[[3], 0])
        a.add_annotation(
            'line',
            location=Location(points=[[360, y1], [400, y2]], page=0),
            appearance=dashed,
        )

    def _add_image_annotations(self, a, appearance, y1=120, y2=160):
        # Draw a row of image annotations for each type of image, with a label
        # on top of the image type
        x = 10
        text_appearance = Appearance(
            font_size=5,
            text_baseline=constants.TEXT_BASELINE_BOTTOM,
            fill=[0, 0, 0])
        a.add_annotation(
            'text',
            Location(x1=x, y1=y2, x2=(x + 20), y2=(y2 + 10), page=0),
            text_appearance.copy(content='PNG'),
        )
        for png_file in PNG_FILES[:-1]:
            a.add_annotation(
                'image',
                Location(x1=x, y1=y1, x2=(x + 40), y2=y2, page=0),
                appearance.copy(image=png_file),
            )
            x += 50
        # The last PNG file has transparency, so let's draw a rectangle behind
        # so you can see that it's transparent.
        a.add_annotation(
            'square',
            Location(x1=x, y1=y1, x2=(x + 40), y2=y2, page=0),
            self.gaudy.copy(stroke_width=0),
        )
        a.add_annotation(
            'image',
            Location(x1=x, y1=y1, x2=(x + 40), y2=y2, page=0),
            appearance.copy(image=PNG_FILES[-1]),
        )
        x += 50

        a.add_annotation(
            'text',
            Location(x1=x, y1=y2, x2=(x + 20), y2=(y2 + 10), page=0),
            text_appearance.copy(content='JPEG'),
        )
        for jpeg_file in JPEG_FILES:
            a.add_annotation(
                'image',
                Location(x1=x, y1=y1, x2=(x + 40), y2=y2, page=0),
                appearance.copy(image=jpeg_file),
            )
            x += 50

        a.add_annotation(
            'text',
            Location(x1=x, y1=y2, x2=(x + 20), y2=(y2 + 10), page=0),
            text_appearance.copy(content='GIF'),
        )
        for gif_file in GIF_FILES:
            a.add_annotation(
                'image',
                Location(x1=x, y1=y1, x2=(x + 40), y2=y2, page=0),
                appearance.copy(image=gif_file),
            )
            x += 50

    def _add_text_annotations(self, a, y1=220, y2=300):
        xs = [10 + (i * 50) for i in range(len(self.texts))]
        for x, appearance in zip(xs, self.texts):
            a.add_annotation(
                'text',
                Location(x1=x, y1=y1, x2=(x + 40), y2=y2, page=0),
                appearance,
            )

    def _add_rounded_rectangles(self, a):
        """Add a few rounded rectangles with different border radii."""
        y1, y2 = 360, 410
        xs = [10, 60, 110]
        rxs = [5, 10, 15]
        rys = [5, 5, 15]
        for x1, rx, ry in zip(xs, rxs, rys):
            x2 = x1 + 40
            location = Location(x1=x1, y1=y1, x2=x2, y2=y2, page=0)
            content_stream = ContentStream([
                Save(),
                StrokeColor(1, 0, 0),
                FillColor(0, 1, 0),
            ])
            add_rounded_rectangle(
                stream=content_stream,
                x=x1,
                y=y1,
                width=(x2 - x1),
                height=(y2 - y1),
                rx=rx,
                ry=ry,
            )
            content_stream.extend([
                StrokeAndFill(),
                Restore(),
            ])
            appearance = Appearance(appearance_stream=content_stream, )
            a.add_annotation('square', location, appearance)

    def _add_explicit_image_annotation(self, a):
        """Add an image annotation using ContentStream commands instead of the
        Image type's commands. This is testing that the external XObjects API
        works, and that images can be embedded inside other, more complex
        annotations.
        """
        x1, y1, x2, y2 = 10, 310, 50, 350
        location = Location(x1=x1, y1=y1, x2=x2, y2=y2, page=0)

        content_stream = ContentStream([
            StrokeColor(1, 0, 0),
            Rect(x1, y1, x2 - x1, y2 - y1),
            Save(),
            # The image is inside an outer rectangle
            CTM(Image.get_ctm(x1 + 10, y1 + 10, x2 - 10, y2 - 10)),
            XObject('MyXObject'),
            Restore(),
            Stroke(),
        ])
        appearance = Appearance(
            appearance_stream=content_stream,
            xobjects={
                'MyXObject': Image.make_image_xobject(PNG_FILES[0]),
            },
        )

        a.add_annotation(
            'square',
            location=location,
            appearance=appearance,
        )

    def _add_explicit_graphics_state_annotation(self, a):
        graphics_states = {
            'BevelSquare':
            GraphicsState(
                line_join=constants.LINE_JOIN_BEVEL,
                line_cap=constants.LINE_CAP_SQUARE,
                stroke_transparency=0.75,
            ),
            'MiterButt':
            GraphicsState(
                line_join=constants.LINE_JOIN_MITER,
                line_cap=constants.LINE_CAP_BUTT,
                stroke_transparency=0.5,
            ),
            'RoundRound':
            GraphicsState(
                line_join=constants.LINE_JOIN_ROUND,
                line_cap=constants.LINE_CAP_ROUND,
                stroke_transparency=0.25,
            ),
        }

        # Defines the bounding box of the chevrons
        x1, y1, x2, y2 = 60, 310, 100, 350
        lines_location = Location(x1=x1, y1=y1, x2=x2, y2=y2, page=0)

        # Defines the start/end of the chevrons
        x1, midpoint, x2 = 65, 80, 95
        y1 = 315

        content_stream = ContentStream([
            Save(),
            StrokeWidth(5),
            CSGraphicsState('BevelSquare'),
            Move(x1, y1),
            Line(midpoint, y1 + 10),
            Line(x2, y1),
            Stroke(),
            CSGraphicsState('MiterButt'),
            Move(x1, y1 + 10),
            Line(midpoint, y1 + 20),
            Line(x2, y1 + 10),
            Stroke(),
            CSGraphicsState('RoundRound'),
            Move(x1, y1 + 20),
            Line(midpoint, y1 + 30),
            Line(x2, y1 + 20),
            Stroke(),
            Restore(),
        ])
        appearance = Appearance(
            appearance_stream=content_stream,
            graphics_states=graphics_states,
        )
        a.add_annotation(
            'square',
            location=lines_location,
            appearance=appearance,
        )

    def _add_explicit_text_annotation(self, a):
        x1, y1, x2, y2 = 110, 310, 200, 350
        font_size = 4

        content_stream = ContentStream([
            Save(),
            BeginText(),
            FillColor(0, 0, 0),
            Font('MyFontyFont', font_size),
        ])
        content_stream.extend(
            get_text_commands(
                x1,
                y1,
                x2,
                y2,
                text=(r'Twas brilling and the slithy toves \n'
                      r'Did gyre and gimbel in the wabe \n'
                      r'All mimsy were the borogroves \n'
                      r'And the mome raths outgrabe \n'),
                font_size=font_size,
                wrap_text=True,
                align=constants.TEXT_ALIGN_LEFT,
                baseline=constants.TEXT_BASELINE_TOP,
                line_spacing=1.2,
            ))
        content_stream.extend([
            EndText(),
            Restore(),
        ])

        appearance = Appearance(
            appearance_stream=content_stream,
            fonts={'MyFontyFont': FreeText.make_font_object()},
        )
        a.add_annotation(
            'square',
            location=Location(x1=x1, y1=y1, x2=x2, y2=y2, page=0),
            appearance=appearance,
        )