Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions js/qz-tray.js
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ var qz = (function() {
rotation: 0,
scaleContent: true,
size: null,
stream: false,
units: 'in',

forceRaw: false,
Expand Down Expand Up @@ -1533,6 +1534,7 @@ var qz = (function() {
* @param {Object} [options.spool=null] Advanced spooling options.
* @param {number} [options.spool.size=null] Number of pages per spool. Default is no limit. If <code>spool.end</code> is provided, defaults to <code>1</code>
* @param {string} [options.spool.end=null] Raw only: Character(s) denoting end of a page to control spooling.
* @param {boolean} [options.stream=false] Currently PDF only. If documents should be loaded on demand.
*
* @memberof qz.configs
*/
Expand Down
9 changes: 9 additions & 0 deletions src/qz/printer/PrintOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,10 @@ public PrintOptions(JSONObject configOpts, PrintOutput output, PrintingUtilities
LoggerUtilities.optionWarn(log, "JSONObject", "size", configOpts.opt("size"));
}
}
if (!configOpts.isNull("stream")) {
try { psOptions.stream = configOpts.getBoolean("stream"); }
catch(JSONException e) { LoggerUtilities.optionWarn(log, "boolean", "stream", configOpts.opt("stream")); }
}

//grab any useful service defaults
PrinterResolution defaultRes = null;
Expand Down Expand Up @@ -477,6 +481,7 @@ public class Pixel {
private double rotation = 0; //Image rotation
private boolean scaleContent = true; //Adjust paper size for best image fit
private Size size = null; //Paper size
private boolean stream = false; //Lazy loading documents
private Unit units = Unit.INCH; //Units for density, margins, size


Expand Down Expand Up @@ -556,6 +561,10 @@ public Size getSize() {
return size;
}

public boolean isStream() {
return stream;
}

public Unit getUnits() {
return units;
}
Expand Down
128 changes: 36 additions & 92 deletions src/qz/printer/action/PrintPDF.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import qz.printer.PrintOptions;
import qz.printer.PrintOutput;
import qz.printer.action.pdf.BookBundle;
import qz.printer.action.pdf.FuturePdf;
import qz.printer.action.pdf.PDFWrapper;
import qz.printer.action.pdf.PdfParams;
import qz.utils.ConnectionUtilities;
import qz.utils.PrintingUtilities;
import qz.utils.SystemUtilities;
Expand All @@ -36,23 +38,17 @@
import java.awt.print.PrinterJob;
import java.io.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class PrintPDF extends PrintPixel implements PrintProcessor {

private static final Logger log = LogManager.getLogger(PrintPDF.class);

private List<PDDocument> originals;
private List<PDDocument> printables;
private Splitter splitter = new Splitter();
private final List<PDDocument> originals;
private final List<PDDocument> printables;
private final Splitter splitter = new Splitter();

private double docWidth = 0;
private double docHeight = 0;
private boolean ignoreTransparency = false;
private boolean altFontRendering = false;
private PdfParams pdfParams;


public PrintPDF() {
Expand All @@ -68,54 +64,24 @@ public PrintingUtilities.Format getFormat() {
@Override
public void parseData(JSONArray printData, PrintOptions options) throws JSONException, UnsupportedOperationException {
PrintOptions.Pixel pxlOpts = options.getPixelOptions();
double convert = 72.0 / pxlOpts.getUnits().as1Inch();
RenderingHints renderingHints = new RenderingHints(buildRenderingHints(pxlOpts.getDithering(), pxlOpts.getInterpolation()));

for(int i = 0; i < printData.length(); i++) {
JSONObject data = printData.getJSONObject(i);
HashSet<Integer> pagesToPrint = new HashSet<>();

if (!data.isNull("options")) {
JSONObject dataOpt = data.getJSONObject("options");

if (!dataOpt.isNull("pageWidth") && dataOpt.optDouble("pageWidth") > 0) {
docWidth = dataOpt.optDouble("pageWidth") * convert;
}
if (!dataOpt.isNull("pageHeight") && dataOpt.optDouble("pageHeight") > 0) {
docHeight = dataOpt.optDouble("pageHeight") * convert;
}

ignoreTransparency = dataOpt.optBoolean("ignoreTransparency", false);
altFontRendering = dataOpt.optBoolean("altFontRendering", false);

if (!dataOpt.isNull("pageRanges")) {
String[] ranges = dataOpt.optString("pageRanges", "").split(",");
for(String range : ranges) {
range = range.trim();
if(range.isEmpty()) {
continue;
}
String[] period = range.split("-");

try {
int start = Integer.parseInt(period[0]);
pagesToPrint.add(start);

if (period.length > 1) {
int end = Integer.parseInt(period[period.length - 1]);
pagesToPrint.addAll(IntStream.rangeClosed(start, end).boxed().collect(Collectors.toSet()));
}
}
catch(NumberFormatException nfe) {
log.warn("Unable to parse page range {}.", range);
}
}
}
}
pdfParams = new PdfParams(data.optJSONObject("options"), options, renderingHints);

PrintingUtilities.Flavor flavor = PrintingUtilities.Flavor.parse(data, PrintingUtilities.Flavor.FILE);

try {
PDDocument doc;

if (options.getPixelOptions().isStream()) {
doc = new FuturePdf(data.getString("data"));
printables.add(doc);
continue; //no further doc processing, as it doesn't exist yet
}

switch(flavor) {
case PLAIN:
// There's really no such thing as a 'PLAIN' PDF, assume it's a URL
Expand All @@ -135,29 +101,19 @@ public void parseData(JSONArray printData, PrintOptions options) throws JSONExce
}

if (pxlOpts.getBounds() != null) {
PrintOptions.Bounds bnd = pxlOpts.getBounds();

for(PDPage page : doc.getPages()) {
PDRectangle box = new PDRectangle(
(float)(bnd.getX() * convert),
page.getMediaBox().getUpperRightY() - (float)((bnd.getHeight() + bnd.getY()) * convert),
(float)(bnd.getWidth() * convert),
(float)(bnd.getHeight() * convert));
page.setMediaBox(box);
page.setMediaBox(pdfParams.calculateMediaBox(page));
}
}

if (pagesToPrint.isEmpty()) {
pagesToPrint.addAll(IntStream.rangeClosed(1, doc.getNumberOfPages()).boxed().collect(Collectors.toSet()));
}

pdfParams.setPageRange(doc);
originals.add(doc);

List<PDDocument> splitPages = splitter.split(doc);
originals.addAll(splitPages); //ensures non-ranged page will still get closed

for(int pg = 0; pg < splitPages.size(); pg++) {
if (pagesToPrint.contains(pg + 1)) { //ranges are 1-indexed
if (pdfParams.getPageRange().contains(pg + 1)) { //ranges are 1-indexed
printables.add(splitPages.get(pg));
}
}
Expand Down Expand Up @@ -194,7 +150,6 @@ public void print(PrintOutput output, PrintOptions options) throws PrinterExcept
job.setPrintService(output.getPrintService());

PrintOptions.Pixel pxlOpts = options.getPixelOptions();
Scaling scale = (pxlOpts.isScaleContent()? Scaling.SCALE_TO_FIT:Scaling.ACTUAL_SIZE);

PrintRequestAttributeSet attributes = applyDefaultSettings(pxlOpts, job.getPageFormat(null), (Media[])output.getPrintService().getSupportedAttributeValues(Media.class, null, null));

Expand All @@ -204,36 +159,33 @@ public void print(PrintOutput output, PrintOptions options) throws PrinterExcept
attributes.clear();
}

RenderingHints hints = new RenderingHints(buildRenderingHints(pxlOpts.getDithering(), pxlOpts.getInterpolation()));
double useDensity = pxlOpts.getDensity();

if (!pxlOpts.isRasterize()) {
if (pxlOpts.getDensity() > 0) {
// clear density for vector prints (applied via print attributes instead)
useDensity = 0;
} else if (SystemUtilities.isMac() && Constants.JAVA_VERSION.compareWithBuildsTo(Version.valueOf("1.8.0+121")) < 0) {
log.warn("OSX systems cannot print vector PDF's, forcing raster to prevent crash.");
useDensity = options.getDefaultOptions().getDensity();
}
}

BookBundle bundle = new BookBundle();

for(PDDocument doc : printables) {
PageFormat page = job.getPageFormat(null);
applyDefaultSettings(pxlOpts, page, output.getSupportedMedia());

//trick pdfbox into an alternate doc size if specified
if (docWidth > 0 || docHeight > 0) {
Paper paper = page.getPaper();
if (doc instanceof FuturePdf) {
FuturePdf future = (FuturePdf)doc;
future.buildFutureWrapper(pdfParams);

if (docWidth <= 0) { docWidth = page.getImageableWidth(); }
if (docHeight <= 0) { docHeight = page.getImageableHeight(); }
bundle.flagForStreaming(true);
//fixme - book bundle short-circuits based on total pages, how to bypass ?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commenting to add to the PR's TODO listing.

bundle.append(future.getFutureWrapper(), page, 5);

paper.setImageableArea(paper.getImageableX(), paper.getImageableY(), docWidth, docHeight);
continue; //no further doc processing, as it doesn't exist yet
}

//trick pdfbox into an alternate doc size if specified
if (pdfParams.isCustomSize()) {
Paper paper = page.getPaper();
paper.setImageableArea(paper.getImageableX(),
paper.getImageableY(),
pdfParams.getDocWidth(page.getImageableWidth()),
pdfParams.getDocHeight(page.getImageableHeight()));
page.setPaper(paper);

scale = Scaling.SCALE_TO_FIT; //to get custom size we need to force scaling
pdfParams.setScaling(Scaling.SCALE_TO_FIT); //to get custom size we need to force scaling

//pdf uses imageable area from Paper, so this can be safely removed
attributes.remove(MediaPrintableArea.class);
Expand Down Expand Up @@ -263,10 +215,7 @@ public void print(PrintOutput output, PrintOptions options) throws PrinterExcept
}
}

PDFWrapper wrapper = new PDFWrapper(doc, scale, false, ignoreTransparency, altFontRendering,
(float)(useDensity * pxlOpts.getUnits().as1Inch()),
false, pxlOpts.getOrientation(), hints);

PDFWrapper wrapper = new PDFWrapper(doc, pdfParams);
bundle.append(wrapper, page, doc.getNumberOfPages());
}

Expand Down Expand Up @@ -327,10 +276,5 @@ public void cleanup() {

originals.clear();
printables.clear();

docWidth = 0;
docHeight = 0;
ignoreTransparency = false;
altFontRendering = false;
}
}
43 changes: 36 additions & 7 deletions src/qz/printer/action/pdf/BookBundle.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,27 @@ public class BookBundle extends Book {

private static final Logger log = LogManager.getLogger(BookBundle.class);

private boolean streaming = false;
private int streamAt = 0;

private Printable lastPrint;
private int lastStarted;

public BookBundle() {
super();
}

public void flagForStreaming(boolean streaming) {
this.streaming = streaming;
}

/**
* Wrapper of the wrapper class so that PrinterJob implementations will handle it as proper pageable
*/
public Book wrapAndPresent() {
Book cover = new Book();
for(int i = 0; i < getNumberOfPages(); i++) {
cover.append(new PrintingPress(), getPageFormat(i));
cover.append(new PrintingPress(streaming), getPageFormat(i));
}

return cover;
Expand All @@ -39,31 +46,53 @@ public Book wrapAndPresent() {
public Book wrapAndPresent(int offset, int length) {
Book coverSubset = new Book();
for(int i = offset; i < offset + length && i < getNumberOfPages(); i++) {
coverSubset.append(new PrintingPress(offset), getPageFormat(i));
coverSubset.append(new PrintingPress(offset, streaming), getPageFormat(i));
}

return coverSubset;
}


/** Printable wrapper to ensure proper reading of multiple documents across spooling */
private class PrintingPress implements Printable {

private boolean isStream;
private int pageOffset;

public PrintingPress() {
this(0);
public PrintingPress(boolean stream) {
this(0, stream);
}

public PrintingPress(int offset) {
public PrintingPress(int offset, boolean stream) {
pageOffset = offset;
isStream = stream;
}

@Override
public int print(Graphics g, PageFormat format, int pageIndex) throws PrinterException {
pageIndex += pageOffset;
log.trace("Requested page {} for printing", pageIndex);

if (pageIndex < getNumberOfPages()) {
if (isStream) {
Printable printable = getPrintable(streamAt);
if (printable != lastPrint) {
lastPrint = printable;
lastStarted = pageIndex;
}

//fixme - this setup results in too many blank pages after a no_such_page
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commenting to add to the PR's TODO listing.

int result = printable.print(g, format, pageIndex - lastStarted);
if (result == NO_SUCH_PAGE) {
// finished the last page of this document, move to the next
streamAt++;
((FuturePdf.FutureWrapper)printable).sendToPast();
}
if (streamAt < getNumberOfPages()) {
// always return "exists" if there are more documents to print
return PAGE_EXISTS;
}

return result;
} else if (pageIndex < getNumberOfPages()) {
Printable printable = getPrintable(pageIndex);
if (printable != lastPrint) {
lastPrint = printable;
Expand Down
Loading