GithubHelp home page GithubHelp logo

pdfapi2's People

Contributors

cl0ne avatar mbeijen avatar paultcochrane avatar sciurius avatar ssimms avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pdfapi2's Issues

Problem with arc (bogen)

The attached program produces the following image
arc-wrong
while it should look like
arc-good

Even though the bogen function is considered obsolete, it is still the only method available to produce arcs in the style of SVG.

arcs.zip

Dimensions are rounded to two decimal points

I'm using PDF::API2 in a script accompanying a TeX package I have developed (Memoize, currently in the process of submission to CTAN). The purpose of the script is to extract selected pages from the PDF generated by TeX. As a paranoid measure, I'm comparing the expected page size to the size of MediaBox. (I know that TeX (pt) and PDF (bp) points are not the same, and I'm performing the conversion.) Some TeX engines (pdfTeX, LuaTeX) can set the number of decimal points used in PDF dimensions (using \pdfdecimaldigits or \pdfvariable decimaldigits). However, when PDF::API2 reads the dimensions with more than two decimal digits, it rounds them down to two. Could this be remedied, or is there a deeper reason for this behaviour?

Unable to get the mediabox (file size) to change

I want to modify this pdf so it's 86 X 327pts and rotation to 270. The rotation works but I can't get it to 86 X 237pts

I need it to be:
Page size: 86 x 327 pts
Page rot: 270

Here's the current size of the pdf with pdfinfo:
Page size: 326.05 x 85.05 pts
Page rot: 0
File size: 10256 bytes
Optimized: no
PDF version: 1.4

I'm unable to get the document to 86 X 327 pts and tried with various settings. It changes it but the 2nd coordinate never gets above 85 pts.

#!/usr/bin/perl
use strict;
use PDF::API2;
my $gpage;

Open an existing PDF file

my $pdf = PDF::API2->open('/tdata/onetest.pdf');

#my $page = $pdf->page();
$gpage = $pdf->openpage(1);

Get page info

$gpage->rotate(270);
my @Rectangle = $gpage->page_size();
print "...Page Size: @Rectangle..\n";

set Media Size

$gpage->boundaries(media => [0, 0, 6 * 72, 4 * 72]);

show new settings

my @Rectangle = $gpage->page_size();
print "...Page Size: @Rectangle..\n";

my @box = $gpage->get_mediabox();
print "... Media Box: @box ... \n";

$gpage->mediabox(86,327);
my @box = $gpage->get_mediabox();

print "... Media Box: @box ... \n";

$pdf->saveas("test.pdf");

Output of the sscript
...Page Size: 0 0 612 792..
...Page Size: 0 0 612 792..
... Media Box: 0 0 432 288 ...
... Media Box: 0 0 86 327 ...

pdfinfo of the files shows:
Page size: 86 x 85.05 pts
Page rot: 270
File size: 11166 bytes
Optimized: no
PDF version: 1.4

########################################################
PDF-API2-2.043
CPAN

Skip junk at end of file

We are encountering a large number of files in the wild with junk at the end (usually html from buggy download pages).

The current open() function in Basic/PDF/File.pm stops after the first 1kB.

The below change continues all the way to the beginning of the file (in a horribly inefficient way - 1k sliding window), but it seems to work:

    #foreach my $offset (1..64) {
    #   $fh->seek($end - 16 * $offset, 0);
    #   $fh->read($buffer, 16 * $offset);
    #   last if $buffer =~ m/startxref($cr|\s*)\d+($cr|\s*)\%\%eof.*?/i;
    #}
    my $scan_length = 16;
    my $scan_start = $end - $scan_length;
    for(;;) {
        $fh->seek($scan_start, 0);
        $fh->read($buffer, $scan_length);
        last if $buffer =~ m/startxref($cr|\s*)\d+($cr|\s*)\%\%eof.*?/i;
        last if $scan_start < 16;
        $scan_start -= 16;
        if($scan_length < 1024) { $scan_length += 16; }
    }

Shouldn't PDF::API2 enable warnings?

At the start of PDF::API2:

package PDF::API2;

use strict;
no warnings qw[ deprecated recursion uninitialized ];

Certain warnings are disabled, but warnings are not enabled. Did the line use warnings; got lost?

Page numbers in page_labels

2.042 introduced a new method to specify page labels, page_labels, deprecating the old pageLabels method. Simultaneously the page number parameter changed from 0-based to 1-based. Even though documented, it can be easily overlooked, as I did, and, in my case it led to PDF documents that were not correctly dealt with by certain viewers.

Would it be possible to add a warning in page_labels when the page parameter is less than 1?

PDF::API2::Content doc mistake

At TEXT STATE, the example reads

my $pdf = PDF::API2->new();
my $page = $pdf->page();
my $text = $pdf->text();

The last line needs to be

my $text = $page->text();

PDF::API2::Content::stroke_color (and fill_color): documentation implies they return "self" (i.e. to chain), but they don't

These methods are coded as setters/getters, but it would be nice if they could be chained with most other methods, as per the POD. In fact I'm not sure when a "getter" would be useful. There seem to be no other "getters" e.g. for line_width, etc.

Also, related, i.e. in the same module, end is grouped into "PATH CONSTRUCTION" category, while it's painting no-op (e.g. see where its description is placed in the Reference); and perhaps it would be worth to document, that

$content = $content->clip(rule => $rule)->end;

is its only practically useful idiom.

Several issues with bookmarks (+fix)

I totally forgot about this.

use strict;
use warnings;
use feature 'say';
use utf8;
use Test::More;
use PDF::API2 2.047;

my $pdf;

########################################################

$pdf = PDF::API2-> new; $pdf-> page;
$pdf-> outline-> outline for 1 .. 2;
$pdf-> outline-> last-> delete;
is $pdf-> outline-> count, 1, 
    'looks like we successfully deleted the last item...';

$pdf = PDF::API2-> from_string( $pdf-> to_string );
is $pdf-> outline-> count, 1, 
    '... (save, re-open and check to be sure)';

########################################################

$pdf = PDF::API2-> new; $pdf-> page;
$pdf-> outline-> outline-> title( 'bookmark' );
$pdf = PDF::API2-> from_string( $pdf-> to_string );

my $title = eval { $pdf-> outline-> first-> title };
is $@, '', 'we can examine bookmarks in existing file';

$pdf-> outline-> count;             # fix for test #3

$title = $pdf-> outline-> first-> title;
is $title, 'bookmark', 'yes we can';

########################################################

my $chinese = '中國的';
$pdf = PDF::API2-> new; $pdf-> page;
$pdf-> outline-> outline-> title( $chinese );
$pdf = PDF::API2-> from_string( $pdf-> to_string );

$pdf-> outline-> count;             # fix for test #3

is join( ' ', unpack 'U*', $pdf-> outline-> first-> title ),
   join( ' ', unpack 'U*', $chinese ),
    '...and read interesting characters';

########################################################

$pdf = PDF::API2-> new; $pdf-> page;
$pdf-> outline-> outline-> title( $_ ) for qw/ a B c D e F /;
$pdf = PDF::API2-> from_string( $pdf-> to_string );

$pdf-> outline-> count;             # fix for test #3

my $bm = $pdf-> outline-> first;
while ( $bm ) {
    my $next = $bm-> next;
    $bm-> delete if $bm-> title =~ /\p{Lu}/;
    $bm = $next
}
$pdf = PDF::API2-> from_string( $pdf-> to_string );

is $pdf-> outline-> count, 3,
    'we can modify outline in existing file and save changes';

########################################################

done_testing;

The output:

ok 1 - looks like we successfully deleted the last item...
not ok 2 - ... (save, re-open and check to be sure)
#   Failed test '... (save, re-open and check to be sure)'
#   at outline.pl line 19.
#          got: '2'
#     expected: '1'
not ok 3 - we can examine bookmarks in existing file
#   Failed test 'we can examine bookmarks in existing file'
#   at outline.pl line 29.
#          got: 'Can't locate object method "title" via package "PDF::API2::Basic::PDF::Objind" at outline.pl line 28.
# '
#     expected: ''
ok 4 - yes we can
not ok 5 - ...and read interesting characters
#   Failed test '...and read interesting characters'
#   at outline.pl line 45.
#          got: '254 255 78 45 87 11 118 132'
#     expected: '20013 22283 30340'
not ok 6 - we can modify outline in existing file and save changes
#   Failed test 'we can modify outline in existing file and save changes'
#   at outline.pl line 67.
#          got: '6'
#     expected: '3'
1..6
# Looks like you failed 4 tests of 6.

2 files to patch:

--- PDF/API2_orig.pm    Sat May 18 21:19:41 2024
+++ PDF/API2.pm Sat Jun 22 11:28:39 2024
@@ -833,6 +833,7 @@
         bless $obj, 'PDF::API2::Outlines';
         $obj->{' api'} = $self;
         weaken $obj->{' api'};
+        $obj->count();
     }
     else {
         $obj = PDF::API2::Outlines->new($self);
--- PDF/API2/Outline_orig.pm    Sat May 18 21:19:41 2024
+++ PDF/API2/Outline.pm Sat Jun 22 11:27:46 2024
@@ -90,26 +90,24 @@
     if ($count) {
         $self->{'Count'} = PDFNum($self->is_open() ? $count : -$count);
     }
-
+    else {
+        delete $self->{'Count'}
+    }
     return $count;
 }

 sub _load_children {
     my $self = shift();
     my $item = $self->{'First'};
-    return unless $item;
-    $item->realise();
-    bless $item, __PACKAGE__;

-    push @{$self->{' children'}}, $item;
-    while ($item->next()) {
-        $item = $item->next();
+    while ($item) {
         $item->realise();
         bless $item, __PACKAGE__;
+        $item->{' api'} = $self->{' api'};
         push @{$self->{' children'}}, $item;
-    }
-    return $self;
-}
+        $item = $item->next()
+     }
+ }

 =head3 first

