alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.openide.text;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Font;
import java.awt.Dialog;
import java.awt.print.Printable;
import java.awt.print.PageFormat;
import java.awt.print.PrinterGraphics;
import java.awt.print.PrinterException;
import java.awt.print.PrinterAbortException;
import java.awt.print.PrinterJob;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextLayout;
import java.awt.font.FontRenderContext;
import java.text.AttributedCharacterIterator;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;

import javax.swing.text.Document;
import javax.swing.text.BadLocationException;

import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.ErrorManager;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;

/** The class creates from an instance of AttributedCharacterIterator
 * a java.awt.print.Pageable object.
 *
 * @author Ales Novak
 */
final class DefaultPrintable extends Object implements Printable {

    /**
     * for each item is created new LineBreakMeasurer
     */
    private AttributedCharacterIterator[] styledTexts;
    /** expected page */
//    private int pageno;
    /** Start page */
    private int startPage = -1;
    
    /** created text layouts */
    private ArrayList textLayouts;
    /** page indices to textLayouts list */
    private int[] pageIndices;
    /** pageIndices size */
    private int pageIndicesSize;
    /** iterator over textLayouts */
    private int currentLayout;
    /** curent styledText entry */
    private int currentStyledText;
    /** current LineBreakMeasurer */
    private LineBreakMeasurer lineBreakMeasurer;
    /** maximal page */
    private int maxPage;

    /** text layouts that starts new line (those that were not created thanks to wrapping) */
    private ArrayList startLayouts;
    /** page to line indexes (page 5 starts with line 112) */
    private int[] lineIndices; // pageIndicesSize

    /** Number of args. */
    private static final int ARG_SIZE = 3;
    /** Arguments for page. */
    private Object[] pageArgs;
    /** Header of each page. */
    private MessageFormat header;
    /** Should be header printed? */
    private boolean printHeader;
    /** Bottom of each page. */
    private MessageFormat footer;
    /** Should be footer printed? */
    private boolean printFooter;
    /** Options */
    private static PrintSettings printSettings;
    /** CancellationPanel */
    private CancellationPanel cancellationPanel;
    /** Dialog */
    private Dialog cancellationDialog;

    /**
    * @param attrs an AttributedCharacterIterator
    * @param filename
    */
    private DefaultPrintable(AttributedCharacterIterator[] iter, String filename) {

        if ((iter == null) ||
                (iter.length == 0)) {
            throw new IllegalArgumentException();
        }

        if (printSettings == null) {
            printSettings = (PrintSettings) PrintSettings.findObject(PrintSettings.class, true);
        }

        // bugfix for sun.awt.Bidi line 250
        replaceEmptyIterators(iter);

        styledTexts = iter;
//        pageno = 0;
        textLayouts = new ArrayList(100); // 100 lines
        pageIndices = new int[50];
        pageIndicesSize = 0;
        currentLayout = 0;
        currentStyledText = 0;
        lineBreakMeasurer = null;
        maxPage = Integer.MAX_VALUE;

        startLayouts = new ArrayList(10); // 10 lines
        lineIndices = new int[pageIndices.length];

        pageArgs = new Object[ARG_SIZE];
        pageArgs[2] = filename;
        pageArgs[1] = new Date(System.currentTimeMillis());

        header = new MessageFormat(getHeaderFormat());
        printHeader = !getHeaderFormat().equals(""); // NOI18N
        footer = new MessageFormat(getFooterFormat());
        printFooter = !getFooterFormat().equals(""); // NOI18N
    }

    /**
    * @param doc printed document
    */
    public DefaultPrintable(Document doc) {
        this(getIterators(doc), getFilename(doc));
    }

