* This method is to be called on the EDT only. Layout should be done before * calling this method. */ private int printOnEDT(final Graphics graphics, final PageFormat pf, final int pageIndex) throws PrinterException { assert SwingUtilities.isEventDispatchThread(); Border border = BorderFactory.createEmptyBorder(); //handle header and footer if (headerFormat != null || footerFormat != null) { //Printable page enumeration is 0 base. We need 1 based. Object[] formatArg = new Object[]{Integer.valueOf(pageIndex + 1)}; if (headerFormat != null) { border = new TitledBorder(border, headerFormat.format(formatArg), TitledBorder.CENTER, TitledBorder.ABOVE_TOP, headerFont, printShell.getForeground()); } if (footerFormat != null) { border = new TitledBorder(border, footerFormat.format(formatArg), TitledBorder.CENTER, TitledBorder.BELOW_BOTTOM, footerFont, printShell.getForeground()); } } Insets borderInsets = border.getBorderInsets(printShell); updatePagesMetrics(pageIndex, (int)Math.floor(pf.getImageableHeight()) - borderInsets.top - borderInsets.bottom); if (pagesMetrics.size() <= pageIndex) { return NO_SUCH_PAGE; } Graphics2D g2d = (Graphics2D)graphics.create(); g2d.translate(pf.getImageableX(), pf.getImageableY()); border.paintBorder(printShell, g2d, 0, 0, (int)Math.floor(pf.getImageableWidth()), (int)Math.floor(pf.getImageableHeight())); g2d.translate(0, borderInsets.top); //want to clip only vertically Rectangle clip = new Rectangle(0, 0, (int) pf.getWidth(), pagesMetrics.get(pageIndex).end - pagesMetrics.get(pageIndex).start + 1); g2d.clip(clip); int xStart = 0; if (ComponentOrientation.RIGHT_TO_LEFT == printShell.getComponentOrientation()) { xStart = (int) pf.getImageableWidth() - printShell.getWidth(); } g2d.translate(xStart, - pagesMetrics.get(pageIndex).start); printShell.print(g2d); g2d.dispose(); return Printable.PAGE_EXISTS; } private boolean needReadLock = false; /** * Tries to release document's readlock * * Note: Not to be called on the EDT. */ private void releaseReadLock() { assert ! SwingUtilities.isEventDispatchThread(); Document document = textComponentToPrint.getDocument(); if (document instanceof AbstractDocument) { try { ((AbstractDocument) document).readUnlock(); needReadLock = true; } catch (Error ignore) { // readUnlock() might throw StateInvariantError } } } /** * Tries to acquire document's readLock if it was released * in releaseReadLock() method. * * Note: Not to be called on the EDT. */ private void acquireReadLock() { assert ! SwingUtilities.isEventDispatchThread(); if (needReadLock) { try { /* * wait until all the EDT events are processed * some of the document changes are asynchronous * we need to make sure we get the lock after those changes */ SwingUtilities.invokeAndWait( new Runnable() { public void run() { } }); } catch (InterruptedException ignore) { } catch (java.lang.reflect.InvocationTargetException ignore) { } Document document = textComponentToPrint.getDocument(); ((AbstractDocument) document).readLock(); needReadLock = false; } } /** * Prepares {@code printShell} for printing. * * Sets properties from the component to print. * Sets width and FontRenderContext. * * Triggers Views creation for the printShell. * * There are two parts in the implementation. * First part (layout) is to be called on the printing thread. * Second part (layoutOnEDT) is to be called on the EDT only. * * {@code layout} triggers {@code layoutOnEDT}. * * @param width width to layout the text for */ private void layout(final int width) { if (!SwingUtilities.isEventDispatchThread()) { Callable<Object> doLayoutOnEDT = new Callable<Object>() { public Object call() throws Exception { layoutOnEDT(width); return null; } }; FutureTask<Object> futureLayoutOnEDT = new FutureTask<Object>( doLayoutOnEDT); /* * We need to release document's readlock while printShell is * initializing */ releaseReadLock(); SwingUtilities.invokeLater(futureLayoutOnEDT); try { futureLayoutOnEDT.get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } else { throw new RuntimeException(cause); } } finally { acquireReadLock(); } } else { layoutOnEDT(width); } isLayouted = true; } /** * The EDT part of layout method. * * This method is to be called on the EDT only. */ private void layoutOnEDT(final int width) { assert SwingUtilities.isEventDispatchThread(); //need to have big value but smaller than MAX_VALUE otherwise //printing goes south due to overflow somewhere final int HUGE_INTEGER = Integer.MAX_VALUE - 1000; CellRendererPane rendererPane = new CellRendererPane(); //need to use JViewport since text is layouted to the viewPort width //otherwise it will be layouted to the maximum text width JViewport viewport = new JViewport(); viewport.setBorder(null); Dimension size = new Dimension(width, HUGE_INTEGER); //JTextField is a special case //it layouts text in the middle by Y if (printShell instanceof JTextField) { size = new Dimension(size.width, printShell.getPreferredSize().height); } printShell.setSize(size); viewport.setComponentOrientation(printShell.getComponentOrientation()); viewport.setSize(size); viewport.add(printShell); rendererPane.add(viewport); } /** * Calculates pageMetrics for the pages up to the {@code pageIndex} using * {@code rowsMetrics}. * Metrics are stored in the {@code pagesMetrics}. * * @param pageIndex the page to update the metrics for * @param pageHeight the page height */ private void updatePagesMetrics(final int pageIndex, final int pageHeight) { while (pageIndex >= pagesMetrics.size() && !rowsMetrics.isEmpty()) { // add one page to the pageMetrics int lastPage = pagesMetrics.size() - 1; int pageStart = (lastPage >= 0) ? pagesMetrics.get(lastPage).end + 1 : 0; int rowIndex; for (rowIndex = 0; rowIndex < rowsMetrics.size() && (rowsMetrics.get(rowIndex).end - pageStart + 1) <= pageHeight; rowIndex++) { } if (rowIndex == 0) { // can not fit a single row // need to split pagesMetrics.add( new IntegerSegment(pageStart, pageStart + pageHeight - 1)); } else { rowIndex--; pagesMetrics.add(new IntegerSegment(pageStart, rowsMetrics.get(rowIndex).end)); for (int i = 0; i <= rowIndex; i++) { rowsMetrics.remove(0); } } } } /** * Calculates rowsMetrics for the document. The result is stored * in the {@code rowsMetrics}. * * Two steps process. * First step is to find yStart and yEnd for the every document position. * Second step is to merge all intersected segments ( [yStart, yEnd] ). */ private void calculateRowsMetrics() { final int documentLength = printShell.getDocument().getLength(); List<IntegerSegment> documentMetrics = new ArrayList<IntegerSegment>(LIST_SIZE); Rectangle rect; for (int i = 0, previousY = -1, previousHeight = -1; i < documentLength; i++) { try { rect = printShell.modelToView(i); if (rect != null) { int y = (int) rect.getY(); int height = (int) rect.getHeight(); if (height != 0 && (y != previousY || height != previousHeight)) { /* * we do not store the same value as previous. in our * documents it is often for consequent positons to have * the same modelToView y and height. */ previousY = y; previousHeight = height; documentMetrics.add(new IntegerSegment(y, y + height - 1)); } } } catch (BadLocationException e) { assert false; } } /* * Merge all intersected segments. */ Collections.sort(documentMetrics); int yStart = Integer.MIN_VALUE; int yEnd = Integer.MIN_VALUE; for (IntegerSegment segment : documentMetrics) { if (yEnd < segment.start) { if (yEnd != Integer.MIN_VALUE) { rowsMetrics.add(new IntegerSegment(yStart, yEnd)); } yStart = segment.start; yEnd = segment.end; } else { yEnd = segment.end; } } if (yEnd != Integer.MIN_VALUE) { rowsMetrics.add(new IntegerSegment(yStart, yEnd)); } } /** * Class to represent segment of integers. * we do not call it Segment to avoid confusion with * javax.swing.text.Segment */ private static class IntegerSegment implements Comparable<IntegerSegment> { final int start; final int end; IntegerSegment(int start, int end) { this.start = start; this.end = end; } public int compareTo(IntegerSegment object) { int startsDelta = start - object.start; return (startsDelta != 0) ? startsDelta : end - object.end; } @Override public boolean equals(Object obj) { if (obj instanceof IntegerSegment) { return compareTo((IntegerSegment) obj) == 0; } else { return false; } } @Override public int hashCode() { // from the "Effective Java: Programming Language Guide" int result = 17; result = 37 * result + start; result = 37 * result + end; return result; } @Override public String toString() { return "IntegerSegment [" + start + ", " + end + "]"; } }}Lines 501–839 of 839. Previous lines
* This method is to be called on the EDT only. Layout should be done before * calling this method. */ private int printOnEDT(final Graphics graphics, final PageFormat pf, final int pageIndex) throws PrinterException { assert SwingUtilities.isEventDispatchThread(); Border border = BorderFactory.createEmptyBorder(); //handle header and footer if (headerFormat != null || footerFormat != null) { //Printable page enumeration is 0 base. We need 1 based. Object[] formatArg = new Object[]{Integer.valueOf(pageIndex + 1)}; if (headerFormat != null) { border = new TitledBorder(border, headerFormat.format(formatArg), TitledBorder.CENTER, TitledBorder.ABOVE_TOP, headerFont, printShell.getForeground()); } if (footerFormat != null) { border = new TitledBorder(border, footerFormat.format(formatArg), TitledBorder.CENTER, TitledBorder.BELOW_BOTTOM, footerFont, printShell.getForeground()); } } Insets borderInsets = border.getBorderInsets(printShell); updatePagesMetrics(pageIndex, (int)Math.floor(pf.getImageableHeight()) - borderInsets.top - borderInsets.bottom); if (pagesMetrics.size() <= pageIndex) { return NO_SUCH_PAGE; } Graphics2D g2d = (Graphics2D)graphics.create(); g2d.translate(pf.getImageableX(), pf.getImageableY()); border.paintBorder(printShell, g2d, 0, 0, (int)Math.floor(pf.getImageableWidth()), (int)Math.floor(pf.getImageableHeight())); g2d.translate(0, borderInsets.top); //want to clip only vertically Rectangle clip = new Rectangle(0, 0, (int) pf.getWidth(), pagesMetrics.get(pageIndex).end - pagesMetrics.get(pageIndex).start + 1); g2d.clip(clip); int xStart = 0; if (ComponentOrientation.RIGHT_TO_LEFT == printShell.getComponentOrientation()) { xStart = (int) pf.getImageableWidth() - printShell.getWidth(); } g2d.translate(xStart, - pagesMetrics.get(pageIndex).start); printShell.print(g2d); g2d.dispose(); return Printable.PAGE_EXISTS; } private boolean needReadLock = false; /** * Tries to release document's readlock * * Note: Not to be called on the EDT. */ private void releaseReadLock() { assert ! SwingUtilities.isEventDispatchThread(); Document document = textComponentToPrint.getDocument(); if (document instanceof AbstractDocument) { try { ((AbstractDocument) document).readUnlock(); needReadLock = true; } catch (Error ignore) { // readUnlock() might throw StateInvariantError } } } /** * Tries to acquire document's readLock if it was released * in releaseReadLock() method. * * Note: Not to be called on the EDT. */ private void acquireReadLock() { assert ! SwingUtilities.isEventDispatchThread(); if (needReadLock) { try { /* * wait until all the EDT events are processed * some of the document changes are asynchronous * we need to make sure we get the lock after those changes */ SwingUtilities.invokeAndWait( new Runnable() { public void run() { } }); } catch (InterruptedException ignore) { } catch (java.lang.reflect.InvocationTargetException ignore) { } Document document = textComponentToPrint.getDocument(); ((AbstractDocument) document).readLock(); needReadLock = false; } } /** * Prepares {@code printShell} for printing. * * Sets properties from the component to print. * Sets width and FontRenderContext. * * Triggers Views creation for the printShell. * * There are two parts in the implementation. * First part (layout) is to be called on the printing thread. * Second part (layoutOnEDT) is to be called on the EDT only. * * {@code layout} triggers {@code layoutOnEDT}. * * @param width width to layout the text for */ private void layout(final int width) { if (!SwingUtilities.isEventDispatchThread()) { Callable<Object> doLayoutOnEDT = new Callable<Object>() { public Object call() throws Exception { layoutOnEDT(width); return null; } }; FutureTask<Object> futureLayoutOnEDT = new FutureTask<Object>( doLayoutOnEDT); /* * We need to release document's readlock while printShell is * initializing */ releaseReadLock(); SwingUtilities.invokeLater(futureLayoutOnEDT); try { futureLayoutOnEDT.get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } else { throw new RuntimeException(cause); } } finally { acquireReadLock(); } } else { layoutOnEDT(width); } isLayouted = true; } /** * The EDT part of layout method. * * This method is to be called on the EDT only. */ private void layoutOnEDT(final int width) { assert SwingUtilities.isEventDispatchThread(); //need to have big value but smaller than MAX_VALUE otherwise //printing goes south due to overflow somewhere final int HUGE_INTEGER = Integer.MAX_VALUE - 1000; CellRendererPane rendererPane = new CellRendererPane(); //need to use JViewport since text is layouted to the viewPort width //otherwise it will be layouted to the maximum text width JViewport viewport = new JViewport(); viewport.setBorder(null); Dimension size = new Dimension(width, HUGE_INTEGER); //JTextField is a special case //it layouts text in the middle by Y if (printShell instanceof JTextField) { size = new Dimension(size.width, printShell.getPreferredSize().height); } printShell.setSize(size); viewport.setComponentOrientation(printShell.getComponentOrientation()); viewport.setSize(size); viewport.add(printShell); rendererPane.add(viewport); } /** * Calculates pageMetrics for the pages up to the {@code pageIndex} using * {@code rowsMetrics}. * Metrics are stored in the {@code pagesMetrics}. * * @param pageIndex the page to update the metrics for * @param pageHeight the page height */ private void updatePagesMetrics(final int pageIndex, final int pageHeight) { while (pageIndex >= pagesMetrics.size() && !rowsMetrics.isEmpty()) { // add one page to the pageMetrics int lastPage = pagesMetrics.size() - 1; int pageStart = (lastPage >= 0) ? pagesMetrics.get(lastPage).end + 1 : 0; int rowIndex; for (rowIndex = 0; rowIndex < rowsMetrics.size() && (rowsMetrics.get(rowIndex).end - pageStart + 1) <= pageHeight; rowIndex++) { } if (rowIndex == 0) { // can not fit a single row // need to split pagesMetrics.add( new IntegerSegment(pageStart, pageStart + pageHeight - 1)); } else { rowIndex--; pagesMetrics.add(new IntegerSegment(pageStart, rowsMetrics.get(rowIndex).end)); for (int i = 0; i <= rowIndex; i++) { rowsMetrics.remove(0); } } } } /** * Calculates rowsMetrics for the document. The result is stored * in the {@code rowsMetrics}. * * Two steps process. * First step is to find yStart and yEnd for the every document position. * Second step is to merge all intersected segments ( [yStart, yEnd] ). */ private void calculateRowsMetrics() { final int documentLength = printShell.getDocument().getLength(); List<IntegerSegment> documentMetrics = new ArrayList<IntegerSegment>(LIST_SIZE); Rectangle rect; for (int i = 0, previousY = -1, previousHeight = -1; i < documentLength; i++) { try { rect = printShell.modelToView(i); if (rect != null) { int y = (int) rect.getY(); int height = (int) rect.getHeight(); if (height != 0 && (y != previousY || height != previousHeight)) { /* * we do not store the same value as previous. in our * documents it is often for consequent positons to have * the same modelToView y and height. */ previousY = y; previousHeight = height; documentMetrics.add(new IntegerSegment(y, y + height - 1)); } } } catch (BadLocationException e) { assert false; } } /* * Merge all intersected segments. */ Collections.sort(documentMetrics); int yStart = Integer.MIN_VALUE; int yEnd = Integer.MIN_VALUE; for (IntegerSegment segment : documentMetrics) { if (yEnd < segment.start) { if (yEnd != Integer.MIN_VALUE) { rowsMetrics.add(new IntegerSegment(yStart, yEnd)); } yStart = segment.start; yEnd = segment.end; } else { yEnd = segment.end; } } if (yEnd != Integer.MIN_VALUE) { rowsMetrics.add(new IntegerSegment(yStart, yEnd)); } } /** * Class to represent segment of integers. * we do not call it Segment to avoid confusion with * javax.swing.text.Segment */ private static class IntegerSegment implements Comparable<IntegerSegment> { final int start; final int end; IntegerSegment(int start, int end) { this.start = start; this.end = end; } public int compareTo(IntegerSegment object) { int startsDelta = start - object.start; return (startsDelta != 0) ? startsDelta : end - object.end; } @Override public boolean equals(Object obj) { if (obj instanceof IntegerSegment) { return compareTo((IntegerSegment) obj) == 0; } else { return false; } } @Override public int hashCode() { // from the "Effective Java: Programming Language Guide" int result = 17; result = 37 * result + start; result = 37 * result + end; return result; } @Override public String toString() { return "IntegerSegment [" + start + ", " + end + "]"; } } }