/*
* Copyright (c) 2005, 2006, 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.BorderFactory;
import javax.swing.CellRendererPane;
import javax.swing.JTextField;
import javax.swing.JTextArea;
import javax.swing.JEditorPane;
import javax.swing.JViewport;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
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: - c – 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 c 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);
}
}
}
private JTextComponent createPrintShellOnEDT(final JTextComponent textComponent) {
assert SwingUtilities.isEventDispatchThread();
JTextComponent ret = null;
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] ).
*/
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 + "]";
}
}
}