@@ -124,6 +122,9 @@
     if (defined $self->{' children'} and defined $self->{' children'}->[0]) {
         $self->{'First'} = $self->{' children'}->[0];
     }
+    else {
+        delete $self->{'First'}
+    }
     return $self->{'First'};
 }

@@ -140,6 +141,9 @@
     if (defined $self->{' children'} and defined $self->{' children'}->[-1]) {
         $self->{'Last'} = $self->{' children'}->[-1];
     }
+    else {
+        delete $self->{'Last'}
+    }
     return $self->{'Last'};
 }

@@ -154,7 +158,7 @@

 sub parent {
     my $self = shift();
-    $self->{'Parent'} = shift() if defined $_[0];
+#    $self->{'Parent'} = shift() if defined $_[0];
     return $self->{'Parent'};
 }

@@ -167,8 +171,11 @@
 =cut

 sub prev {
-    my $self = shift();
-    $self->{'Prev'} = shift() if defined $_[0];
+    my ($self, $other) = @_;
+    if ($other) {
+        $self->{'Prev'} = $other;
+        $self->{' api'}{'pdf'}->out_obj($self);
+    }
     return $self->{'Prev'};
 }

@@ -181,8 +188,11 @@
 =cut

 sub next {
-    my $self = shift();
-    $self->{'Next'} = shift() if defined $_[0];
+    my ($self, $other) = @_;
+    if ($other) {
+        $self->{'Next'} = $other;
+        $self->{' api'}{'pdf'}->out_obj($self);
+    }
     return $self->{'Next'};
 }

@@ -208,6 +218,7 @@
         $self->{' api'}->{'pdf'}->new_obj($child);
     }

+    $self->{' api'}{'pdf'}->out_obj($self);
     return $child;
 }

@@ -268,6 +279,7 @@
         $item = $item->next();
         push @{$self->{' children'}}, $item;
     }
+    $self->{' api'}{'pdf'}->out_obj($self);
     return $self;
 }

@@ -286,12 +298,20 @@

     my $prev = $self->prev();
     my $next = $self->next();
-    $prev->next($next) if defined $prev;
-    $next->prev($prev) if defined $next;

+    if (defined $prev and defined $next) {
+        $prev->next($next);
+        $next->prev($prev);
+    }
+    else {
+        delete $prev->{'Next'} if defined $prev;
+        delete $next->{'Prev'} if defined $next;
+    }
+
     my $siblings = $self->parent->{' children'};
     @$siblings = grep { $_ ne $self } @$siblings;
     delete $self->parent->{' children'} unless $self->parent->has_children();
