#! /usr/bin/perl # Adapted and translated to Perl from the test/mime-unique-id.c file in the # Cairo (version 1.17.3) source repository. # Check that source surfaces with same CAIRO_MIME_TYPE_UNIQUE_ID are # embedded only once in PDF/PS. # # To exercise all the surface embedding code in PDF, four types of # source surfaces are painted on each page, each with its own UNIQUE_ID: # - an image surface # - a recording surface with a jpeg mime attached # - a bounded recording surface # - an unbounded recording surface. # # Four pages are generated. Each source is clipped starting with the # smallest area on the first page increasing to the unclipped size on # the last page. This is to ensure the output does not embed the # source clipped to a smaller size than used on subsequent pages. use strict; use warnings; use Cairo; use Fcntl; use feature 'say'; use constant { NUM_PAGES => 4, WIDTH => 275, HEIGHT => 275, RECORDING_SIZE => 800, TILE_SIZE => 40, PNG_FILENAME => 'romedalen.png', JPG_FILENAME => 'romedalen.jpg', OUTPUT_FILENAME => 'mime-unique-id.perl.pdf', M_PI => 3.1415926, }; sub create_image_surface { my $surface = Cairo::ImageSurface->create_from_png(PNG_FILENAME); my $status = $surface->status(); if ($status ne 'success') { say $surface->status(); die; } $surface->set_mime_data($surface->MIME_TYPE_UNIQUE_ID, PNG_FILENAME); $surface->set_mime_data($surface->MIME_TYPE_UNIQUE_ID, 'image'); return $surface; } sub create_recording_surface_with_mime_jpg { my $surface = Cairo::RecordingSurface->create('alpha', {x => 0, y => 0, width => 1, height => 1}); if ($surface->status() ne 'success') { say $surface->status(); die; } my ($FH, $want, $data); unless (sysopen($FH, JPG_FILENAME, O_RDONLY|O_BINARY)) { die; } $want = -s $FH; $data = ''; while (1) { my $rc = sysread($FH, $data, $want, length($data)); die unless defined $rc; last if $rc == 0; $want -= $rc; last if $want <= 0; } close($FH); $surface->set_mime_data($surface->MIME_TYPE_JPEG, $data); if ($surface->status() ne 'success') { say $surface->status(); die; } $surface->set_mime_data($surface->MIME_TYPE_UNIQUE_ID, 'jpeg'); if ($surface->status() ne 'success') { say $surface->status(); die; } return $surface; } sub draw_tile { my ($cr) = @_; $cr->move_to(10+5, 10); $cr->arc(10, 10, 5, 0, 2*M_PI); $cr->close_path(); $cr->set_source_rgb(1, 0, 0); $cr->fill(); $cr->move_to(30, 10-10*0.43); $cr->line_to(25, 10+10*0.43); $cr->line_to(35, 10+10*0.43); $cr->close_path(); $cr->set_source_rgb(0, 1, 0); $cr->fill(); $cr->rectangle(5, 25, 10, 10); $cr->set_source_rgb(0, 0, 0); $cr->fill(); $cr->save(); $cr->translate(30, 30); $cr->rotate(M_PI/4.0); $cr->rectangle(-5, -5, 10, 10); $cr->set_source_rgb(1, 0, 1); $cr->fill(); $cr->restore(); } sub create_recording_surface { my ($bounded) = @_; my ($surface, $start, $size); if ($bounded) { $surface = Cairo::RecordingSurface->create('alpha', {x => 0, y => 0, width => RECORDING_SIZE, height => RECORDING_SIZE}); ($start, $size) = (0, RECORDING_SIZE); } else { $surface = Cairo::RecordingSurface->create('alpha', undef); ($start, $size) = (RECORDING_SIZE/2, RECORDING_SIZE*2); } # Draw each tile instead of creating a cairo pattern to make size # of the emitted recording as large as possible. my ($cr) = Cairo::Context->create($surface); $cr->set_source_rgb(1, 1, 0); $cr->paint(); my $ctm = $cr->get_matrix(); for (my $y = $start; $y < $size; $y += TILE_SIZE) { for (my $x = $start; $x < $size; $x += TILE_SIZE) { draw_tile($cr); $cr->translate(TILE_SIZE, 0); } $ctm->translate(0, TILE_SIZE); $cr->set_matrix($ctm); } $cr = undef; $surface->set_mime_data($surface->MIME_TYPE_UNIQUE_ID, $bounded ? 'recording bounded' : 'recording unbounded'); if ($surface->status() ne 'success') { say $surface->status(); die; } return $surface; } # Draw @source scaled to fit @rect and clipped to a rectangle # @clip_margin units smaller on each side. @rect will be stroked # with a solid line and the clip rect stroked with a dashed line. sub draw_surface { my ($cr, $source, $rect, $clip_margin) = @_; my ($width, $height); my $type = $source->get_type(); if ($type eq 'image') { $width = $source->get_width(); $height = $source->get_height(); } elsif (defined(my $extents = $source->get_extents())) { $width = $$extents{width}; $height = $$extents{height}; } else { $width = RECORDING_SIZE; $height = RECORDING_SIZE; } $cr->save(); $cr->rectangle($$rect{x}, $$rect{y}, $$rect{width}, $$rect{height}); $cr->stroke(); $cr->rectangle($$rect{x}+$clip_margin, $$rect{y}+$clip_margin, $$rect{width}-$clip_margin*2, $$rect{height}-$clip_margin*2); $cr->set_dash(0, 2, 2); $cr->stroke_preserve(); $cr->clip(); $cr->translate($$rect{x}, $$rect{y}); $cr->scale($$rect{width}/$width, $$rect{height}/$height); $cr->set_source_surface($source, 0, 0); $cr->paint(); $cr->restore(); } sub draw_pages { my ($surface) = @_; my $cr = Cairo::Context->create($surface); # Draw the image and recording surface on each page. The sources # are clipped starting with a small clip area on the first page # and increasing to the source size on last page to ensure the # embedded source is not clipped to the area used on the first # page. # # The sources are created each time they are used to ensure # CAIRO_MIME_TYPE_UNIQUE_ID is tested. for (my $i=0; $i 25, y => 25, width => 100, height => 100,}, $clip_margin); $source = undef; $source = create_recording_surface_with_mime_jpg(); draw_surface($cr, $source, {x => 150, y => 25, width => 100, height => 100,}, $clip_margin); $source = undef; $source = create_recording_surface(1); draw_surface($cr, $source, {x => 25, y => 150, width => 100, height => 100,}, $clip_margin); $source = undef; $source = create_recording_surface(0); draw_surface($cr, $source, {x => 150, y => 150, width => 100, height => 100,}, $clip_margin); $source = undef; # REQUIRED! $cr->show_page(); } $cr = undef; } my $surface = Cairo::PdfSurface->create(OUTPUT_FILENAME, WIDTH, HEIGHT); if ($surface->status() ne 'success') { say $surface->status(); die; } draw_pages($surface); $surface->finish(); 0;