Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/share/classes/sun/swing/text/TextComponentPrintable.java
41155 views
1
/*
2
* Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
package sun.swing.text;
26
27
import java.awt.ComponentOrientation;
28
import java.awt.Dimension;
29
import java.awt.Font;
30
import java.awt.FontMetrics;
31
import java.awt.Graphics;
32
import java.awt.Graphics2D;
33
import java.awt.Insets;
34
import java.awt.Rectangle;
35
import java.awt.Component;
36
import java.awt.Container;
37
import java.awt.font.FontRenderContext;
38
import java.awt.print.PageFormat;
39
import java.awt.print.Printable;
40
import java.awt.print.PrinterException;
41
import java.text.MessageFormat;
42
import java.util.ArrayList;
43
import java.util.Collections;
44
import java.util.List;
45
import java.util.concurrent.Callable;
46
import java.util.concurrent.ExecutionException;
47
import java.util.concurrent.FutureTask;
48
import java.util.concurrent.atomic.AtomicReference;
49
50
import javax.swing.*;
51
import javax.swing.border.Border;
52
import javax.swing.border.TitledBorder;
53
import javax.swing.text.BadLocationException;
54
import javax.swing.text.JTextComponent;
55
import javax.swing.text.Document;
56
import javax.swing.text.EditorKit;
57
import javax.swing.text.AbstractDocument;
58
import javax.swing.text.html.HTMLDocument;
59
import javax.swing.text.html.HTML;
60
61
import sun.font.FontDesignMetrics;
62
63
import sun.swing.text.html.FrameEditorPaneTag;
64
65
/**
66
* An implementation of {@code Printable} to print {@code JTextComponent} with
67
* the header and footer.
68
*
69
* <h1>
70
* WARNING: this class is to be used in
71
* javax.swing.text.JTextComponent only.
72
* </h1>
73
*
74
* <p>
75
* The implementation creates a new {@code JTextComponent} ({@code printShell})
76
* to print the content using the {@code Document}, {@code EditorKit} and
77
* rendering-affecting properties from the original {@code JTextComponent}.
78
*
79
* <p>
80
* {@code printShell} is laid out on the first {@code print} invocation.
81
*
82
* <p>
83
* This class can be used on any thread. Part of the implementation is executed
84
* on the EDT though.
85
*
86
* @author Igor Kushnirskiy
87
*
88
* @since 1.6
89
*/
90
public class TextComponentPrintable implements CountingPrintable {
91
92
93
private static final int LIST_SIZE = 1000;
94
95
private boolean isLayouted = false;
96
97
/*
98
* The text component to print.
99
*/
100
private final JTextComponent textComponentToPrint;
101
102
/*
103
* The FontRenderContext to layout and print with
104
*/
105
private final AtomicReference<FontRenderContext> frc =
106
new AtomicReference<FontRenderContext>(null);
107
108
/**
109
* Special text component used to print to the printer.
110
*/
111
private final JTextComponent printShell;
112
113
private final MessageFormat headerFormat;
114
private final MessageFormat footerFormat;
115
116
private static final float HEADER_FONT_SIZE = 18.0f;
117
private static final float FOOTER_FONT_SIZE = 12.0f;
118
119
private final Font headerFont;
120
private final Font footerFont;
121
122
/**
123
* stores metrics for the unhandled rows. The only metrics we need are
124
* yStart and yEnd when row is handled by updatePagesMetrics it is removed
125
* from the list. Thus the head of the list is the fist row to handle.
126
*
127
* sorted
128
*/
129
private final List<IntegerSegment> rowsMetrics;
130
131
/**
132
* thread-safe list for storing pages metrics. The only metrics we need are
133
* yStart and yEnd.
134
* It has to be thread-safe since metrics are calculated on
135
* the printing thread and accessed on the EDT thread.
136
*
137
* sorted
138
*/
139
private final List<IntegerSegment> pagesMetrics;
140
141
/**
142
* Returns {@code TextComponentPrintable} to print {@code textComponent}.
143
*
144
* @param textComponent {@code JTextComponent} to print
145
* @param headerFormat the page header, or {@code null} for none
146
* @param footerFormat the page footer, or {@code null} for none
147
* @return {@code TextComponentPrintable} to print {@code textComponent}
148
*/
149
public static Printable getPrintable(final JTextComponent textComponent,
150
final MessageFormat headerFormat,
151
final MessageFormat footerFormat) {
152
153
if (textComponent instanceof JEditorPane
154
&& isFrameSetDocument(textComponent.getDocument())) {
155
//for document with frames we create one printable per
156
//frame and merge them with the CompoundPrintable.
157
List<JEditorPane> frames = getFrames((JEditorPane) textComponent);
158
List<CountingPrintable> printables =
159
new ArrayList<CountingPrintable>();
160
for (JEditorPane frame : frames) {
161
printables.add((CountingPrintable)
162
getPrintable(frame, headerFormat, footerFormat));
163
}
164
return new CompoundPrintable(printables);
165
} else {
166
return new TextComponentPrintable(textComponent,
167
headerFormat, footerFormat);
168
}
169
}
170
171
/**
172
* Checks whether the document has frames. Only HTMLDocument might
173
* have frames.
174
*
175
* @param document the {@code Document} to check
176
* @return {@code true} if the {@code document} has frames
177
*/
178
private static boolean isFrameSetDocument(final Document document) {
179
boolean ret = false;
180
if (document instanceof HTMLDocument) {
181
HTMLDocument htmlDocument = (HTMLDocument)document;
182
if (htmlDocument.getIterator(HTML.Tag.FRAME).isValid()) {
183
ret = true;
184
}
185
}
186
return ret;
187
}
188
189
190
/**
191
* Returns frames under the {@code editor}.
192
* The frames are created if necessary.
193
*
194
* @param editor the {@JEditorPane} to find the frames for
195
* @return list of all frames
196
*/
197
private static List<JEditorPane> getFrames(final JEditorPane editor) {
198
List<JEditorPane> list = new ArrayList<JEditorPane>();
199
getFrames(editor, list);
200
if (list.size() == 0) {
201
//the frames have not been created yet.
202
//let's trigger the frames creation.
203
createFrames(editor);
204
getFrames(editor, list);
205
}
206
return list;
207
}
208
209
/**
210
* Adds all {@code JEditorPanes} under {@code container} tagged by {@code
211
* FrameEditorPaneTag} to the {@code list}. It adds only top
212
* level {@code JEditorPanes}. For instance if there is a frame
213
* inside the frame it will return the top frame only.
214
*
215
* @param container the container to find all frames under
216
* @param list {@code List} to append the results too
217
*/
218
private static void getFrames(final Container container, List<JEditorPane> list) {
219
for (Component c : container.getComponents()) {
220
if (c instanceof FrameEditorPaneTag
221
&& c instanceof JEditorPane ) { //it should be always JEditorPane
222
list.add((JEditorPane) c);
223
} else {
224
if (c instanceof Container) {
225
getFrames((Container) c, list);
226
}
227
}
228
}
229
}
230
231
/**
232
* Triggers the frames creation for {@code JEditorPane}
233
*
234
* @param editor the {@code JEditorPane} to create frames for
235
*/
236
private static void createFrames(final JEditorPane editor) {
237
Runnable doCreateFrames =
238
new Runnable() {
239
public void run() {
240
final int WIDTH = 500;
241
final int HEIGHT = 500;
242
CellRendererPane rendererPane = new CellRendererPane();
243
rendererPane.add(editor);
244
//the values do not matter
245
//we only need to get frames created
246
rendererPane.setSize(WIDTH, HEIGHT);
247
};
248
};
249
if (SwingUtilities.isEventDispatchThread()) {
250
doCreateFrames.run();
251
} else {
252
try {
253
SwingUtilities.invokeAndWait(doCreateFrames);
254
} catch (Exception e) {
255
if (e instanceof RuntimeException) {
256
throw (RuntimeException) e;
257
} else {
258
throw new RuntimeException(e);
259
}
260
}
261
}
262
}
263
264
/**
265
* Constructs {@code TextComponentPrintable} to print {@code JTextComponent}
266
* {@code textComponent} with {@code headerFormat} and {@code footerFormat}.
267
*
268
* @param textComponent {@code JTextComponent} to print
269
* @param headerFormat the page header or {@code null} for none
270
* @param footerFormat the page footer or {@code null} for none
271
*/
272
private TextComponentPrintable(JTextComponent textComponent,
273
MessageFormat headerFormat,
274
MessageFormat footerFormat) {
275
this.textComponentToPrint = textComponent;
276
this.headerFormat = headerFormat;
277
this.footerFormat = footerFormat;
278
headerFont = textComponent.getFont().deriveFont(Font.BOLD,
279
HEADER_FONT_SIZE);
280
footerFont = textComponent.getFont().deriveFont(Font.PLAIN,
281
FOOTER_FONT_SIZE);
282
this.pagesMetrics =
283
Collections.synchronizedList(new ArrayList<IntegerSegment>());
284
this.rowsMetrics = new ArrayList<IntegerSegment>(LIST_SIZE);
285
this.printShell = createPrintShell(textComponent);
286
}
287
288
289
/**
290
* creates a printShell.
291
* It creates closest text component to {@code textComponent}
292
* which uses {@code frc} from the {@code TextComponentPrintable}
293
* for the {@code getFontMetrics} method.
294
*
295
* @param textComponent {@code JTextComponent} to create a
296
* printShell for
297
* @return the print shell
298
*/
299
private JTextComponent createPrintShell(final JTextComponent textComponent) {
300
if (SwingUtilities.isEventDispatchThread()) {
301
return createPrintShellOnEDT(textComponent);
302
} else {
303
FutureTask<JTextComponent> futureCreateShell =
304
new FutureTask<JTextComponent>(
305
new Callable<JTextComponent>() {
306
public JTextComponent call() throws Exception {
307
return createPrintShellOnEDT(textComponent);
308
}
309
});
310
SwingUtilities.invokeLater(futureCreateShell);
311
try {
312
return futureCreateShell.get();
313
} catch (InterruptedException e) {
314
throw new RuntimeException(e);
315
} catch (ExecutionException e) {
316
Throwable cause = e.getCause();
317
if (cause instanceof Error) {
318
throw (Error) cause;
319
}
320
if (cause instanceof RuntimeException) {
321
throw (RuntimeException) cause;
322
}
323
throw new AssertionError(cause);
324
}
325
}
326
}
327
@SuppressWarnings("serial") // anonymous class inside
328
private JTextComponent createPrintShellOnEDT(final JTextComponent textComponent) {
329
assert SwingUtilities.isEventDispatchThread();
330
331
JTextComponent ret = null;
332
if (textComponent instanceof JPasswordField) {
333
ret =
334
new JPasswordField() {
335
{
336
setEchoChar(((JPasswordField) textComponent).getEchoChar());
337
setHorizontalAlignment(
338
((JTextField) textComponent).getHorizontalAlignment());
339
}
340
@Override
341
public FontMetrics getFontMetrics(Font font) {
342
return (frc.get() == null)
343
? super.getFontMetrics(font)
344
: FontDesignMetrics.getMetrics(font, frc.get());
345
}
346
};
347
} else if (textComponent instanceof JTextField) {
348
ret =
349
new JTextField() {
350
{
351
setHorizontalAlignment(
352
((JTextField) textComponent).getHorizontalAlignment());
353
}
354
@Override
355
public FontMetrics getFontMetrics(Font font) {
356
return (frc.get() == null)
357
? super.getFontMetrics(font)
358
: FontDesignMetrics.getMetrics(font, frc.get());
359
}
360
};
361
} else if (textComponent instanceof JTextArea) {
362
ret =
363
new JTextArea() {
364
{
365
JTextArea textArea = (JTextArea) textComponent;
366
setLineWrap(textArea.getLineWrap());
367
setWrapStyleWord(textArea.getWrapStyleWord());
368
setTabSize(textArea.getTabSize());
369
}
370
@Override
371
public FontMetrics getFontMetrics(Font font) {
372
return (frc.get() == null)
373
? super.getFontMetrics(font)
374
: FontDesignMetrics.getMetrics(font, frc.get());
375
}
376
};
377
} else if (textComponent instanceof JTextPane) {
378
ret =
379
new JTextPane() {
380
@Override
381
public FontMetrics getFontMetrics(Font font) {
382
return (frc.get() == null)
383
? super.getFontMetrics(font)
384
: FontDesignMetrics.getMetrics(font, frc.get());
385
}
386
@Override
387
public EditorKit getEditorKit() {
388
if (getDocument() == textComponent.getDocument()) {
389
return ((JTextPane) textComponent).getEditorKit();
390
} else {
391
return super.getEditorKit();
392
}
393
}
394
};
395
} else if (textComponent instanceof JEditorPane) {
396
ret =
397
new JEditorPane() {
398
@Override
399
public FontMetrics getFontMetrics(Font font) {
400
return (frc.get() == null)
401
? super.getFontMetrics(font)
402
: FontDesignMetrics.getMetrics(font, frc.get());
403
}
404
@Override
405
public EditorKit getEditorKit() {
406
if (getDocument() == textComponent.getDocument()) {
407
return ((JEditorPane) textComponent).getEditorKit();
408
} else {
409
return super.getEditorKit();
410
}
411
}
412
};
413
}
414
//want to occupy the whole width and height by text
415
ret.setBorder(null);
416
417
//set properties from the component to print
418
ret.setOpaque(textComponent.isOpaque());
419
ret.setEditable(textComponent.isEditable());
420
ret.setEnabled(textComponent.isEnabled());
421
ret.setFont(textComponent.getFont());
422
ret.setBackground(textComponent.getBackground());
423
ret.setForeground(textComponent.getForeground());
424
ret.setComponentOrientation(
425
textComponent.getComponentOrientation());
426
427
if (ret instanceof JEditorPane) {
428
ret.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES,
429
textComponent.getClientProperty(
430
JEditorPane.HONOR_DISPLAY_PROPERTIES));
431
ret.putClientProperty(JEditorPane.W3C_LENGTH_UNITS,
432
textComponent.getClientProperty(JEditorPane.W3C_LENGTH_UNITS));
433
ret.putClientProperty("charset",
434
textComponent.getClientProperty("charset"));
435
}
436
ret.setDocument(textComponent.getDocument());
437
return ret;
438
}
439
440
441
442
443
/**
444
* Returns the number of pages in this printable.
445
* <p>
446
* This number is defined only after {@code print} returns NO_SUCH_PAGE.
447
*
448
* @return the number of pages.
449
*/
450
public int getNumberOfPages() {
451
return pagesMetrics.size();
452
}
453
454
/**
455
* See Printable.print for the API description.
456
*
457
* There are two parts in the implementation.
458
* First part (print) is to be called on the printing thread.
459
* Second part (printOnEDT) is to be called on the EDT only.
460
*
461
* print triggers printOnEDT
462
*/
463
public int print(final Graphics graphics,
464
final PageFormat pf,
465
final int pageIndex) throws PrinterException {
466
if (!isLayouted) {
467
if (graphics instanceof Graphics2D) {
468
frc.set(((Graphics2D)graphics).getFontRenderContext());
469
}
470
layout((int)Math.floor(pf.getImageableWidth()));
471
calculateRowsMetrics();
472
}
473
int ret;
474
if (!SwingUtilities.isEventDispatchThread()) {
475
Callable<Integer> doPrintOnEDT = new Callable<Integer>() {
476
public Integer call() throws Exception {
477
return printOnEDT(graphics, pf, pageIndex);
478
}
479
};
480
FutureTask<Integer> futurePrintOnEDT =
481
new FutureTask<Integer>(doPrintOnEDT);
482
SwingUtilities.invokeLater(futurePrintOnEDT);
483
try {
484
ret = futurePrintOnEDT.get();
485
} catch (InterruptedException e) {
486
throw new RuntimeException(e);
487
} catch (ExecutionException e) {
488
Throwable cause = e.getCause();
489
if (cause instanceof PrinterException) {
490
throw (PrinterException)cause;
491
} else if (cause instanceof RuntimeException) {
492
throw (RuntimeException) cause;
493
} else if (cause instanceof Error) {
494
throw (Error) cause;
495
} else {
496
throw new RuntimeException(cause);
497
}
498
}
499
} else {
500
ret = printOnEDT(graphics, pf, pageIndex);
501
}
502
return ret;
503
}
504
505
506
/**
507
* The EDT part of the print method.
508
*
509
* This method is to be called on the EDT only. Layout should be done before
510
* calling this method.
511
*/
512
private int printOnEDT(final Graphics graphics,
513
final PageFormat pf,
514
final int pageIndex) throws PrinterException {
515
assert SwingUtilities.isEventDispatchThread();
516
Border border = BorderFactory.createEmptyBorder();
517
//handle header and footer
518
if (headerFormat != null || footerFormat != null) {
519
//Printable page enumeration is 0 base. We need 1 based.
520
Object[] formatArg = new Object[]{Integer.valueOf(pageIndex + 1)};
521
if (headerFormat != null) {
522
border = new TitledBorder(border,
523
headerFormat.format(formatArg),
524
TitledBorder.CENTER, TitledBorder.ABOVE_TOP,
525
headerFont, printShell.getForeground());
526
}
527
if (footerFormat != null) {
528
border = new TitledBorder(border,
529
footerFormat.format(formatArg),
530
TitledBorder.CENTER, TitledBorder.BELOW_BOTTOM,
531
footerFont, printShell.getForeground());
532
}
533
}
534
Insets borderInsets = border.getBorderInsets(printShell);
535
updatePagesMetrics(pageIndex,
536
(int)Math.floor(pf.getImageableHeight()) - borderInsets.top
537
- borderInsets.bottom);
538
539
if (pagesMetrics.size() <= pageIndex) {
540
return NO_SUCH_PAGE;
541
}
542
543
Graphics2D g2d = (Graphics2D)graphics.create();
544
545
g2d.translate(pf.getImageableX(), pf.getImageableY());
546
border.paintBorder(printShell, g2d, 0, 0,
547
(int)Math.floor(pf.getImageableWidth()),
548
(int)Math.floor(pf.getImageableHeight()));
549
550
g2d.translate(0, borderInsets.top);
551
//want to clip only vertically
552
Rectangle clip = new Rectangle(0, 0,
553
(int) pf.getWidth(),
554
pagesMetrics.get(pageIndex).end
555
- pagesMetrics.get(pageIndex).start + 1);
556
557
g2d.clip(clip);
558
int xStart = 0;
559
if (ComponentOrientation.RIGHT_TO_LEFT ==
560
printShell.getComponentOrientation()) {
561
xStart = (int) pf.getImageableWidth() - printShell.getWidth();
562
}
563
g2d.translate(xStart, - pagesMetrics.get(pageIndex).start);
564
printShell.print(g2d);
565
566
g2d.dispose();
567
568
return Printable.PAGE_EXISTS;
569
}
570
571
572
private boolean needReadLock = false;
573
574
/**
575
* Tries to release document's readlock
576
*
577
* Note: Not to be called on the EDT.
578
*/
579
private void releaseReadLock() {
580
assert ! SwingUtilities.isEventDispatchThread();
581
Document document = textComponentToPrint.getDocument();
582
if (document instanceof AbstractDocument) {
583
try {
584
((AbstractDocument) document).readUnlock();
585
needReadLock = true;
586
} catch (Error ignore) {
587
// readUnlock() might throw StateInvariantError
588
}
589
}
590
}
591
592
593
/**
594
* Tries to acquire document's readLock if it was released
595
* in releaseReadLock() method.
596
*
597
* Note: Not to be called on the EDT.
598
*/
599
private void acquireReadLock() {
600
assert ! SwingUtilities.isEventDispatchThread();
601
if (needReadLock) {
602
try {
603
/*
604
* wait until all the EDT events are processed
605
* some of the document changes are asynchronous
606
* we need to make sure we get the lock after those changes
607
*/
608
SwingUtilities.invokeAndWait(
609
new Runnable() {
610
public void run() {
611
}
612
});
613
} catch (InterruptedException ignore) {
614
} catch (java.lang.reflect.InvocationTargetException ignore) {
615
}
616
Document document = textComponentToPrint.getDocument();
617
((AbstractDocument) document).readLock();
618
needReadLock = false;
619
}
620
}
621
622
/**
623
* Prepares {@code printShell} for printing.
624
*
625
* Sets properties from the component to print.
626
* Sets width and FontRenderContext.
627
*
628
* Triggers Views creation for the printShell.
629
*
630
* There are two parts in the implementation.
631
* First part (layout) is to be called on the printing thread.
632
* Second part (layoutOnEDT) is to be called on the EDT only.
633
*
634
* {@code layout} triggers {@code layoutOnEDT}.
635
*
636
* @param width width to layout the text for
637
*/
638
private void layout(final int width) {
639
if (!SwingUtilities.isEventDispatchThread()) {
640
Callable<Object> doLayoutOnEDT = new Callable<Object>() {
641
public Object call() throws Exception {
642
layoutOnEDT(width);
643
return null;
644
}
645
};
646
FutureTask<Object> futureLayoutOnEDT = new FutureTask<Object>(
647
doLayoutOnEDT);
648
649
/*
650
* We need to release document's readlock while printShell is
651
* initializing
652
*/
653
releaseReadLock();
654
SwingUtilities.invokeLater(futureLayoutOnEDT);
655
try {
656
futureLayoutOnEDT.get();
657
} catch (InterruptedException e) {
658
throw new RuntimeException(e);
659
} catch (ExecutionException e) {
660
Throwable cause = e.getCause();
661
if (cause instanceof RuntimeException) {
662
throw (RuntimeException) cause;
663
} else if (cause instanceof Error) {
664
throw (Error) cause;
665
} else {
666
throw new RuntimeException(cause);
667
}
668
} finally {
669
acquireReadLock();
670
}
671
} else {
672
layoutOnEDT(width);
673
}
674
675
isLayouted = true;
676
}
677
678
/**
679
* The EDT part of layout method.
680
*
681
* This method is to be called on the EDT only.
682
*/
683
private void layoutOnEDT(final int width) {
684
assert SwingUtilities.isEventDispatchThread();
685
//need to have big value but smaller than MAX_VALUE otherwise
686
//printing goes south due to overflow somewhere
687
final int HUGE_INTEGER = Integer.MAX_VALUE - 1000;
688
689
CellRendererPane rendererPane = new CellRendererPane();
690
691
//need to use JViewport since text is layouted to the viewPort width
692
//otherwise it will be layouted to the maximum text width
693
JViewport viewport = new JViewport();
694
viewport.setBorder(null);
695
Dimension size = new Dimension(width, HUGE_INTEGER);
696
697
//JTextField is a special case
698
//it layouts text in the middle by Y
699
if (printShell instanceof JTextField) {
700
size =
701
new Dimension(size.width, printShell.getPreferredSize().height);
702
}
703
printShell.setSize(size);
704
viewport.setComponentOrientation(printShell.getComponentOrientation());
705
viewport.setSize(size);
706
viewport.add(printShell);
707
rendererPane.add(viewport);
708
}
709
710
/**
711
* Calculates pageMetrics for the pages up to the {@code pageIndex} using
712
* {@code rowsMetrics}.
713
* Metrics are stored in the {@code pagesMetrics}.
714
*
715
* @param pageIndex the page to update the metrics for
716
* @param pageHeight the page height
717
*/
718
private void updatePagesMetrics(final int pageIndex, final int pageHeight) {
719
while (pageIndex >= pagesMetrics.size() && !rowsMetrics.isEmpty()) {
720
// add one page to the pageMetrics
721
int lastPage = pagesMetrics.size() - 1;
722
int pageStart = (lastPage >= 0)
723
? pagesMetrics.get(lastPage).end + 1
724
: 0;
725
int rowIndex;
726
for (rowIndex = 0;
727
rowIndex < rowsMetrics.size()
728
&& (rowsMetrics.get(rowIndex).end - pageStart + 1)
729
<= pageHeight;
730
rowIndex++) {
731
}
732
if (rowIndex == 0) {
733
// can not fit a single row
734
// need to split
735
pagesMetrics.add(
736
new IntegerSegment(pageStart, pageStart + pageHeight - 1));
737
} else {
738
rowIndex--;
739
pagesMetrics.add(new IntegerSegment(pageStart,
740
rowsMetrics.get(rowIndex).end));
741
for (int i = 0; i <= rowIndex; i++) {
742
rowsMetrics.remove(0);
743
}
744
}
745
}
746
}
747
748
/**
749
* Calculates rowsMetrics for the document. The result is stored
750
* in the {@code rowsMetrics}.
751
*
752
* Two steps process.
753
* First step is to find yStart and yEnd for the every document position.
754
* Second step is to merge all intersected segments ( [yStart, yEnd] ).
755
*/
756
@SuppressWarnings("deprecation")
757
private void calculateRowsMetrics() {
758
final int documentLength = printShell.getDocument().getLength();
759
List<IntegerSegment> documentMetrics = new ArrayList<IntegerSegment>(LIST_SIZE);
760
Rectangle rect;
761
for (int i = 0, previousY = -1, previousHeight = -1; i < documentLength;
762
i++) {
763
try {
764
rect = printShell.modelToView(i);
765
if (rect != null) {
766
int y = (int) rect.getY();
767
int height = (int) rect.getHeight();
768
if (height != 0
769
&& (y != previousY || height != previousHeight)) {
770
/*
771
* we do not store the same value as previous. in our
772
* documents it is often for consequent positons to have
773
* the same modelToView y and height.
774
*/
775
previousY = y;
776
previousHeight = height;
777
documentMetrics.add(new IntegerSegment(y, y + height - 1));
778
}
779
}
780
} catch (BadLocationException e) {
781
assert false;
782
}
783
}
784
/*
785
* Merge all intersected segments.
786
*/
787
Collections.sort(documentMetrics);
788
int yStart = Integer.MIN_VALUE;
789
int yEnd = Integer.MIN_VALUE;
790
for (IntegerSegment segment : documentMetrics) {
791
if (yEnd < segment.start) {
792
if (yEnd != Integer.MIN_VALUE) {
793
rowsMetrics.add(new IntegerSegment(yStart, yEnd));
794
}
795
yStart = segment.start;
796
yEnd = segment.end;
797
} else {
798
yEnd = segment.end;
799
}
800
}
801
if (yEnd != Integer.MIN_VALUE) {
802
rowsMetrics.add(new IntegerSegment(yStart, yEnd));
803
}
804
}
805
806
/**
807
* Class to represent segment of integers.
808
* we do not call it Segment to avoid confusion with
809
* javax.swing.text.Segment
810
*/
811
private static class IntegerSegment implements Comparable<IntegerSegment> {
812
final int start;
813
final int end;
814
815
IntegerSegment(int start, int end) {
816
this.start = start;
817
this.end = end;
818
}
819
820
public int compareTo(IntegerSegment object) {
821
int startsDelta = start - object.start;
822
return (startsDelta != 0) ? startsDelta : end - object.end;
823
}
824
825
@Override
826
public boolean equals(Object obj) {
827
if (obj instanceof IntegerSegment) {
828
return compareTo((IntegerSegment) obj) == 0;
829
} else {
830
return false;
831
}
832
}
833
834
@Override
835
public int hashCode() {
836
// from the "Effective Java: Programming Language Guide"
837
int result = 17;
838
result = 37 * result + start;
839
result = 37 * result + end;
840
return result;
841
}
842
843
@Override
844
public String toString() {
845
return "IntegerSegment [" + start + ", " + end + "]";
846
}
847
}
848
}
849
850