+    $self->{' api'}{'pdf'}->out_obj($self->parent);

     return;
 }
@@ -326,6 +346,7 @@
     # Set
     my $is_open = shift();
     $self->{' closed'} = (not $is_open);
+    $self->{' api'}{'pdf'}->out_obj($self);

     return $self;
 }
@@ -362,12 +383,17 @@
     # Get
     unless (@_) {
         return unless $self->{'Title'};
-        return $self->{'Title'}->val();
+        my $s = $self->{'Title'}->val();
+        if ( $s =~ s/^\x{fe}\x{ff}// ) {
+            $s = Encode::decode( 'UTF-16BE', $s )
+        }
+        return $s;
     }

     # Set
     my $text = shift();
     $self->{'Title'} = PDFStr($text);
+    $self->{' api'}{'pdf'}->out_obj($self);
     return $self;
 }

@@ -403,6 +429,7 @@
         $self->{'Dest'} = PDFStr($destination);
     }

+    $self->{' api'}{'pdf'}->out_obj($self);
     return $self;
 }

@@ -443,6 +470,7 @@
     $self->{'A'}->{'S'}   = PDFName('URI');
     $self->{'A'}->{'URI'} = PDFStr($uri);

+    $self->{' api'}{'pdf'}->out_obj($self);
     return $self;
 }

@@ -465,6 +493,7 @@
     $self->{'A'}->{'S'} = PDFName('Launch');
     $self->{'A'}->{'F'} = PDFStr($file);

+    $self->{' api'}{'pdf'}->out_obj($self);
     return $self;
 }

@@ -511,6 +540,7 @@

     $self->{'A'}->{'D'} = _destination(PDFNum($page_number), $location, @args);

+    $self->{' api'}{'pdf'}->out_obj($self);
     return $self;
 }

Lines marked 'fix for test #3' in the script are no longer required and can be deleted, and:

ok 1 - looks like we successfully deleted the last item...
ok 2 - ... (save, re-open and check to be sure)
ok 3 - we can examine bookmarks in existing file
ok 4 - yes we can
ok 5 - ...and read interesting characters
ok 6 - we can modify outline in existing file and save changes
1..6

(and outline.t passes OK)

  • I understand the PDF::API2::Outline::count() does not "count" descendants/children, but its use is OK for these tests. Further, it may seem that tests 1-2 are superfluous and their case is covered in later tests, but that is not exactly true. The later tests deal only with accessing outline in existing (i.e. opened with PDF::API2) files. The 1st test also produces invalid PDF syntax, i.e. broken outline tree, in freshly generated file. The same happens when deleting the first outline item, but then the "phantom" bookmark is somehow "invisible" i.e. ignored by Reader, PDF::API2, etc. The new first/last item still have their prev/next attribute set and pointing at phantom (deleted) item. (Perhaps "Outline tree" has unnecessary redundancy and is never checked in full by readers/browsers?) As to "why would anyone want to generate a fresh tree and immediately delete the first/last item" -- I don't know.
  • Test #3 fails because objects aren't blessed into correct class. Effectively, the PDF::API2::Outline::count() will read the entire tree and re-bless (therefore test #2 did not result in fatal error); this call is injected in the above script to enable tests 4-6; and is later used in the patch, seemingly as a no-op.
  • I'll further comment on changes if necessary. Some were discovered only later when fixing and testing for most obvious ones. Maybe I missed something.

Edit: I think I missed something indeed! (Rubber duck strikes again.) The count() can't reliably be used to re-bless because it doesn't follow items which are "closed". Perhaps dedicated internal traversing sub will be required.

Edit 2 : So, then, the sub below should be added/appended to PDF/API2/Outline.pm; and line patched in PDF/API2.pm should be a call to this sub i.e. $obj->_fix_on_open();

sub _fix_on_open {
    my $self = shift();

    if ($self->has_children()) {
        $self->_load_children() unless exists $self->{' children'};
        foreach my $child (@{$self->{' children'}}) {
            $child->_fix_on_open();
        }
    }
}

inserting a logo pdf (correct pdf) into another pdf (correct pdf) using embed_page() results in a broken PDF (tested with gs-9.561)

I need to add a cc-by-nc logo to a large number of pdf files.
In order to do this, I wrote a short test script, which opens a source file (metz_03_3-preview.pdf) and places a logo (ccbync-inkscape.pdf) at a preset position in the source file.
While both the source PDF and the logo pdf open in gs 9.561 without a hitch, the source file with the logo inserted (with-ccbync-logo.pdf) miserably fails to open in gs with the following error:

The following errors were encountered at least once while processing this file:
object lacks an endobj
PDF file was repaired

**** This file had errors that were repaired or ignored.
**** The file was produced by:
**** >>>> itext-paulo-155 (itextpdf.sf.net - lowagie.com) <<<<
**** Please notify the author of the software that produced this
**** file that it does not conform to Adobe's published PDF
**** specification.

Interestingly, evince is obviously able to sanitize the broken file and display it, but neither is gs, nor acrobat.
Unfortunately, my knowledge of the inner pdf structures is limited and I have not been able to diagnose which object lacks the endobj that gs moans about.

----------------here's the test script ----
#!/usr/bin/perl
use strict;
use warnings;
use PDF::API2;

my $pdf = PDF::API2->new();
my $source_pdf = PDF::API2->open('metz_03_3-preview.pdf');

for (my $page=1; $page <= 1; $page++) {
my $source_page = $source_pdf->open_page($page);

#read logo
my $logo= PDF::API2->open('ccbync-inkscape.pdf');
# Import Page 1 from the logo PDF
my $object = $logo->embed_page($logo, 1);

# Add it to the source PDF's first page at 1/2 scale
my ($x, $y) = (498,765);
$source_page->object($object, $x, $y, 0.5);

}
$source_pdf->{forcecompress} = 0; ## doesn't solve the problem
$source_pdf->save('with-ccbync-logo.pdf');

the tar.gz contains both test script, source-pdf, logo-pdf for testing.
Any hint how to fix this is greatly appreciated.

pdf-api2-2044-error-inserting-pdf-into-pdf-endobj-missing.tar.gz

Confusing image / object handling

It is nice that images and other objects can be placed with a single call:

$gfx->object( $thing, $x, $y, ... )

When I have a $thing that is 100x100 and needs to be scaled 50%, then apparently I still do need to distinguish images (that need $w=50 and $h=50) from other objects (that need $scale_x=0.5 and $scale_y=0.5).

Am I missing something?

Pagemode "SinglePage" unsupported

By default PDF::API2 creates documents with pagemode 'SinglePage', however the page_mode method doesn't understand this.

use PDF::API2;

my $pdf = PDF::API2->new;
my $page = $pdf->page;
my $text = $page->text;
$text->translate( 100,100);
$text->font( $pdf->font("Times-Roman"), 20 );;
$text->text("Hello, World");
$pdf->save("x.pdf");

$pdf = PDF::API2->open("x.pdf");
warn("pagemode = ", $pdf->page_mode, "\n");

PDF::API2 making pdf's that CUPS can not print

I wrote some perl code about ten years ago that uses PDF::API2 to open an existing pdf, add some stuff, and save it as a new pdf. (basically using a template to do fill-in-the-blanks) and then sending the new pdf to CUPS for printing. This has worked fine for all these years! (thanks!) But this week I upgraded the system OS from Debian Buster to Bullseye, and with that upgrade I had to re-install all of my CPAN modules including PDF::API2. After the OS upgrade, something is broken. The saved PDF can not be printed by CUPS (doesn't seem to matter what printer, I tried several). I think this is the relevant error from the CUPS log:

E [22/Aug/2022:18:47:35 -0400] [Job 6462] loadFilename failed: /var/spool/cups/d06462-001 (offset 79458): unable to find /Root dictionary

That is a very obscure error, I can only find maybe 4 hits in all of google. So I'm really not sure if this is CUPS or PDF::API2. However, the following tidbits led me to think it was PDF::API2. 1) CUPS can still print the original (ten year old) template file, it's only the new files made by PDF::API2 that won't print. 2) if I trim out a section "<< ,,, >>" of the new PDF that contains the string "/Root", the file becomes printable, 3) the new pdf contains multiple %%EOF. I dont know much PDF but I think it should only have one?

If you want a look at the template PDF, the resulting PDF, or my code, I will snip it together and post it here.

Thanks!

Feature: Flexible font handling

With 2.042, it is possible to create a font using a single method, font, instead of having to choose the right method for built-in and TT fonts.

I like to suggest to carry this one step further. I would like to use a font name or filename everywhere currently a font object is required. For example, assuming $tc is a text content:

$tc->font( "Times-Roman", 12 );
$tc->text( "Hello, World!" );

A trivial cache suffices to reuse font objects.

In a later stadium, integration with fontconfig would allow state of the art font selection, e.g.

$tc->font( "Serif 14 bold" );

What do you think?

Understanding translate

I cannot understand the results of the attached program.

The first part behaves as if the transformation is relative, even though relative => $rel with $rel = 0.

The scond part displays the big crosshairs at the expected place, but the other transformations act if relative to ???

The last magenta crosshairs seems to be translated [20,10] w.r.t the previous blue crosshairs, despite the save/restore.

I must be missing something very fundamental (stupid...)?

translate.zip

Creating a PDF destroys existing file

Creating a PDF object immedeately writes PDF header to disk.

$pdf = PDF::API2->new( file => "tl.pdf" );

I am not sure this is intentional, I'd expect that the file is not created/written until save is performed.

PDF::API2->from_string causes for action "page1-to-thumbnail" an OOM (memory-leak, loop)

We have normal pdf created with "Acrobat PDFMaker for Word". It has only 6 pages.

We try to generate a Thumbnail from the PDF per PDF::API2

$file = '2024_Q1_-digitale-Veranstaltungen_de.pdf';
my $data = do {
    local $/ = undef;
    open my $fh, "<", $file or die "could not open $file: $!";
    <$fh>;
};

$pdf = PDF::API2->from_string($data);
# never gets here

my $sp_pdf = new PDF::API2;
eval {
    $sp_pdf->import_page($pdf,1,0);
};
if ($@) {
    warn $@;
}
my $image = Image::Magick->new;
$error = $image->BlobToImage($sp_pdf->stringify);

It hangs on line 1 (first from_string()) forever, increasing the use resident-memory over time (using 1GB per 300 seconds more)
2024_Q1_-digitale-Veranstaltungen_de.pdf

Workaround is to encapsulate the "from_string" method with an POSIX::sigaction and alarm(10).

Page boundaries should be adjusted after content is "un-rotated" on open_page()

use strict;
use warnings;
use feature 'say';
use PDF::API2;

my $pdf = PDF::API2-> new;

my $p = $pdf-> page;
$p-> boundaries( 
    media => [ 0, 0, 600, 400 ], 
    crop  => [ 50, 50, 600, 400 ],
);
$p-> rotation( 180 );

my $f = $pdf-> font( 'Times-Roman' );
$p-> text
  -> font( $f, 96 )
  -> position( 100, 300 )
  -> text( 'Hello, PDF!' );

$pdf = PDF::API2-> from_string( $pdf-> to_string );

$pdf-> open_page( 1 );          # no-op? nope   (*)

$pdf-> save( 'visual_test.pdf' );

Initial PDF, serialized to string, simulates a file from external source. Boxes (e.g. CropBox here) is non-symmetrical relative to MediaBox, and page was rotated. The content is just chosen to be clearly seen if deformed/cropped. Comment/uncomment the star (*) marked line and inspect the output. This affects opening files and then adding content to a page, as well as importing a page to another document. Gaps between (Crop|Trim|Bleed|Art)Box and MediaBox should stay the same on top/left/bottom/right edges of un-rotated page, as they were on rotated page. If I get it right. Looks like this bug is primordial i.e. 20+ y.o. I'll try to supply a fix unless someone comes with better idea.

Edit: Actually, MediaBox on its own is enough to observe a bug, e.g. replace in the above with:

my $p = $pdf-> page;
$p-> boundaries( 
    media => [ 50, 50, 600, 400 ], 
);
$p-> rotation( 90 );

The "un-rotating" code in PDF/API2.pm does at least something to boxes boundaries in case of 90 and 270 degrees, and nothing for 180. Here we see that the bug is not because nothing was done for 180, but the whole logic appears to be simplistic and flawed.

Not a HASH reference at PDF/API2.pm line 2360.

Consider this example:

use strict;
use warnings;
use GD::Image;
use PDF::API2;

my $image  = GD::Image->new(300,300);
$image->colorAllocate(255,255,255);
$image->colorAllocate(0,0,0);
$image->colorAllocate(255,0,0);
$image->rectangle(0,0,300,300,0);
$image->filledRectangle(10,10,50,50,2);
my $pdf = PDF::API2->new();
my $page = $pdf->page();
$page->boundaries(media => '8x11');
my $image2 = $pdf->image($image);
$page->object($image2, 0, 0, 200 * 72/96, 200 * 72/96);
$pdf->save('test.pdf');

The program fails with

Not a HASH reference at /home/hakon/perlbrew/perls/perl-5.34.1/lib/site_perl/5.34.1/PDF/API2.pm line 2360.

I believe the problem is

return image_gd($file, %options);

this line should rather be

return $self->image_gd($file, %options);

passing $self as first argument.

Puzzled by text position

On a new page

$text->position(72, 720);
( $x, $y ) = $text->position;
$text->text("Hello, World! @ $x,$y");

The docs say that position moves to the start of the current line of text, offset by $x and $y.
The text "Hello World! @ 72,720" is placed at position 72,720 on the page. So apparently the beginning of the current line is initially at 0,0. After setting the position, the current line now starts at 0,720.

Then

$text->position(0, -100);
( $x, $y ) = $text->position;
$text->text("Hello, World! @ $x,$y");

The text "Hello World! @ 0,620" is placed at position 72,620 on the page.

I don't understand. I would have expected "Hello World! @ 0,620" to be placed at position 0,620. Or "Hello World! @ 72,620" placed at position 72,620. What am I missing?

Some fonts stopped working

The attached program uses the Amiri font. I 100% sure it used to work in the past (I have PDF docs to prove it) but now I only get a blank page.
Inspection shows that the font is embedded, the glyphs are emitted (I can copy them from the PDF) but nothing shows.

The Amiri font is installed from the Fedora repository.
I tried PDF::API2 from git, and PDF::Builder from git.

Any clues?

amiri.pl.txt

Warning in PDF/API2/Resource/CIDFont.pm encodeByName() with utf-8 encoding.

When calling ttfont() on a PDF::API2 object with -encoding => 'utf-8', I receive a warning three times. Unfortunately I understand neither the aim of the code here, nor pack and decode well enough, to say much more.

use PDF::API2;
print "Version: $PDF::API2::VERSION\n";

my $pdf = PDF::API2->new();
$pdf->addFontDirs('var/fonts'); # some font containing directory

my $font = $pdf->ttfont('DejaVuSansMono.ttf', '-encode', 'utf-8');
Version: 2.045
Use of uninitialized value in hash element at /usr/local/share/perl/5.26.1/PDF/API2/Resource/CIDFont.pm line 290.
Use of uninitialized value in hash element at /usr/local/share/perl/5.26.1/PDF/API2/Resource/CIDFont.pm line 290.
Use of uninitialized value in hash element at /usr/local/share/perl/5.26.1/PDF/API2/Resource/CIDFont.pm line 290.

This comes from

unpack('U*', decode($enc, pack('C*', 0 .. 255)))
only returning 253 instead of 256 strings, and thus the line above mapping (253..255) onto undef.

Old versions (at least 2.033-old) had a no warnings qw[ deprecated recursion uninitialized ]; in the code, so I assume that this is maybe no error. And map { unpack( 'U*', decode( $enc, pack( 'C*', $_ ) ) ) } (0..255) would do something which looks similar in outcome (to me^^), but doesn't have the gaps, but that is, as said before, just guess work...

Thanks in any case and best regards, Sven

Problem rendering TIFF images

use PDF::API2;
my $pdf = PDF::API2->new;
my $page = $pdf->page;
my $gfx = $page->gfx;
my $img = $pdf->image("squirrel.tiff");
$gfx->object( $img, 100, 500, 100, 100 );
$pdf->save("squirrel.pdf");

When applied to the attached TIFF image file, it renders a garbled image.
tiff.zip

paragraph justified text does not work with embedded fonts because word spacing only applies to x20 ascii char

according to PDF32000-1:2008 specifications for PDF1.7 point 9.3.3 at pages 244-245
and point 9.4.3 at pages 250-251
I suggest to change or overclass function text_fill_justified as follows for a better management of justified text.

*PDF::API2::Content::text_fill_justified =
sub {
    my ($self, $text, $width, %opts) = @_;
    my ($line, $ret) = $self->_text_fill_line($text, $width);
    my $ws = $self->wordspace();
    my $w = $self->advancewidth($line);
    my $space_count = (split /\s/, $line) - 1;

    # Normal Line
    if ($ret) {
        #$self->wordspace(($width - $w) / $space_count) if $space_count;
        #$width = $self->text($line, %opts, align => 'left');
        #$self->wordspace($ws);
        my $s = $self->advancewidth(' ') + (($width - $w) / $space_count) if $space_count;
        my @words = split /\s/, $line;
        $width =0;
        foreach my $word (@words){
        	$width += $self->text($word, %opts, align => 'left');
        	$opts{indent} = $s if $space_count;
        }
        return $width, $ret;
    }

    # Last Line
    if ($opts{'align-last'}) {
        unless ($opts{'align-last'} =~ /^(left|center|right|justified)$/) {
            croak 'Invalid align-last (must be left, center, right, or justified)';
        }
    }
    my $align_last = $opts{'align-last'} // 'left';
    if ($align_last eq 'left') {
        $width = $self->text($line, %opts, align => 'left');
    }
    elsif ($align_last eq 'center') {
        $width = $self->text($line, %opts, align => 'center');
    }
    elsif ($align_last eq 'right') {
        $width = $self->text($line, %opts, align => 'right');
    }
    else { 
        #$self->wordspace(($width - $w) / $space_count) if $space_count;
        #$width = $self->text($line, %opts, align => 'left');
        #$self->wordspace($ws);
        my $s = $self->advancewidth(' ') + (($width - $w) / $space_count) if $space_count;
        my @words = split /\s/, $line;
        $width =0;
        foreach my $word (@words){
        	$width += $self->text($word, %opts, align => 'left');
        	$opts{indent} = $s if $space_count;
        }
    }
    return $width, $ret;
};

Appending many pages goes quadratic

For e.g. "data merge", VDP printing and similar cases, PDF files with a few thousand pages aren't extraordinary. Content is generated and appended "on the fly" page by page; PDF::API2 can't add pages other than "one by one" anyway.

use strict;
use warnings;
use Time::HiRes 'time';
use File::Temp 'tempfile';
use PDF::API2 2.045;

my ( $fh, $tmp ) = tempfile;

for my $n ( map $_ * 1000, 1 .. 5 ) {
    my $t = time;
    my $pdf = PDF::API2-> new( file => $tmp );
    for ( 1 .. $n ) {
        $pdf-> page;
    }
    $pdf-> save;
    printf "%d\t%.2f\n", $n, time - $t
}

It prints:

1000    0.53
2000    1.95
3000    4.32
4000    8.66
5000    15.52

Compare:

use PDF::Reuse;
for my $n ( map $_ * 1000, 1 .. 5 ) {
    my $t = time;
    prFile( $tmp );
    for ( 1 .. $n ) {
        prText( 0, 0, 'text' );
        prPage;
    }
    prEnd;
    printf "%d\t%.2f\n", $n, time - $t
}

and:

1000    0.03
2000    0.06
3000    0.08
4000    0.11
5000    0.14

(Had to add at least something to every page with that module)

As Devel::NYTProf reveals, time is wasted in PDF::API2::Basic::PDF::Pages::find_page_recurse. Perhaps appending pages is common enough task, no need to constantly "find" which page to append after i.e. the last.

A patch:

--- PDF/API2/Basic/PDF/Pages orig.pm    Mon Sep 25 20:00:09 2023
+++ PDF/API2/Basic/PDF/Pages.pm Fri Feb 23 11:28:43 2024
@@ -132,15 +132,23 @@
     my ($self, $page, $page_number) = @_;
     my $top = $self->get_top();

-    $page_number = -1 unless defined $page_number and $page_number <= $top->{'Count'}->val();
+    $page_number = -1 unless defined $page_number and $page_number < $top->{'Count'}->val();

     my $previous_page;
     if ($page_number == -1) {
-        $previous_page = $top->find_page($top->{'Count'}->val() - 1);
+        $previous_page = $top->{' last_page'}
+            ? $top->{' last_page'}
+            : $top->find_page($top->{'Count'}->val() - 1);
+        $top->{' last_page'} = $page;
     }
     else {
         $page_number = $top->{'Count'}->val() + $page_number + 1 if $page_number < 0;
-        $previous_page = $top->find_page($page_number);
+        if ($top->{'Count'}->val() == scalar $top->{'Kids'}->realise->elements()) {
+            $previous_page = ($top->{'Kids'}->elements())[$page_number];
+        }
+        else {
+            $previous_page = $top->find_page($page_number);
+        }
     }

     my $parent;
@@ -180,7 +188,7 @@

     my $parent = $self;
     my $max_kids_per_parent = 8; # Why?
-    if (scalar $parent->{'Kids'}->elements() >= $max_kids_per_parent and $parent->{'Parent'} and $page_index < 1) {
+    if (scalar $parent->{'Kids'}->elements() >= $max_kids_per_parent and $parent->{'Parent'} and $page_index < 0) {
         my $grandparent = $parent->{'Parent'}->realise();
         $parent = $parent->new($parent->_pdf(), $grandparent);

Test suite runs OK; benchmark is now tolerable:

1000    0.18
2000    0.32
3000    0.49
4000    0.67
5000    0.84

In the patch, the last line changed is for #70. 2 shortcuts above it:

  1. The ' last_page' property of pages tree root is created/maintained/used. If PDF::API2 is ever going to shuffle/remove pages, this property should be invalidated by these (non-existent now) calls.
  2. Not for appending, but inserting anywhere in case of "tree" being a simple flat list (***). This shortcut could be used alone instead of (1), which would be slightly slower.

The very 1st line changed by the patch: the <= must be <. I noticed it accidentally while looking at debugging output. Seems harmless, like as if this bug is compensated/cancelled-out somewhere nearby.

(+) Please note, large positive argument to PDF::API2::page is clamped to valid range, but large negative one causes a crash with ugly error message.

(***) Further note, or rather RFC. ("Patch" is for whomever does VDP, etc. and bumps into performance issues. Slim chance it's accepted into distribution. Then "RFC" is even funnier.) I've never seen issues/slow-downs/RAM-hogs with Reader or other PDF consumer apps if pages "tree" is a flat array/list, even for tens of thousands of pages, compared to a "proper tree". The latest PDF 2.0 spec repeats after its 1990-s predecessor: "balanced tree for performance and/or limited memory". Do they just copy-paste it mindlessly, is/was this consideration relevant? Adding pages into a tree is triggered, in PDF::API2, only very rarely. E.g., benchmarking code above creates a flat list 5000 pages long. OTOH, the PDF::Reuse, also above, does create a tree with no more than 10 branches/leaves per node. Is it worth pursuing this goal e.g. to rebuild_tree?

RT121911 re-open/fix -- off-by-one error

Hi, looks like off-by-one error at

https://metacpan.org/dist/PDF-API2/source/lib/PDF/API2/Basic/PDF/Pages.pm#L183

where it should be "0", not "1" (rather, condition should be == -1, to the same effect). Currently, $pdf->page(1); adds a new page at the end for sample.pdf attached there. Alternatively, see Perl-PDF-Builder/issues/203 for a sample -- more interesting tree => curiouser bugs for positions other than "1". No bugs with fix as proposed

Underlinethickness and position are incorrect for most fonts.

In PDF/API2/Resource/CIDFont/TrueType/FontFile.pm, font metrics are scaled to 1000/upem.
However, underlineposition and underlinethickness are not scaled.

PDF/API2/Content.pm, lines 2043-2044 seem to assume 1000, not upem:

    my $underlineposition = (-$self->{' font'}->underlineposition() * $self->{' fontsize'} / 1000 || 1);
    my $underlinethickness = ($self->{' font'}->underlinethickness() * $self->{' fontsize'} / 1000 || 1);

This affects all fonts with a design size not equal to 1000, which includes all modern TTF/OTF fonts that have a design size 2048.

The result can be seen in the attached image: left is LibreOffice, right is PDF::API2. The font is DejaVuSerif.

scrot20240410121629

It is probably too late to change but it should at least be documented somewhere.

RFC: Proposal for more flexible file/data handling for image routines.

To include an image in a PDF document, PDF::API2 provides the method image:

$img = $pdf->image( $file, %options );

where $file may be either a file name, a filehandle, or a GD::Image object.

In a number of situations the calling program provides the image data itself, or has the image file already read into a scalar variable. To pass this data to the image method we need a temporary file, or 'wrap' it in a file handle:

open( my $fh, '<:raw', \$data );
$img = $pdf->image( $fh, %options );

This works most of the time, but crashes Image::PNG::Libpng.

Although it is not very hard to augment image to (also) take scalar data as its first argument I think it is better to add a new method, image_from_scalar.

$img = $pdf->image_from_scalar( $data, %options );

This will allow PDF::API2 and calling programs to take advantage of file data in scalars without the need to use temporary files or faked file handles.

If you agree I offer to make a PR.

EDIT: $font = $pdf->font($file) is another good candidate for a font_from_scalar sibling.

Incompatible change in pageLabel

Before the last changes, pageLabel accepted style arguments roman, arabic, etc.
In 2.042, it merely takes the first letter (provided it is a, d or r).
In short, arabic now comes out as a (lowercase letters) instead of D (Decimal arabic numerals).

See #39 for a possible fix.

Extra "59" in LICENSE

The LICENSE file in the source tarball contains these lines:

Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59
51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

Please note there is extra 59 at the end of first line above when compared to https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt.

I'm not sure where the LICENSE file comes from (it is not in git), but it would be great if the extra 59 is removed from it.

Thank you.

PDF::API2::Basic::PDF::File::ship_out is not compliant to spec ISO 19005-1:2005, Clause: 6.1.8

ISO 19005-1:2005, Clause: 6.1.8
The object number and generation number shall be separated by a single white-space character. The generation number and obj keyword shall be separated by a single white-space character. The object number and endobj keyword shall each be preceded by an EOL marker. The obj and endobj keywords shall each be followed by an EOL marker.

patch two line:
from
$fh->printf('%d %d obj ', $objnum, $objgen);
to
$fh->printf('%d %d obj'."\n", $objnum, $objgen);
and from
$fh->print(" endobj\n");
to
$fh->print("\n"."endobj\n");

Reading and modifying a read-only PDF creates corrupted output

If the input PDF file is read-only the output PDF is corrupt. E.g. KDE's okular shows "Some errors were found in the document, Okular might not be able to show the content correctly" and Ghostscript prints "Dereference of free object 22, next object number as offset failed (code = -18), returning NULL object." - anyway, both show the PDF. I was told, some windows PDF apps can't view the files.
With the old version v2.038 of PDF::API2 this does not happen.

To reproduce I used this perl script and the attached input pdf:

#!/usr/bin/perl -w
use PDF::API2;

my $pdf=PDF::API2->open('test_input.pdf');
my $page=$pdf->openpage(1);
my $text=$page->text();
my $font=$pdf->font('Helvetica-Bold');
$text->font($font, 20);
$text->position(200, 700);
$text->text('Hello World!');
$pdf->saveas('test_output.pdf');

Sample input PDF: test_input.pdf
Running the script with a read-write input file, the output is ok: test_output_from_rw-ok.pdf
After making the input file read-only (e.g. chmod 444 test_input.pdf), the output is corrupt: test_output_from_ro-error.pdf

Tested with versions 2.043, 2.045 and 2.047 on Ubuntu and openSuse systems.

Viewer preference non_full_screen_page_mode returns invalid value

By default, PDF::API2 provides the value use_none for viewer preference non_full_screen_page_mode. However, this value is not understood by the viewer_prefrences method.

my $pdf = PDF::API2->new;
my %i = $pdf->viewer_preferences;
warn("viewer_preferences non_full_screen_page_mode = ",
     $i{non_full_screen_page_mode}, "\n");
$pdf->viewer_preferences(%i);

Symbol (core)fonts and utf8 clash

Short description: When an perl string containing utf8 characters is printed with Symbol or ZapfDingbats corefont, nothing (a sequence of null characters) is printed.

use strict;
use utf8;
use PDF::API2 2.043;   # Or PDF::Builder

my $pdf = PDF::API2->new;
my $page = $pdf->page;
my $text = $page->text;

# First, use corefont Times-Roman.
# "ABC" is shown twice (the smiley cannot be printed
# since it is not in the corefont).
my $font = $pdf->font("Times-Roman");
$text->font($font,30);
$text->translate(100,700);
$text->text("ABC");		# (ABC) Tj
$text->translate(200,700);
$text->text("ABC☺");		# (ABC\0) Tj

# Now, use a symol corefont.
# "ABC" is shown once, the second occurrence completely disappears.
$font = $pdf->font("ZapfDingbats");
$text->font($font,30);
$text->translate(100,600);
$text->text("ABC");		# (ABC) Tj
$text->translate(200,600);
$text->text("ABC☺");		# (\0\0\0\0) Tj

$pdf->save("plain.pdf");

pdf generated by PDF::API2 unprintable by cups

I have some ten-year-old legacy code that was broken when I upgraded to the most recent version of PDF::API2. The code was opening an existing pdf file, writing some stuff into it, then saving it as a new file. (basically using the existing file as a template). The pdf that is generated can still be opened by Acrobat and various web browsers, but when CUPS tries to print it, it generates the following error: (/var/log/cups/error.log)

loadFilename failed: /var/spool/cups/d06466-001 (offset 88342): unable to find /Root dictionary

That's a really obscure error, I could only find 3 references to it in all of google.

So, i suppose its possible that the problem is with CUPS, not sure really. When I examined the pdf file that was created by API::PDF2 it did seem a bit odd looking, but I'm not expert on PDF. I was able to avoid the problem by rewriting my legacy code to not use the template pdf and instead generate a new empty pdf and write what was necessary into it.

PDF::API2->open() holds a filesystem lock until the returned object goes out of scope

This is a weird bug that I see in Windows (Strawberry Perl) but not linux (native perl).

When I open a PDF file with PDF::API2->open(), somehow there is a lingering filesystem lock that prevents me from deleting the input file until the returned PDF object goes out of scope.

For example, this code:

my $pdf = PDF::API2->open('test.pdf');
unlink 'test.pdf' or warn "Could not unlink 'test.pdf': $!";

results in the following error:

Could not unlink 'test.pdf': Permission denied at bad.pl line 12.

But if I undefine $pdf (or force it to go out of scope):

my $pdf = PDF::API2->open('test.pdf');
$pdf = undef;
unlink 'test.pdf' or warn "Could not unlink 'test.pdf': $!";

then the unlink operation on the input file succeeds.

I tried the following versions, and the bug occurs with all of them:

  • PDF::API2 versions 2.040, 2.041
  • Strawberry Perl versions 5.26.3, 5.28.2, 5.30.3, 5.32.1

Tiny testcase at:

testcase.tar.gz

Incorrect format for PDF Dates

According to PDF 1.7 section 7.9.4 the (full) format is YYYYMMDDHHmmSSOHH'mm'. Both HH and mm (if present) must be followed by an apostrophe character. Setting a valid date like 20230313194003+01'00' will result in an error Invalid date string: D:20230313194003+01'00' at ....

The check on the date format was introduced in 2.042. The regexp in sub _is_date (PDF/API2, around line 555) should be:

    return unless $value =~ /^D:([0-9]{4})        # D:YYYY (required)
                             (?:([01][0-9])       # Month (01-12)
                             (?:([0123][0-9])     # Day (01-31)
                             (?:([012][0-9])      # Hour (00-23)
                             (?:([012345][0-9])   # Minute (00-59)
                             (?:([012345][0-9])   # Second (00-59)
                             (?:([Z+-])           # UT Offset Direction
                             (?:([012][0-9]\')    # UT Offset Hours plus apostrophe
                             (?:([012345][0-9]\') # UT Offset Minutes plus apostrophe
                             )?)?)?)?)?)?)?)?$/x;

How to name a destination

Annotations have a method link.

$annotation->link($destination, $location, @args);

According to the docs, $destination is a PDF::API2::Page object or the name of a named destination defined elsewhere.
I must be overlooking something, but how can I define a named destination?

$destination = PDF::API2::NamedDestination->new($pdf, ...);

This seems to produce an unnamed (explicit) destination.

PDF::API2::Content::matrix property purpose

Greetings!

Private matrix field is never used (neither written to nor read from)

$self->{' matrix'} = [1, 0, 0, 1, 0, 0];

and in matrix property we return $self for graphics objects:
sub matrix {
my $self = shift();
if (scalar(@_)) {
my ($a, $b, $c, $d, $e, $f) = @_;
if ($self->_in_text_object()) {
$self->add(_matrix_text($a, $b, $c, $d, $e, $f));
$self->{' textmatrix'} = [$a, $b, $c, $d, $e, $f];
$self->{' textlinematrix'} = [0, 0];
}
else {
$self->add(_matrix_gfx($a, $b, $c, $d, $e, $f));
}
}
if ($self->_in_text_object()) {
return @{$self->{' textmatrix'}};
}
else {
return $self;
}
}

Shouldn't we store changes to the graphics state matrix in the aforementioned private field and return it from matrix accessor?

my $pdf = PDF::API2->open( $keyhash->{filename} ) FAILS when PDF is protected

Rosenshine.pdf

When I try to open the attached file, my code crashes with this error:

Uncaught exception from user code:
Objind 163 does not exist at index 0 at /Library/Perl/5.30/PDF/API2/Basic/PDF/File.pm line 783.
PDF::API2::Basic::PDF::File::read_objnum(PDF::API2::Basic::PDF::File=HASH(0x12ebded98), 163, 0) called at /Library/Perl/5.30/PDF/API2/Basic/PDF/File.pm line 737
PDF::API2::Basic::PDF::File::read_obj(PDF::API2::Basic::PDF::File=HASH(0x12ebded98), PDF::API2::Basic::PDF::Objind=HASH(0x12f818f98)) called at /Library/Perl/5.30/PDF/API2/Basic/PDF/Objind.pm line 155
PDF::API2::Basic::PDF::Objind::realise(PDF::API2::Basic::PDF::Objind=HASH(0x12f818f98)) called at /Library/Perl/5.30/PDF/API2.pm line 179
PDF::API2::_open_common(PDF::API2=HASH(0x12ebdedc8)) called at /Library/Perl/5.30/PDF/API2.pm line 166
PDF::API2::open("PDF::API2", "LP/Rosenshine.pdf") called at AnalyseIMSCCv2.17.pl line 2357
main::GetPDFMetadata(HASH(0x12f48aec0)) called at AnalyseIMSCCv2.17.pl line 5463
main::ProcessItems() called at AnalyseIMSCCv2.17.pl line 770

Problem loading an emoji font

Hi,

I was trying to put some emoji characters in a PDF, and started by trying to load an emoji font.

use strict;
use warnings;
use PDF::API2;

my $pdf  = PDF::API2->new();
my $page = $pdf->page();
my $text = $page->text();

# the font was downloaded from
# https://github.com/googlefonts/noto-emoji/raw/main/fonts/NotoColorEmoji.ttf
my $font = $pdf->font('NotoColorEmoji.ttf');

$text->font( $font, 24 );
$text->position( 72, 720 );
$text->text('Hello world');

$pdf->save('sample.pdf');

The script fails while loading the font:

Can't call method "read" on an undefined value at /usr/share/perl5/PDF/API2/Resource/CIDFont/TrueType/FontFile.pm line 556.

I expect putting some Unicode Emoji character in a PDF file is bit more complicated than doing $text->text("♥") with the appropriate font. This is failing when loading the font, however.

Regards,

Q: Delete, discard, overwrite pages

While creating a PDF document I create pages sequentially.
At a certain point I decide that the last couple of pages that have been written are wrong, and I want to discard these and then add new pages.
Since PDF::API2 does not have a remove_page method, what would be 'the right' way to obtain this? Do I need to manually manipulate the pagestack?

Page import discards annotations

When I import a page from an existing PDF file, the contents is imported properly but the annotations are discarded.

If this is a permanent restriction, please add this to the documentation of the import_page method.

Example program

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.