    /**
     * Prints a page.
     *
     * @param g a Graphics
     * @param pf a PageFormat
     * @param pageNo Which page?
     */
    public int print(Graphics g, PageFormat pf, int pageNo) throws PrinterException {
        boolean processDummy = false;
        
        if(startPage == -1) {
            processDummy = true;
            startPage = pageNo;
        }
        
        if(processDummy) {
            for(int i = 0; i < startPage; i++) {
                // XXX #21245 Processes dummy pages (first pages to not print).
                // PENDING shuold be made better+faster way to skip
                // processing of such pages (then this - hot fix).
                printImpl(g, pf, i, false);
            }
        }
        
        return printImpl(g, pf, pageNo, true);
    }
        
    private int printImpl(
        Graphics g,
        PageFormat pf,
        int pageNo,
        boolean print
    ) throws PrinterException {
        if (pageNo > maxPage) {
            closeDialog();
            return Printable.NO_SUCH_PAGE;
        } else if(pageNo < 0){
            closeDialog();
            throw new IllegalArgumentException("Illegal page number="+pageNo); // NOI18N
        }

        // stop if cancelled
        if ((g instanceof PrinterGraphics) && isCancelled(((PrinterGraphics) g).getPrinterJob())) {
            closeDialog();
            throw new PrinterAbortException();
        }

        if ((cancellationPanel == null) && (g instanceof PrinterGraphics)) {
            // [TODO] - commented out since the awt API does not allow proper handling
            // of the dialog - e.g when am I to close it?

            PrinterJob pJob = ((PrinterGraphics) g).getPrinterJob();
            createCancellationPanel(pJob);
        }
        
        if(cancellationPanel != null) {
            int pageNumber = (print ? pageNo : startPage);
            cancellationPanel.setPageno(pageNumber);
            packDialog();
        }

        // line numbers init
        int startLine = 0;
        int correction = 3;  // magic - take pencil, paper, and start measuring.
//        if (lineNumbers()) {  // not used
            /*
            lineNo = Integer.toString(startLine = page2Line(pageNo));
            // correction may not be ok
            if (startLine < 100) {
              lineNo = lineNo + "   ";
        } else {
              lineNo = lineNo + " ";
        }
            correction = g.getFontMetrics().stringWidth(lineNo);
            */
//        }
        g.setColor(Color.black);

        final Graphics2D graphics = (Graphics2D) g;
        final Point2D.Float pen = new Point2D.Float(getImageableXPatch(pf),
                                  getImageableYPatch(pf)
                                                   );

        /*    System.out.println("PEN IS: " + pen);
            pen = new Point2D.Float((float) pf.getImageableWidth(),
                                    (float) pf.getImageableHeight()
                                   );
            System.out.println("END IS: " + pen);
            Paper paper = pf.getPaper();
            System.out.println("DEF IS: " + paper.getHeight() + ", " + paper.getWidth());
        */  

        // header & footer init
        pageArgs[0] = new Integer(pageNo + 1); // pages numbered from 1

        float pageBreakCorrection = 0.0F;
        TextLayout headerString = null;
        TextLayout footerString = null;
        
        if(printHeader) {
            headerString = new TextLayout(header.format(pageArgs),
                                      getHeaderFont(),
                                      graphics.getFontRenderContext());
            
            pageBreakCorrection += headerString.getAscent() 
                                    + (headerString.getDescent()
                                    + headerString.getLeading()) * 2;
        }
        
        if(printFooter) {
            footerString = new TextLayout(footer.format(pageArgs),
                                      getFooterFont(),
                                      graphics.getFontRenderContext());

            pageBreakCorrection += footerString.getAscent() * 2
                                        + footerString.getDescent()
                                        + footerString.getLeading();
        }

        // for now suppose that getImageableWidthPatch(pf) is always
        // the same during the same print job
        final float wrappingWidth = (wrap() ? (float) pf.getImageableWidth() - correction :
                                     Float.MAX_VALUE);
        final float pageBreak = ((float) pf.getImageableHeight()) + ((float)pf.getImageableY()) - pageBreakCorrection;
        final FontRenderContext frCtx = graphics.getFontRenderContext();

        boolean pageExists = false;

        // page rendering
        for (TextLayout layout = layoutForPage(pageNo, wrappingWidth, frCtx);
                (pen.y < pageBreak);
                layout = nextLayout(wrappingWidth, frCtx)
            ) {
            if (layout == null) {
                maxPage = pageNo;
                break;
            }
            if (!pageExists) {
                // draw header
                if (printHeader && headerString != null) {
                    pen.y += headerString.getAscent();
                    float center = computeStart(headerString.getBounds(), (float) pf.getImageableWidth(), getHeaderAlignment());
                    float dx = (headerString.isLeftToRight() ? center: wrappingWidth - headerString.getAdvance() - center);
                    if(print) {
                        headerString.draw(graphics, pen.x + dx, pen.y);
                    }
                    pen.y += (headerString.getDescent() + headerString.getLeading()) * 2;
                }
                pageExists = true;
            }
            pen.y += layout.getAscent() * getLineAscentCorrection();
            // line number handling
//            if (lineNumbers() && isNewline(layout, startLine)) {
                /*
                lineNo = Integer.toString(++startLine); // + 1 -> lines starts from 1

                if (startLine < 100) {
                  lineNo = lineNo + "   ";
            } else {
                  lineNo = lineNo + " ";
            }

                // graphics.drawString(lineNo, (int) pen.x, (int) pen.y);
                TextLayout tl = new TextLayout(lineNo, lineNumbersFont(), graphics.getFontRenderContext());
                tl.draw(graphics, pen.x, pen.y);
                */
//            }
            float dx = (layout.isLeftToRight() ? 0 : wrappingWidth - layout.getAdvance());
            if(print) {
                layout.draw(graphics, correction + pen.x + dx, pen.y);
            }
            pen.y += (layout.getDescent() + layout.getLeading()) * getLineAscentCorrection();
        }

        // draw footer
        if (printFooter && pageExists && footerString != null) {
            pen.y = pageBreak;
            pen.y += footerString.getAscent() * 2;
            float center = computeStart(footerString.getBounds(), (float) pf.getImageableWidth(), getFooterAlignment());
            float dx = (footerString.isLeftToRight() ? 0 + center : wrappingWidth - footerString.getAdvance() - center);
            if(print) {
                footerString.draw(graphics, pen.x + dx, pen.y);
            }
        }

        // stop if cancelled
        if ((g instanceof PrinterGraphics) && isCancelled(((PrinterGraphics) g).getPrinterJob())) {
            closeDialog();
            throw new PrinterAbortException();
        }

        // at least one layout draw?
        if (!pageExists) {
            closeDialog();
            return Printable.NO_SUCH_PAGE;
        } else {
            return Printable.PAGE_EXISTS;
        }
    }

