qzind / tray

Browser plugin for sending documents and raw commands to a printer or attached device.
https://qz.io
Other
850 stars 276 forks source link

PDF CropBox Support #198

Closed dsanders11 closed 4 years ago

dsanders11 commented 7 years ago

It would be great to have the ability to pass a cropbox when printing PDFs. Apache PDFBox supports it. Here's a StackOverflow post discussing implementation. It looks like it would fit into the QZ source code somewhere around here.

We've got a use case where we'd like to print USPS SCAN forms on a 4"x6" thermal printer to avoid adding a full printer for that sole reason. The form is an 8.5"x11" PDF, which we can send to QZ reasonably, but the barcode is too small for USPS to scan. In QZ's defense it's perfectly legible so that's a technical shortcoming on their end.

A solution to the problem would be to crop out just the barcode when printing since it is a fixed location, and this would allow it to fit unscaled on the 4"x6" label since the true size of the barcode is less than that size.

Cropping would need to play nice with rotation - in this specific case the goal is to crop and print landscape rather than the PDF's natural portrait orientation.

Here is an example USPS SCAN form.

tresf commented 7 years ago

@dsanders11 this sounds like an interesting feature. Question... I was under the impression that all of the shipping providers had an API that could fetch labels in various formats. Is this exempt because it's not an actual shipping label?

Secondly, if you have the number from the barcode and the barcode format (e.g. i2of5), this could be generated through a server-side or client-side language and printed through more conventional means (HTML, image).

Back on topic... if this cropbox were implemented, we may have to offer some templated variables (document width, etc) so that the box can be created in relation to the document. We'd also need a way of converting units (pixels, inches, cm, etc).

If you would be OK using static values, it would make the implementation a bit quicker versus something like width - 2in, height - 4in, etc.

Also, you mentioned the problem of rotation, but we will also have to deal with rasterization, automatic content scaling and consider at least the possibility of inclusion into other pixel formats such as image, HTML.

dsanders11 commented 7 years ago

Is this exempt because it's not an actual shipping label?

We use EasyPost to generate shipping labels, and they have no format selection for creating SCAN forms so it's only PDF.

If you would be OK using static values, it would make the implementation a bit quicker versus something like width - 2in, height - 4in, etc.

I was imagining static values, yea. Something like:

cropbox: {x: 3, y: 4, width: 3, height: 2}

Secondly, if you have the number from the barcode and the barcode format (e.g. i2of5), this could be generated through a server-side or client-side language and printed through more conventional means (HTML, image).

This is a really good idea. It's Code 128, which means it's fairly trivial to write ZPL to render it directly on the printer as long as the number is known.

Looks like we're not provided the barcode number in the metadata, although we are provided the tracking codes for each individual shipment. I'll see if I can get EasyPost to add this functionality, which would render my use case for the cropbox null, although I think it's still a nice feature to support if it's not a huge PITA.

dsanders11 commented 7 years ago

@tresf, I think the idea of generating the barcode separately is a great one, and I've got a request in with the provider to add that one little detail to their API (USPS provides it, thankfully), but the ETA is 'a couple months' which from experience is anywhere from 1-6 months.

So, long-term that's a better solution, but in the interim I need something else, so crop box for PDF still seems the most promising. I could do it in JavaScript with a library like pdf.js but unfortunately JavaScript can't access the PDF directly because it's own a server that doesn't set a Cross-Origin Resource Sharing header allowing access, and they aren't going to change that.

tresf commented 4 years ago

Obstacles:

Here's some cropboxes that worked on some USPS labels.

// USPS API
pd.setMediaBox(new PDRectangle(112, 457, 433, 277));

// USPS Click-N-Ship
pd.setMediaBox(new PDRectangle(58, 447, 466, 287));

Using the (-y + upperRight) method, the coordinates can be provided as standard x:y:w:h coordinates:

API Adusted: x: 112.0, y: 335, w: 433, h: 277 Click-N-Ship Adjusted: x: 58, y: 345, width: 466, height: 288

With successful cropping, the next is to convert the coordinates to units (72 dpi):

API: { units: 'in', cropbox: { x: 1.56, y: 4.65, width: 6.00, height: 3.85 } }

Click-N-Ship: { units: 'in', cropbox: { x: 0.80, y: 4.79, width: 6.47, height: 4 } }

Notice how the API fits strictly within a 4x6 label whereas the Click-N-Ship is slightly bigger.

Unfortunately the printing isn't as nice.

@bberenz any ideas? Here's the working code:


                            PDDocument doc;
                if (flavor == PrintingUtilities.Flavor.BASE64) {
                    doc = PDDocument.load(new ByteArrayInputStream(Base64.decodeBase64(data.getString("data"))));
                } else {
                    doc = PDDocument.load(ConnectionUtilities.getInputStream(data.getString("data")));
                }
+
+               // Handle cropbox
+               for(PDPage pd : doc.getPages()) {
+                   // USPS API
+                   float x = 1.56f * 72.0f;
+                   float y = 4.65f * 72.0f;
+                   float w = 6.00f * 72.0f;
+                   float h = 3.85f * 72.0f;
+
+                   /*  // USPS Click-N-Ship
+                   float x = 0.80f * 72.0f;
+                   float y = 4.79f * 72.0f;
+                   float w = 6.47f * 72.0f;
+                   float h = 4.00f * 72.0f;
+                    */
+
+                   // Translate to PDF coordinate system
+                   x =  x + pd.getMediaBox().getLowerLeftX();
+                   y = -y + pd.getMediaBox().getUpperRightY();
+
+                   PDRectangle crop = new PDRectangle(x, y, w, h);
+                   pd.setMediaBox(crop);
+               }
+                
+               // Write the PDF to disk
+               doc.setAllSecurityToBeRemoved(true);
+               doc.save(System.getProperty("user.home'") + "/Desktop/cropbox.pdf");
tresf commented 4 years ago

Added via #563, will be available in 2.1.1. If anyone wants a one-off build with this feature before 2.1.1 is released, just ask. :D