Search code examples
javaswingprintingjavafxjava-print

Java PrintJob to DocPrintJob Not Working?


I have a class that extends from Printable, it prints fine using the standard PrintJob, but i would like to move to DocPrintJob so i can listen to the status of the print (successful print etc).

My current code looks like this to create a print job and print

// define printer
AttributeSet attributes = new HashAttributeSet();
attributes.add(new Copies(1));

// get printerJob
PrinterJob printJob = PrinterJob.getPrinterJob();
PageFormat pageFormat = printJob.defaultPage();

// sizing (standard is 72dpi, so multiple inches by this)
Double height = 8d * 72d;
Double width = 3d * 72d;
Double margin = 0.1d * 72d;

// set page size
Paper paper = pageFormat.getPaper();

paper.setSize(width, height);
paper.setImageableArea(margin, margin, width - (margin * 2), height - (margin * 2));

// set orientation and paper
pageFormat.setOrientation(PageFormat.PORTRAIT);
pageFormat.setPaper(paper);

// create a book for printing
Book book = new Book();
book.append(new EventPrint(args.getEvent()), pageFormat);

// set book to what we are printing
printJob.setPageable(book);

// set print request attributes
PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
aset.add(OrientationRequested.PORTRAIT);

// now print
try {
    printJob.print();
} catch (PrinterException e) {
    e.printStackTrace();
}   

Now to convert this to a DocPrintJob, i followed this link which told me how to convert from PrintJob to DocPrintJob. So now my code became this

PrintService[] services = PrinterJob.lookupPrintServices(); //list of printers
PrintService printService = services[0];
DocFlavor[] flavours = printService.getSupportedDocFlavors();

// may need to determine which printer to use
DocPrintJob printJob = printService.createPrintJob();

// get out printable
EventPrint eventPrint = new EventPrint(args.getEvent());

// convert printable to doc
Doc doc = new SimpleDoc(eventPrint, DocFlavor.SERVICE_FORMATTED.PRINTABLE, null);

// add eventlistener to printJob
printJob.addPrintJobListener(new PrintJobListener() {

    @Override
    public void printDataTransferCompleted(PrintJobEvent pje) {
        Console.Log("Print data sent successfully");
    }

    @Override
    public void printJobCompleted(PrintJobEvent pje) {
        Console.Log("Print successful");
    }

    @Override
    public void printJobFailed(PrintJobEvent pje) {
        Console.Log("Print failed");
    }

    @Override
    public void printJobCanceled(PrintJobEvent pje) {
        Console.Log("Print cancelled");                     
    }

    @Override
    public void printJobNoMoreEvents(PrintJobEvent pje) {
        Console.Log("No more printJob events");
    }

    @Override
    public void printJobRequiresAttention(PrintJobEvent pje) {
        Console.Log("printJob requires attention");                     
    }

});

PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
aset.add(new Copies(1));

try {
    printJob.print(doc, aset);
} catch (Exception e) {

}

Now for some reason, my print just keeps executing (400+ times until i close application). I am not sure if it is because maybe i have not set the paper size, like i did when i was using PrintJob? Would that cause it? If so, how can i set the paperSize of the DocPrintJob as it doesnt have the methods the normal printJob has?

Anyone else faces this problem before?

EDIT: Here is my eventPrint class and the class it inherits from

PRINTABLEBASE.JAVA

public class PrintableBase {

    public Graphics2D g2d;
    public float x, y, imageHeight, imageWidth, maxY;
    public enum Alignment { LEFT, RIGHT, CENTER };

    public void printSetup(Graphics graphics, PageFormat pageFormat) {
        // user (0,0) is typically outside the imageable area, so we must translate
        // by the X and Y values in the pageFormat to avoid clipping
        g2d = (Graphics2D) graphics;
        g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());

        // get starter X and Y
        x = 0;//(float)pageFormat.getImageableX();