    /* for following two methods:
    * windows page setup dialog behaves incorrectly for LANDSCAPE format
    * the x coordinate is influenced by RIGHT margin instead of LEFT margin
    * the y coordinate ... BOTTOM instead of TOP
    * #3732
    */  

    /** Patch for a bug in the PageFormat class
    * @param pf PageFormat
    * @return imageable x coordination for this page format
    */
    private float getImageableXPatch(PageFormat pf) {
        if (pf.getOrientation() == PageFormat.LANDSCAPE) {
            double ret = pf.getPaper().getHeight() - (pf.getImageableX() + pf.getImageableWidth());
            return (float) Math.round(ret);
        } else {
            return (float) pf.getImageableX();
        }
    }

    /** Patch for a bug in the PageFormat class
    * @param pf PageFormat
    * @return imageable y coordination for this page format
    */
    private float getImageableYPatch(PageFormat pf) {
        if (pf.getOrientation() == PageFormat.LANDSCAPE) {
            double ret = pf.getPaper().getWidth() - (pf.getImageableY() + pf.getImageableHeight());
            return (float) Math.round(ret);
        } else {
            return (float) pf.getImageableY();
        }
    }

    /** Translates given page number to line number.
    * @param pageNo
    * @return number of first line on the page
    * /
    private int page2Line(int pageNo) {
        if (pageNo == 0) {
            return 0;
        } else {
            return (pageNo == pageIndicesSize ? Math.max(startLayouts.size() - 1, 0) : lineIndices[pageNo]);
        }
    }
     */
    
