/*
 * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package sun.swing.text;

import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Component;
import java.awt.Container;
import java.awt.font.FontRenderContext;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicReference;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import javax.swing.text.AbstractDocument;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTML;

import sun.font.FontDesignMetrics;

import sun.swing.text.html.FrameEditorPaneTag;

An implementation of Printable to print JTextComponent with the header and footer.

WARNING: this class is to be used in javax.swing.text.JTextComponent only.

The implementation creates a new JTextComponent (printShell) to print the content using the Document, EditorKit and rendering-affecting properties from the original JTextComponent.

printShell is laid out on the first print invocation.

This class can be used on any thread. Part of the implementation is executed on the EDT though.

Author:Igor Kushnirskiy
Since:1.6
/** * An implementation of {@code Printable} to print {@code JTextComponent} with * the header and footer. * * <h1> * WARNING: this class is to be used in * javax.swing.text.JTextComponent only. * </h1> * * <p> * The implementation creates a new {@code JTextComponent} ({@code printShell}) * to print the content using the {@code Document}, {@code EditorKit} and * rendering-affecting properties from the original {@code JTextComponent}. * * <p> * {@code printShell} is laid out on the first {@code print} invocation. * * <p> * This class can be used on any thread. Part of the implementation is executed * on the EDT though. * * @author Igor Kushnirskiy * * @since 1.6 */
public class TextComponentPrintable implements CountingPrintable { private static final int LIST_SIZE = 1000; private boolean isLayouted = false; /* * The text component to print. */ private final JTextComponent textComponentToPrint; /* * The FontRenderContext to layout and print with */ private final AtomicReference<FontRenderContext> frc = new AtomicReference<FontRenderContext>(null);
Special text component used to print to the printer.
/** * Special text component used to print to the printer. */
private final JTextComponent printShell; private final MessageFormat headerFormat; private final MessageFormat footerFormat; private static final float HEADER_FONT_SIZE = 18.0f; private static final float FOOTER_FONT_SIZE = 12.0f; private final Font headerFont; private final Font footerFont;
stores metrics for the unhandled rows. The only metrics we need are yStart and yEnd when row is handled by updatePagesMetrics it is removed from the list. Thus the head of the list is the fist row to handle. sorted
/** * stores metrics for the unhandled rows. The only metrics we need are * yStart and yEnd when row is handled by updatePagesMetrics it is removed * from the list. Thus the head of the list is the fist row to handle. * * sorted */
private final List<IntegerSegment> rowsMetrics;
thread-safe list for storing pages metrics. The only metrics we need are yStart and yEnd. It has to be thread-safe since metrics are calculated on the printing thread and accessed on the EDT thread. sorted
/** * thread-safe list for storing pages metrics. The only metrics we need are * yStart and yEnd. * It has to be thread-safe since metrics are calculated on * the printing thread and accessed on the EDT thread. * * sorted */
private final List<IntegerSegment> pagesMetrics;
Returns TextComponentPrintable to print textComponent.
Params:
  • textComponent – JTextComponent to print
  • headerFormat – the page header, or null for none
  • footerFormat – the page footer, or null for none
Returns:TextComponentPrintable to print textComponent
/** * Returns {@code TextComponentPrintable} to print {@code textComponent}. * * @param textComponent {@code JTextComponent} to print * @param headerFormat the page header, or {@code null} for none * @param footerFormat the page footer, or {@code null} for none * @return {@code TextComponentPrintable} to print {@code textComponent} */
public static Printable getPrintable(final JTextComponent textComponent, final MessageFormat headerFormat, final MessageFormat footerFormat) { if (textComponent instanceof JEditorPane && isFrameSetDocument(textComponent.getDocument())) { //for document with frames we create one printable per //frame and merge them with the CompoundPrintable. List<JEditorPane> frames = getFrames((JEditorPane) textComponent); List<CountingPrintable> printables = new ArrayList<CountingPrintable>(); for (JEditorPane frame : frames) { printables.add((CountingPrintable) getPrintable(frame, headerFormat, footerFormat)); } return new CompoundPrintable(printables); } else { return new TextComponentPrintable(textComponent, headerFormat, footerFormat); } }
Checks whether the document has frames. Only HTMLDocument might have frames.
Params:
  • document – the Document to check
Returns:true if the document has frames
/** * Checks whether the document has frames. Only HTMLDocument might * have frames. * * @param document the {@code Document} to check * @return {@code true} if the {@code document} has frames */
private static boolean isFrameSetDocument(final Document document) { boolean ret = false; if (document instanceof HTMLDocument) { HTMLDocument htmlDocument = (HTMLDocument)document; if (htmlDocument.getIterator(HTML.Tag.FRAME).isValid()) { ret = true; } } return ret; }
Returns frames under the editor. The frames are created if necessary.
Params:
  • editor – the {@JEditorPane} to find the frames for
Returns:list of all frames
/** * Returns frames under the {@code editor}. * The frames are created if necessary. * * @param editor the {@JEditorPane} to find the frames for * @return list of all frames */
private static List<JEditorPane> getFrames(final JEditorPane editor) { List<JEditorPane> list = new ArrayList<JEditorPane>(); getFrames(editor, list); if (list.size() == 0) { //the frames have not been created yet. //let's trigger the frames creation. createFrames(editor); getFrames(editor, list); } return list; }
Adds all JEditorPanes under container tagged by FrameEditorPaneTag to the list. It adds only top level JEditorPanes. For instance if there is a frame inside the frame it will return the top frame only.
Params:
  • container – the container to find all frames under
  • list – List to append the results too
/** * Adds all {@code JEditorPanes} under {@code container} tagged by {@code * FrameEditorPaneTag} to the {@code list}. It adds only top * level {@code JEditorPanes}. For instance if there is a frame * inside the frame it will return the top frame only. * * @param container the container to find all frames under * @param list {@code List} to append the results too */
private static void getFrames(final Container container, List<JEditorPane> list) { for (Component c : container.getComponents()) { if (c instanceof FrameEditorPaneTag && c instanceof JEditorPane ) { //it should be always JEditorPane list.add((JEditorPane) c); } else { if (c instanceof Container) { getFrames((Container) c, list); } } } }
Triggers the frames creation for JEditorPane
Params:
  • editor – the JEditorPane to create frames for
/** * Triggers the frames creation for {@code JEditorPane} * * @param editor the {@code JEditorPane} to create frames for */
private static void createFrames(final JEditorPane editor) { Runnable doCreateFrames = new Runnable() { public void run() { final int WIDTH = 500; final int HEIGHT = 500; CellRendererPane rendererPane = new CellRendererPane(); rendererPane.add(editor); //the values do not matter //we only need to get frames created rendererPane.setSize(WIDTH, HEIGHT); }; }; if (SwingUtilities.isEventDispatchThread()) { doCreateFrames.run(); } else { try { SwingUtilities.invokeAndWait(doCreateFrames); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { throw new RuntimeException(e); } } } }
Constructs TextComponentPrintable to print JTextComponent textComponent with headerFormat and footerFormat.
Params:
  • textComponent – JTextComponent to print
  • headerFormat – the page header or null for none
  • footerFormat – the page footer or null for none
/** * Constructs {@code TextComponentPrintable} to print {@code JTextComponent} * {@code textComponent} with {@code headerFormat} and {@code footerFormat}. * * @param textComponent {@code JTextComponent} to print * @param headerFormat the page header or {@code null} for none * @param footerFormat the page footer or {@code null} for none */
private TextComponentPrintable(JTextComponent textComponent, MessageFormat headerFormat, MessageFormat footerFormat) { this.textComponentToPrint = textComponent; this.headerFormat = headerFormat; this.footerFormat = footerFormat; headerFont = textComponent.getFont().deriveFont(Font.BOLD, HEADER_FONT_SIZE); footerFont = textComponent.getFont().deriveFont(Font.PLAIN, FOOTER_FONT_SIZE); this.pagesMetrics = Collections.synchronizedList(new ArrayList<IntegerSegment>()); this.rowsMetrics = new ArrayList<IntegerSegment>(LIST_SIZE); this.printShell = createPrintShell(textComponent); }
creates a printShell. It creates closest text component to textComponent which uses frc from the TextComponentPrintable for the getFontMetrics method.
Params:
  • textComponent – JTextComponent to create a printShell for
Returns:the print shell
/** * creates a printShell. * It creates closest text component to {@code textComponent} * which uses {@code frc} from the {@code TextComponentPrintable} * for the {@code getFontMetrics} method. * * @param textComponent {@code JTextComponent} to create a * printShell for * @return the print shell */
private JTextComponent createPrintShell(final JTextComponent textComponent) { if (SwingUtilities.isEventDispatchThread()) { return createPrintShellOnEDT(textComponent); } else { FutureTask<JTextComponent> futureCreateShell = new FutureTask<JTextComponent>( new Callable<JTextComponent>() { public JTextComponent call() throws Exception { return createPrintShellOnEDT(textComponent); } }); SwingUtilities.invokeLater(futureCreateShell); try { return futureCreateShell.get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof Error) { throw (Error) cause; } if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } throw new AssertionError(cause); } } } @SuppressWarnings("serial") // anonymous class inside private JTextComponent createPrintShellOnEDT(final JTextComponent textComponent) { assert SwingUtilities.isEventDispatchThread(); JTextComponent ret = null; if (textComponent instanceof JPasswordField) { ret = new JPasswordField() { { setEchoChar(((JPasswordField) textComponent).getEchoChar()); setHorizontalAlignment( ((JTextField) textComponent).getHorizontalAlignment()); } @Override public FontMetrics getFontMetrics(Font font) { return (frc.get() == null) ? super.getFontMetrics(font) : FontDesignMetrics.getMetrics(font, frc.get()); } }; } else if (textComponent instanceof JTextField) { ret = new JTextField() { { setHorizontalAlignment( ((JTextField) textComponent).getHorizontalAlignment()); } @Override public FontMetrics getFontMetrics(Font font) { return (frc.get() == null) ? super.getFontMetrics(font) : FontDesignMetrics.getMetrics(font, frc.get()); } }; } else if (textComponent instanceof JTextArea) { ret = new JTextArea() { { JTextArea textArea = (JTextArea) textComponent; setLineWrap(textArea.getLineWrap()); setWrapStyleWord(textArea.getWrapStyleWord()); setTabSize(textArea.getTabSize()); } @Override public FontMetrics getFontMetrics(Font font) { return (frc.get() == null) ? super.getFontMetrics(font) : FontDesignMetrics.getMetrics(font, frc.get()); } }; } else if (textComponent instanceof JTextPane) { ret = new JTextPane() { @Override public FontMetrics getFontMetrics(Font font) { return (frc.get() == null) ? super.getFontMetrics(font) : FontDesignMetrics.getMetrics(font, frc.get()); } @Override public EditorKit getEditorKit() { if (getDocument() == textComponent.getDocument()) { return ((JTextPane) textComponent).getEditorKit(); } else { return super.getEditorKit(); } } }; } else if (textComponent instanceof JEditorPane) { ret = new JEditorPane() { @Override public FontMetrics getFontMetrics(Font font) { return (frc.get() == null) ? super.getFontMetrics(font) : FontDesignMetrics.getMetrics(font, frc.get()); } @Override public EditorKit getEditorKit() { if (getDocument() == textComponent.getDocument()) { return ((JEditorPane) textComponent).getEditorKit(); } else { return super.getEditorKit(); } } }; } //want to occupy the whole width and height by text ret.setBorder(null); //set properties from the component to print ret.setOpaque(textComponent.isOpaque()); ret.setEditable(textComponent.isEditable()); ret.setEnabled(textComponent.isEnabled()); ret.setFont(textComponent.getFont()); ret.setBackground(textComponent.getBackground()); ret.setForeground(textComponent.getForeground()); ret.setComponentOrientation( textComponent.getComponentOrientation()); if (ret instanceof JEditorPane) { ret.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, textComponent.getClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES)); ret.putClientProperty(JEditorPane.W3C_LENGTH_UNITS, textComponent.getClientProperty(JEditorPane.W3C_LENGTH_UNITS)); ret.putClientProperty("charset", textComponent.getClientProperty("charset")); } ret.setDocument(textComponent.getDocument()); return ret; }
Returns the number of pages in this printable.

This number is defined only after print returns NO_SUCH_PAGE.

Returns:the number of pages.
/** * Returns the number of pages in this printable. * <p> * This number is defined only after {@code print} returns NO_SUCH_PAGE. * * @return the number of pages. */
public int getNumberOfPages() { return pagesMetrics.size(); }
See Printable.print for the API description. There are two parts in the implementation. First part (print) is to be called on the printing thread. Second part (printOnEDT) is to be called on the EDT only. print triggers printOnEDT
/** * See Printable.print for the API description. * * There are two parts in the implementation. * First part (print) is to be called on the printing thread. * Second part (printOnEDT) is to be called on the EDT only. * * print triggers printOnEDT */
public int print(final Graphics graphics, final PageFormat pf, final int pageIndex) throws PrinterException { if (!isLayouted) { if (graphics instanceof Graphics2D) { frc.set(((Graphics2D)graphics).getFontRenderContext()); } layout((int)Math.floor(pf.getImageableWidth())); calculateRowsMetrics(); } int ret; if (!SwingUtilities.isEventDispatchThread()) { Callable<Integer> doPrintOnEDT = new Callable<Integer>() { public Integer call() throws Exception { return printOnEDT(graphics, pf, pageIndex); } }; FutureTask<Integer> futurePrintOnEDT = new FutureTask<Integer>(doPrintOnEDT); SwingUtilities.invokeLater(futurePrintOnEDT); try { ret = futurePrintOnEDT.get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof PrinterException) { throw (PrinterException)cause; } else if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } else { throw new RuntimeException(cause); } } } else { ret = printOnEDT(graphics, pf, pageIndex); } return ret; }
The EDT part of the print method. This method is to be called on the EDT only. Layout should be done before calling this method.
/** * The EDT part of the print method. * * 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.
/** * 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.
/** * 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 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. layout triggers layoutOnEDT.
Params:
  • width – width to layout the text for
/** * 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.
/** * 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 pageIndex using rowsMetrics. Metrics are stored in the pagesMetrics.
Params:
  • pageIndex – the page to update the metrics for
  • pageHeight – the page height
/** * 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 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] ).
/** * 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] ). */
@SuppressWarnings("deprecation") 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
/** * 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 + "]"; } } }