        // do not offset vertical as pushes ticket down too much
        y = 0;//(float)pageFormat.getImageableY();

        // get height and width of the printable image
        imageWidth = (float)pageFormat.getImageableWidth();
        imageHeight = (float)pageFormat.getImageableHeight();

        // maximum that we can go vertically
        maxY = y;

        Console.Log("imageWidth:" + imageWidth);
        Console.Log("imageHeight: " + imageHeight);
        Console.Log("X: " + x);
        Console.Log("Y: " + y);
    }

    public void setFont(Font font) {
        g2d.setFont(font);
    }

    public float addSeparatorLine(float y, float imageWidth) {
        String fontName = g2d.getFont().getName();
        int fontStyle = g2d.getFont().getStyle();
        int fontSize = g2d.getFont().getSize();

        g2d.setFont(new Font("Arial", Font.PLAIN, 10));

        y = drawFirstLine(g2d, "---------------------------------------------------------------------------------------", imageWidth, 0, y, Alignment.LEFT);

        // revery font
        g2d.setFont(new Font(fontName, fontStyle, fontSize));

        return y + 5;
    }

    public BufferedImage scaleImage(BufferedImage sbi, int dWidth, int dHeight) {
        BufferedImage dbi = null;
        if(sbi != null) {
            // calculate ratio between standard size and scaled
            double wRatio = (double)dWidth / (double)sbi.getWidth();
            double hRatio = (double)dHeight / (double)sbi.getHeight();

            // use wRatio by default
            double sWidth = (double)sbi.getWidth() * wRatio;
            double sHeight = (double)sbi.getHeight() * wRatio;

            // if hRation is small, use that, as image will be reduced by more
            if (hRatio < wRatio) {
                sWidth = (double)sbi.getWidth() * hRatio;
                sHeight = (double)sbi.getHeight() * hRatio;             
            }

            double sRatio = (wRatio < hRatio) ? wRatio : hRatio;

            // now create graphic, of new size
            dbi = new BufferedImage((int)sWidth, (int)sHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = dbi.createGraphics();

            AffineTransform at = AffineTransform.getScaleInstance(sRatio, sRatio);
            g.drawRenderedImage(sbi, at);
        }
        return dbi;
    }

    // This function will only add the first line of text and will not wrap
    // useful for adding the ticket title.  
    // Returns the height of the text
    public float drawFirstLine(Graphics2D g2, String text, float width, float x, float y, Alignment alignment) {
        AttributedString attstring = new AttributedString(text);
        attstring.addAttribute(TextAttribute.FONT, g2.getFont());
        AttributedCharacterIterator paragraph = attstring.getIterator();
        int paragraphStart = paragraph.getBeginIndex();
        int paragraphEnd = paragraph.getEndIndex();
        FontRenderContext frc = g2.getFontRenderContext();
        LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc);

        // Set break width to width of Component.
        float breakWidth = width;
        float drawPosY = y;
        // Set position to the index of the first character in the paragraph.
        lineMeasurer.setPosition(paragraphStart);

        // Get lines until the entire paragraph has been displayed.
        if (lineMeasurer.getPosition() < paragraphEnd) {
            // Retrieve next layout. A cleverer program would also cache
            // these layouts until the component is re-sized.
            TextLayout layout = lineMeasurer.nextLayout(breakWidth);
            // Compute pen x position. 
            float drawPosX;

            switch (alignment){         
                case RIGHT:
                    drawPosX = (float) x + breakWidth - layout.getAdvance();
                    break;
                case CENTER:
                    drawPosX = (float) x + (breakWidth - layout.getAdvance())/2;
                    break;
                default: 
                    drawPosX = (float) x;
            }
            // Move y-coordinate by the ascent of the layout.
            drawPosY += layout.getAscent();

            // Draw the TextLayout at (drawPosX, drawPosY).
            layout.draw(g2, drawPosX, drawPosY);

            // Move y-coordinate in preparation for next layout.
            drawPosY += layout.getDescent() + layout.getLeading();
        }
        return drawPosY;
    }

    /**
     * Draw paragraph.
     *
     * @param g2 Drawing graphic.
     * @param text String to draw.
     * @param width Paragraph's desired width.
     * @param x Start paragraph's X-Position.
     * @param y Start paragraph's Y-Position.
     * @param dir Paragraph's alignment.
     * @return Next line Y-position to write to.
     */
    protected float drawParagraph (String text, float width, float x, float y, Alignment alignment){
        AttributedString attstring = new AttributedString(text);
        attstring.addAttribute(TextAttribute.FONT, g2d.getFont());
        AttributedCharacterIterator paragraph = attstring.getIterator();
        int paragraphStart = paragraph.getBeginIndex();
        int paragraphEnd = paragraph.getEndIndex();
        FontRenderContext frc = g2d.getFontRenderContext();
        LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc);

        // Set break width to width of Component.
        float breakWidth = width;
        float drawPosY = y;
        // Set position to the index of the first character in the paragraph.
        lineMeasurer.setPosition(paragraphStart);

        // Get lines until the entire paragraph has been displayed.
        while (lineMeasurer.getPosition() < paragraphEnd) {
            // Retrieve next layout. A cleverer program would also cache
            // these layouts until the component is re-sized.
            TextLayout layout = lineMeasurer.nextLayout(breakWidth);
            // Compute pen x position. 
            float drawPosX;
            switch (alignment){         
                case RIGHT:
                    drawPosX = (float) x + breakWidth - layout.getAdvance();
                    break;
                case CENTER:
                    drawPosX = (float) x + (breakWidth - layout.getAdvance())/2;
                    break;
                default: 
                    drawPosX = (float) x;
            }
            // Move y-coordinate by the ascent of the layout.
            drawPosY += layout.getAscent();

            // Draw the TextLayout at (drawPosX, drawPosY).
            layout.draw(g2d, drawPosX, drawPosY);

            // Move y-coordinate in preparation for next layout.
            drawPosY += layout.getDescent() + layout.getLeading();
        }
        return drawPosY;
    }

}