    /**
    * @param tl a TextLayout
    * @param currentLine
    * @return true iff tl is a TextLayout that does not represent wrapped line
    */
    private boolean isNewline(TextLayout tl, int currentLine) {
        if (currentLine >= startLayouts.size()) {
            return false; // wrapping appeared
        } else {
            return startLayouts.get(currentLine) == tl;
        }
    }

    /** Computes alignment for a TextLayout with given bounds on the page with given width
    * and for given alignment policy.
    *
    * @param rect Bounds of a TextLayout
    * @param width page width
    * @param alignment one of @see PageSettings#LEFT @see PageSettings#CENTER @see PageSettings#RIGHT
    */
    private static float computeStart(Rectangle2D rect, float width, int alignment) {
        float x;
        if (rect instanceof Rectangle2D.Float) {
            x = ((Rectangle2D.Float) rect).width;
        } else {
            x = (float) ((Rectangle2D.Double) rect).width;
        }

        if (x >= width) {
            return 0;
        }

        switch (alignment) {
        case PrintSettings.LEFT : return 0;
        case PrintSettings.RIGHT : return (width - x);
        default: return (width - x) / 2;
        }
    }


    /**
    * @param wrappingWidth width of the layout
    * @param frCtx for possible new instance of LineBreakMeasurer
    * @return next TextLayout that is to be rendered
    */
    private TextLayout nextLayout(float wrappingWidth, FontRenderContext frc) {
        TextLayout l;

        if (currentLayout == textLayouts.size()) {
            LineBreakMeasurer old = lineBreakMeasurer;
            LineBreakMeasurer measurer = getMeasurer(frc);
            if (measurer == null) {
                return null;
            }
            l = measurer.nextLayout(wrappingWidth);
            textLayouts.add(l);

            if (old != measurer) { // new line
                startLayouts.add(l);
            }

        } else {
            l = (TextLayout) textLayouts.get(currentLayout);
        }
        currentLayout++; // advance to next
        return l;
    }

    /** Sets @see #currentLayout variable then calls nextLayout.
    * @param pageNo searched page
    * @param wrappingWidth width of the layout
    * @param frCtx for possible new instance of LineBreakMeasurer
    * @return next TextLayout that is to be rendered
    */
    private TextLayout layoutForPage(int pageNo, float wrappingWidth, FontRenderContext frc) {
        if (pageNo > pageIndicesSize + 1) {
            throw new IllegalArgumentException("Page number " + pageNo // NOI18N
            +" is bigger than array size " + (pageIndicesSize + 1)); // NOI18N
        }

        // first request for a page  // pageNo==3 -> fourth page to print
        if (pageNo == pageIndicesSize) {
            // small array?
            if (pageIndicesSize >= pageIndices.length) {
                pageIndices = increaseArray(pageIndices);
                lineIndices = increaseArray(lineIndices);
            }

            // layouts for given page starts at:
            pageIndices[pageIndicesSize] = Math.max(textLayouts.size() - 1, 0);
            // remember - in the for loop above last layout is not printed
            // if page breaks

            lineIndices[pageIndicesSize++] = Math.max(startLayouts.size() - 1, 0);
        }
        currentLayout = pageIndices[pageNo];  // set the iterator
        return nextLayout(wrappingWidth, frc); // iterate
    }

