pdf-raku / PDF-API6

Facilitates the creation and modification of PDF files
Artistic License 2.0
6 stars 3 forks source link

Click the outlines after make toc, but it doesn't jump to the special page number #5

Closed ohmycloud closed 3 years ago

ohmycloud commented 5 years ago

I use the follwing code to make outlines, it works quit fine. But when I opened the output pdf file, and click the outlines on the left, I expect it will jump to the specified page number, but nothing happend.

use PDF::API6;
my PDF::API6 $pdf .= new;
$pdf.add-page for 1 .. 7;
use PDF::Destination :Fit;

for 1 .. 7 -> $n {
    $pdf.page($n).text: {
        .text-position = 10,20;
        .say('text @10,20');
    }
}

sub dest(|c) { :destination($pdf.destination(|c)) }

$pdf.outlines.kids = [
          %( :Title('1. Purpose of this Document'), dest(:page(1))),
          %( :Title('2. Pre-requisites'),           dest(:page(2))),
          %( :Title('3. Compiler Speed-up'),        dest(:page(3))),
          %( :Title('4. Recompiling the Kernel for Modules'), dest(:page(4)),
             :kids[
                %( :Title('5.1. Configuring Debian or RedHat for Modules'),
                   dest(:page(5), :fit(FitXYZoom), :top(798)) ),
                %( :Title('5.2. Configuring Slackware for Modules'),
                   dest(:page(5), :fit(FitXYZoom), :top(400)) ),
                %( :Title('5.3. Configuring Other Distributions for Module'),
                   dest(:page(5), :fit(FitXYZoom), :top(200)) ),
             ],
           ),
          %( :Title('Appendix'), dest(:page(7))),
         ];

$pdf.save-as: "../tmp/make-toc.pdf";
dwarring commented 5 years ago

@ohmycloud Quite right. That example doesn't work with the current PDF::Class and doesn't correctly build outlines.

I've changed sub dest(|c) { :destination($pdf.destination(|c)) } to sub dest(|c) { :Dest($pdf.destination(|c)) } in the README.

Please try that.

ohmycloud commented 5 years ago

Thanks you! It worked.

Sorry for one more question, after I do some crop on the old pdf, and save the croped pdf, the outlines just doesn't move as expected when clicked. Did I make a mistake?

use v6;
use PDF::API6;
use PDF::Page;
use PDF::XObject;
use PDF::Content::Page :PageSizes;
use PDF::Destination :Fit;

for 1..$pages -> $n {

    my PDF::Page $new-page = $pdf.add-page;
    $new-page.bleed = 50mm;
    my PDF::Page $page = $old-pdf.page($n);
    $page.trim-box = Letter;
    $page.bleed = 10mm;
    my $gfx = $new-page.gfx;

    # Import Page from the old PDF
    my PDF::XObject $xo = $old-pdf.page($n).to-xobject;

    # Add it to the new PDF's first page at 1/2 scale
    my $width = $xo.width / 2;
    my $bottom = 0;
    my $left = 0;
    $gfx.do($xo, $bottom, $left, :$width);
}

$pdf.outlines = $old-pdf.outlines;   # clone outlines from old pdf
$pdf.save-as: "../tmp/pdf-crop.pdf";
dwarring commented 5 years ago

@ohmycloud, Cloning of outlines isn't enough. They're tightly bound by direct references to the pages. On top of that they specify the absolute position on the output page, which needs to be recalculated. PDF::API6 doesn't have any support for this yet.

I've coded minimal changes using PDF::Class that copies the outlines from the input to the output PDF and uses FitWindow so that it navigates to the page, but not the position on the page, just to side-step all the calculations involved.

use v6;
use PDF::API6;
use PDF::Page;
use PDF::XObject;
use PDF::Content::Page :PageSizes;
use PDF::Destination :Fit;
use PDF::Catalog;
use PDF::Action::GoTo;

sub postfix:<mm>(Numeric $v){ ($v * 2.83).round(1) };
my PDF::API6 $old-pdf .= open: "../tmp/make-toc.pdf";
my PDF::API6 $pdf .= new;
my UInt $pages = $old-pdf.page-count;
sub dest(|c) { :Dest($pdf.destination(|c)) }

# mapping of input pages to output page numbers
my UInt %page-numbers{PDF::Page};

for 1..$pages -> $n {

    my PDF::Page $new-page = $pdf.add-page;
    $new-page.bleed = 50mm;
    my PDF::Page $page = $old-pdf.page($n);
    %page-numbers{$page} = $n;
    $page.trim-box = Letter;
    $page.bleed = 10mm;
    my $gfx = $new-page.gfx;

    # Import Page from the old PDF
    my PDF::XObject $xo = $old-pdf.page($n).to-xobject;

    # Add it to the new PDF's first page at 1/2 scale
    my $width = $xo.width / 2;
    my $bottom = 20mm;
    my $left = 10mm;
    $gfx.do($xo, $bottom, $left, :$width);
}

my @outlines = copy-outlines($old-pdf.outlines);
$pdf.outlines.kids = @outlines;
$pdf.save-as: "../tmp/pdf-crop.pdf";

sub copy-outlines($outlines) {
    $outlines.kids.map: {
        my %out = :Title(.Title), ;
        with .Dest // .A {
            my UInt $page = page-number($_);
            %out ,= dest(:$page, :fit(FitWindow));
        }
        %out<kids> = copy-outlines($_);
        %out;
    }
}

# copy outlines when there is a 1-1 correspondance between input and output pages
subset DestInternalRef of Hash where PDF::Action::GoTo | PDF::Catalog::DestDict;

multi sub page-number(DestInternalRef $ref) {
    page-number($ref.D);
}

multi sub page-number(PDF::Page $page) {
    %page-numbers{$page};
}

multi sub page-number(PDF::Destination $_) {
    page-number(.page);
}

Possibly PDF::API6 should have better support for importing Outlines, Annotations etc, or there should be a dedicated module for importing Pages and other related resources.

ohmycloud commented 5 years ago

Thank you so much! It works.