EVENTPRINT.JAVA

public class EventPrint extends PrintableBase implements Printable {

    private Event event;

    public EventPrint(Event event) {
        this.event = event; 
    }

    @Override
    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
            throws PrinterException {

        // setup
        super.printSetup(graphics, pageFormat);

        // title
        super.setFont(new Font("Tahoma", Font.BOLD, 16));
        y = super.drawParagraph(event.getTitle(), imageWidth, x, y, Alignment.LEFT);

    RETURN PAGE_EXISTS;                 
}

Solution

  • I would say that your problem is that you never tell the API that there are no more pages....

    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
            throws PrinterException {
    
        // setup
        super.printSetup(graphics, pageFormat);
    
        // title
        super.setFont(new Font("Tahoma", Font.BOLD, 16));
        y = super.drawParagraph(event.getTitle(), imageWidth, x, y, Alignment.LEFT);
    
        RETURN PAGE_EXISTS;                 
    }
    

    So, assuming you only want to print a single page, you could use something like...

    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
            throws PrinterException {
        int result = NO_SUCH_PAGE;
        if (pageIndex == 0) {
    
            // setup
            super.printSetup(graphics, pageFormat);
    
            // title
            super.setFont(new Font("Tahoma", Font.BOLD, 16));
            y = super.drawParagraph(event.getTitle(), imageWidth, x, y, Alignment.LEFT);
            result = PAGE_EXISTS;
        }
    
        RETURN result;                 
    }
    

    Otherwise the API doesn't know when to stop printing.

    Bookable uses a different approach, in that each page of the book is only printed once and it will continue until all pages are printed. Printable is different, it will continue printing until you tell it to stop

    Take a look at A Basic Printing Program for more details