    /** Called only if new TextLayouts are in need.
    * @param frc is used for possible new LineBreakMeasurer instance
    * @return current LineBreakMeasurer or null if no is available.
    */
    private LineBreakMeasurer getMeasurer(FontRenderContext frc) {
        if (lineBreakMeasurer == null) { // first page to print
            lineBreakMeasurer = new LineBreakMeasurer(styledTexts[currentStyledText],
                                frc);
            // no layouts available in this measurer?
        } else if (lineBreakMeasurer.getPosition() >= styledTexts[currentStyledText].getEndIndex()) {
            // next measurer is not available?
            if (currentStyledText == styledTexts.length - 1) {
                return null; // everything is printed
            } else { // use next styledTexts entry
                lineBreakMeasurer = new LineBreakMeasurer(styledTexts[++currentStyledText],
                                    frc);
            }
        }
        return lineBreakMeasurer;
    }

    // ------------------ options -----------------

    /** @return true iff wrapping is on*/
    private static boolean wrap() {
        return printSettings.getWrap();
    }
    /** @return String describing header */
    private static String getHeaderFormat() {
        return printSettings.getHeaderFormat();
    }
    /** @return String describing footer */
    private static String getFooterFormat() {
        return printSettings.getFooterFormat();
    }
    /** @return font for header */
    private static Font getHeaderFont() {
        return printSettings.getHeaderFont();
    }
    /** @return font for footer */
    private static Font getFooterFont() {
        return printSettings.getFooterFont();
    }
    /** @return an alignment constant for footer */
    private static int getFooterAlignment() {
        return printSettings.getFooterAlignment();
    }
    /** @return an alignment constant for header */
    private static int getHeaderAlignment() {
        return printSettings.getHeaderAlignment();
    }
    /** @return a line ascent correction */
    private static float getLineAscentCorrection() {
        return printSettings.getLineAscentCorrection();
    }

    /** @return false */
    private static boolean lineNumbers() {
        return false;
    }

    // not used
    private static Font lineNumbersFont() {
        return new Font("Courier", java.awt.Font.PLAIN, 6); // NOI18N
    }

    // ----------------- options end --------------

    /** Creates new AttributedCharacterIterator for plain text.
     *
     * @return an AttributedCharacterIterator
     */
    private static AttributedCharacterIterator[] getIterators(Document doc) {

        if (doc instanceof NbDocument.Printable) {
            return ((NbDocument.Printable) doc).createPrintIterators();
        }

        // load options
        java.awt.Font f = new java.awt.Font("Courier", java.awt.Font.PLAIN, 8); // NOI18N

        AttributedCharacters achs = null;
        char[] chars = null;
        ArrayList iterators = new ArrayList(300);
        try {
            String document = doc.getText(0, doc.getLength());
            // now chars are filled from the document

            int firstCharInDoc = 0;
            for (int i = 0; i < document.length(); i++) { // search for new lines
                if (document.charAt(i) == '\n') {
                    chars = new char[i - firstCharInDoc + 1];
                    document.getChars(firstCharInDoc, chars.length + firstCharInDoc, chars, 0);
                    achs = new AttributedCharacters();
                    achs.append(chars, f, Color.black);
                    iterators.add(achs.iterator()); // new iterator for new line
                    firstCharInDoc = i + 1;
                }
            }
        } catch (BadLocationException e){
            ErrorManager.getDefault().notify(e);
        }
        AttributedCharacterIterator[] iters = new AttributedCharacterIterator[iterators.size()];
        iterators.toArray(iters);
        return iters;
    }

    /**
    * @param doc
    * @return filename from which the document is loaded.
    */
    private static String getFilename(Document doc) {
        String ret = (String) doc.getProperty(javax.swing.text.Document.TitleProperty);
        return (ret == null ? "UNKNOWN" : ret); // NOI18N
    }

    /** Doubles given array. The old one is then copied into the new one.
    * @return new int array
    */
    private static int[] increaseArray(int[] old) {
        int[] ret = new int[2 * old.length];
        System.arraycopy(old, 0, ret, 0, old.length);
        return ret;
    }

    // ------------------ cancellation dialog ---------------------------

    /** Creates cancellation dialog.
    * @param job PrinterJob
    */
    private void createCancellationPanel(final PrinterJob job) {
        cancellationPanel= new CancellationPanel(job);
        DialogDescriptor ddesc = new DialogDescriptor(
                                     cancellationPanel,
                                     NbBundle.getMessage(PrintSettings.class, "CTL_Print_cancellation"),
                                     false,
                                     new Object[] {NbBundle.getMessage(PrintSettings.class, "CTL_Cancel")},
                                     NbBundle.getMessage(PrintSettings.class, "CTL_Cancel"),
                                     DialogDescriptor.BOTTOM_ALIGN,
                                     null,
                                     new java.awt.event.ActionListener() {
                                         public void actionPerformed(java.awt.event.ActionEvent ev) {
                                             setCancelled(job);
                                             closeDialog();
                                         }
                                     }
                                 );
        setDialog(DialogDisplayer.getDefault().createDialog(ddesc));
    }

    /** Closes cancellationDialog */
    void closeDialog() {
        if (cancellationDialog != null) {
            cancellationDialog.setVisible(false);
            cancellationDialog.dispose();
        }
    }
    /** @param d New value of cancellationDialog*/
    void setDialog(Dialog d) {
        d.show();
        d.pack();
        cancellationDialog = d;
    }
    /** packs the dialog */
    void packDialog() {
        if (cancellationDialog != null) {
            cancellationDialog.pack();
        }
    }
    /** Marks this job as cancelled.
    * @param job
    */
    void setCancelled(PrinterJob job) {
        job.cancel();
    }
    /** @return true iff the job was cancelled */
    boolean isCancelled(PrinterJob job) {
        return job.isCancelled();
    }

    /** Replaces an empty iterator by an iterator conatining one space. */
    private static void replaceEmptyIterators(AttributedCharacterIterator[] iters) {
        for (int i = 0; i < iters.length; i++) {
            AttributedCharacterIterator achit = iters[i];
            if (achit.getBeginIndex() == achit.getEndIndex()) {
                AttributedCharacters at = new AttributedCharacters();
                at.append(' ', getFontInstance(), Color.white);
                iters[i] = at.iterator();
            }
        }
    }

    /** CancellationPanel class allows user to cancel current PrinterJob */
    static final class CancellationPanel extends javax.swing.JPanel {

        /** Label indicating progress. */
        private final javax.swing.JLabel printProgress;
        /** Format of displayed text. */
        private final MessageFormat format;

        /** Parameters for format. */
        private final Object[] msgParams;


        static final long serialVersionUID =-6419253408585188541L;
        /**
        * @param job PrinterJob
        * @exception IllegalArgumentException is thrown if job is null.
        */
        public CancellationPanel(PrinterJob job) {

            if (job == null) {
                throw new IllegalArgumentException();
            }

            format = new MessageFormat(NbBundle.getMessage(PrintSettings.class, "CTL_Print_progress"));
            msgParams = new Object[1];

            setLayout(new java.awt.BorderLayout());
            setBorder(new javax.swing.border.EmptyBorder(12, 12, 0, 12));
            printProgress = new javax.swing.JLabel();
            printProgress.setHorizontalAlignment(javax.swing.JLabel.CENTER);
            add(printProgress);
        }

        /** Advances progress.
        * @param pageno Page number that was printed.
        */
        public void setPageno(int pageno) {
            msgParams[0] = new Integer(pageno + 1);
            printProgress.setText(format.format(msgParams));
            getAccessibleContext().setAccessibleDescription(printProgress.getText());
        }
    }

    /** Font for CancellationDialog */
    private static Font fontInstance;

    /** @return cached font instance */
    static Font getFontInstance() {
        if (fontInstance == null) {
            fontInstance = new java.awt.Font("Dialog", java.awt.Font.PLAIN, 14); // NOI18N
        }
        return fontInstance;
    }
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.