In java swing JTable, I want to print multiple header with new lines from MessageFormat.
MessageFormat header = new MessageFormat("Products Details" + '\n' + "xyz suppliers");
MessageFormat footer = new MessageFormat("Page{1,number,integer}");
tbl_country.print(JTable.PrintMode.FIT_WIDTH, header, footer);
I had try lot of codes but new lines header does not work.
Finally i found answer to print multiple header with new line of JTable data as below:-
My TablePrintable.java
import javax.swing.table.*;
import java.awt.*;
import java.awt.print.*;
import java.awt.geom.*;
import java.text.MessageFormat;
import javax.swing.JTable;
/**
* An implementation of <code>Printable</code> for printing
* <code>JTable</code>s.
* <p>
* This implementation spreads table rows naturally in sequence across multiple
* pages, fitting as many rows as possible per page. The distribution of
* columns, on the other hand, is controlled by a printing mode parameter passed
* to the constructor. When <code>JTable.PrintMode.NORMAL</code> is used, the
* implementation handles columns in a similar manner to how it handles rows,
* spreading them across multiple pages (in an order consistent with the table's
* <code>ComponentOrientation</code>). When
* <code>JTable.PrintMode.FIT_WIDTH</code> is given, the implementation scales
* the output smaller if necessary, to ensure that all columns fit on the page.
* (Note that width and height are scaled equally, ensuring that the aspect
* ratio remains the same).
* <p>
* The portion of table printed on each page is headed by the appropriate
* section of the table's <code>JTableHeader</code>.
* <p>
* Header and footer text can be added to the output by providing
* <code>MessageFormat</code> instances to the constructor. The printing code
* requests Strings from the formats by calling their <code>format</code> method
* with a single parameter: an <code>Object</code> array containing a single
* element of type <code>Integer</code>, representing the current page number.
* <p>
* There are certain circumstances where this <code>Printable</code> cannot fit
* items appropriately, resulting in clipped output. These are:
* <ul>
* <li>In any mode, when the header or footer text is too wide to fit completely
* in the printable area. The implementation prints as much of the text as
* possible starting from the beginning, as determined by the table's
* <code>ComponentOrientation</code>.
* <li>In any mode, when a row is too tall to fit in the printable area. The
* upper most portion of the row is printed and no lower border is shown.
* <li>In <code>JTable.PrintMode.NORMAL</code> when a column is too wide to fit
* in the printable area. The center of the column is printed and no left and
* right borders are shown.
* </ul>
* <p>
* It is entirely valid for a developer to wrap this <code>Printable</code>
* inside another in order to create complex reports and documents. They may
* even request that different pages be rendered into different sized printable
* areas. The implementation was designed to handle this by performing most of
* its calculations on the fly. However, providing different sizes works best
* when <code>JTable.PrintMode.FIT_WIDTH</code> is used, or when only the
* printable width is changed between pages. This is because when it is printing
* a set of rows in <code>JTable.PrintMode.NORMAL</code> and the implementation
* determines a need to distribute columns across pages, it assumes that all of
* those rows will fit on each subsequent page needed to fit the columns.
* <p>
* It is the responsibility of the developer to ensure that the table is not
* modified in any way after this <code>Printable</code> is created (invalid
* modifications include changes in: size, renderers, or underlying data). The
* behavior of this <code>Printable</code> is undefined if the table is changed
* at any time after creation.
*
* #author Shannon Hickey
* #version 1.41 11/17/05
*/
class MyTablePrintable implements Printable {
/**
* The table to print.
*/
private JTable table;
/**
* For quick reference to the table's header.
*/
private JTableHeader header;
/**
* For quick reference to the table's column model.
*/
private TableColumnModel colModel;
/**
* To save multiple calculations of total column width.
*/
private int totalColWidth;
/**
* The printing mode of this printable.
*/
private JTable.PrintMode printMode;
/**
* Provides the header text for the table.
*/
private MessageFormat[] headerFormat;
/**
* Provides the footer text for the table.
*/
private MessageFormat[] footerFormat;
/**
* The most recent page index asked to print.
*/
private int last = -1;
/**
* The next row to print.
*/
private int row = 0;
/**
* The next column to print.
*/
private int col = 0;
/**
* Used to store an area of the table to be printed.
*/
private final Rectangle clip = new Rectangle(0, 0, 0, 0);
/**
* Used to store an area of the table's header to be printed.
*/
private final Rectangle hclip = new Rectangle(0, 0, 0, 0);
/**
* Saves the creation of multiple rectangles.
*/
private final Rectangle tempRect = new Rectangle(0, 0, 0, 0);
/**
* Vertical space to leave between table and header/footer text.
*/
private static final int H_F_SPACE = 8;
/**
* Font size for the header text.
*/
private static final float HEADER_FONT_SIZE = 15.0f;
/**
* Font size for the footer text.
*/
private static final float FOOTER_FONT_SIZE = 10.0f;
/**
* The font to use in rendering header text.
*/
private Font headerFont;
/**
* The font to use in rendering footer text.
*/
private Font footerFont;
/**
* Create a new <code>TablePrintable</code> for the given
* <code>JTable</code>. Header and footer text can be specified using the
* two <code>MessageFormat</code> parameters. When called upon to provide a
* String, each format is given the current page number.
*
* #param table the table to print
* #param printMode the printing mode for this printable
* #param headerFormat a <code>MessageFormat</code> specifying the text to
* be used in printing a header, or null for none
* #param footerFormat a <code>MessageFormat</code> specifying the text to
* be used in printing a footer, or null for none
* #throws IllegalArgumentException if passed an invalid print mode
*/
public MyTablePrintable(JTable table,
JTable.PrintMode printMode,
MessageFormat[] headerFormat,
MessageFormat[] footerFormat) {
this.table = table;
header = table.getTableHeader();
colModel = table.getColumnModel();
totalColWidth = colModel.getTotalColumnWidth();
if (header != null) {
// the header clip height can be set once since it's unchanging
hclip.height = header.getHeight();
}
this.printMode = printMode;
this.headerFormat = headerFormat;
this.footerFormat = footerFormat;
// derive the header and footer font from the table's font
headerFont = table.getFont().deriveFont(Font.BOLD,
HEADER_FONT_SIZE);
footerFont = table.getFont().deriveFont(Font.PLAIN,
FOOTER_FONT_SIZE);
}
/**
* Prints the specified page of the table into the given {#link Graphics}
* context, in the specified format.
*
* #param graphics the context into which the page is drawn
* #param pageFormat the size and orientation of the page being drawn
* #param pageIndex the zero based index of the page to be drawn
* #return PAGE_EXISTS if the page is rendered successfully, or NO_SUCH_PAGE
* if a non-existent page index is specified
* #throws PrinterException if an error causes printing to be aborted
*/
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
throws PrinterException {
// for easy access to these values
final int imgWidth = (int) pageFormat.getImageableWidth();
final int imgHeight = (int) pageFormat.getImageableHeight();
if (imgWidth <= 0) {
throw new PrinterException("Width of printable area is too small.");
}
// to pass the page number when formatting the header and footer text
Object[] pageNumber = new Object[]{new Integer(pageIndex + 1)};
// fetch the formatted header text, if any
String[] headerText = null;
if (headerFormat != null) {
headerText = new String[headerFormat.length];
for (int i = 0; i < headerFormat.length; i++) {
headerText[i] = headerFormat[i].format(pageNumber);
}
}
// fetch the formatted footer text, if any
String[] footerText = null;
if (footerFormat != null) {
footerText = new String[footerFormat.length];
for (int i = 0; i < footerFormat.length; i++) {
footerText[i] = footerFormat[i].format(pageNumber);
}
}
// to store the bounds of the header and footer text
Rectangle2D[] hRect = null;
Rectangle2D[] fRect = null;
// the amount of vertical space needed for the header and footer text
int headerTextSpace = 15;
int footerTextSpace = 0;
// the amount of vertical space available for printing the table
int availableSpace = imgHeight;
// if there's header text, find out how much space is needed for it
// and subtract that from the available space
if (headerText != null) {
graphics.setFont(headerFont);
hRect = new Rectangle2D[headerText.length];
for (int i = 0; i < headerText.length; i++) {
hRect[i] = graphics.getFontMetrics().getStringBounds(headerText[i], graphics);
headerTextSpace += (int) Math.ceil(hRect[i].getHeight());
}
availableSpace -= headerTextSpace + H_F_SPACE;
}
// if there's footer text, find out how much space is needed for it
// and subtract that from the available space
if (footerText != null) {
graphics.setFont(footerFont);
fRect = new Rectangle2D[footerText.length];
for (int i = 0; i < footerText.length; i++) {
fRect[i] = graphics.getFontMetrics().getStringBounds(footerText[i], graphics);
footerTextSpace += (int) Math.ceil(fRect[i].getHeight());
}
availableSpace -= footerTextSpace + H_F_SPACE;
}
if (availableSpace <= 0) {
throw new PrinterException("Height of printable area is too small.");
}
// depending on the print mode, we may need a scale factor to
// fit the table's entire width on the page
double sf = 1.0D;
if (printMode == JTable.PrintMode.FIT_WIDTH
&& totalColWidth > imgWidth) {
// if not, we would have thrown an acception previously
assert imgWidth > 0;
// it must be, according to the if-condition, since imgWidth > 0
assert totalColWidth > 1;
sf = (double) imgWidth / (double) totalColWidth;
}
// dictated by the previous two assertions
assert sf > 0;
// This is in a loop for two reasons:
// First, it allows us to catch up in case we're called starting
// with a non-zero pageIndex. Second, we know that we can be called
// for the same page multiple times. The condition of this while
// loop acts as a check, ensuring that we don't attempt to do the
// calculations again when we are called subsequent times for the
// same page.
while (last < pageIndex) {
// if we are finished all columns in all rows
if (row >= table.getRowCount() && col == 0) {
return NO_SUCH_PAGE;
}
// rather than multiplying every row and column by the scale factor
// in findNextClip, just pass a width and height that have already
// been divided by it
int scaledWidth = (int) (imgWidth / sf);
int scaledHeight = (int) ((availableSpace - hclip.height) / sf);
// calculate the area of the table to be printed for this page
findNextClip(scaledWidth, scaledHeight);
last++;
}
// translate into the co-ordinate system of the pageFormat
Graphics2D g2d = (Graphics2D) graphics;
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
// to save and store the transform
AffineTransform oldTrans;
// if there's footer text, print it at the bottom of the imageable area
if (footerText != null) {
oldTrans = g2d.getTransform();
g2d.translate(0, imgHeight - footerTextSpace);
for (int i = 0; i < footerText.length; i++) {
printText(g2d, footerText[i], fRect[i], footerFont, i, imgWidth);
}
g2d.setTransform(oldTrans);
}
// if there's header text, print it at the top of the imageable area
// and then translate downwards
if (headerText != null) {
for (int i = 0; i < headerText.length; i++) {
printText(g2d, headerText[i], hRect[i], headerFont, i, imgWidth);
}
g2d.translate(0, headerTextSpace + H_F_SPACE);
}
// constrain the table output to the available space
tempRect.x = 0;
tempRect.y = 0;
tempRect.width = imgWidth;
tempRect.height = availableSpace;
g2d.clip(tempRect);
// if we have a scale factor, scale the graphics object to fit
// the entire width
if (sf != 1.0D) {
g2d.scale(sf, sf);
// otherwise, ensure that the current portion of the table is
// centered horizontally
} else {
int diff = (imgWidth - clip.width) / 2;
g2d.translate(diff, 0);
}
// store the old transform and clip for later restoration
oldTrans = g2d.getTransform();
Shape oldClip = g2d.getClip();
// if there's a table header, print the current section and
// then translate downwards
if (header != null) {
hclip.x = clip.x;
hclip.width = clip.width;
g2d.translate(-hclip.x, 0);
g2d.clip(hclip);
header.print(g2d);
// restore the original transform and clip
g2d.setTransform(oldTrans);
g2d.setClip(oldClip);
// translate downwards
g2d.translate(0, hclip.height);
}
// print the current section of the table
g2d.translate(-clip.x, -clip.y);
g2d.clip(clip);
table.print(g2d);
// restore the original transform and clip
g2d.setTransform(oldTrans);
g2d.setClip(oldClip);
// draw a box around the table
g2d.setColor(Color.BLACK);
g2d.drawRect(0, 0, clip.width, hclip.height + clip.height);
return PAGE_EXISTS;
}
/**
* A helper method that encapsulates common code for rendering the header
* and footer text.
*
* #param g2d the graphics to draw into
* #param text the text to draw, non null
* #param rect the bounding rectangle for this text, as calculated at the
* given font, non null
* #param font the font to draw the text in, non null
* #param imgWidth the width of the area to draw into
*/
private void printText(Graphics2D g2d,
String text,
Rectangle2D rect,
Font font,
int textIndex,
int imgWidth) {
//int tx = -(int)(Math.ceil(rect.getWidth()) - imgWidth); // for right allign
int tx = 0; // for left allign
int ty = textIndex * (int) Math.ceil(Math.abs(rect.getY()));
g2d.setColor(Color.BLACK);
g2d.setFont(font);
g2d.drawString(text, tx, ty);
}
/**
* Calculate the area of the table to be printed for the next page. This
* should only be called if there are rows and columns left to print.
*
* To avoid an infinite loop in printing, this will always put at least one
* cell on each page.
*
* #param pw the width of the area to print in
* #param ph the height of the area to print in
*/
private void findNextClip(int pw, int ph) {
final boolean ltr = table.getComponentOrientation().isLeftToRight();
// if we're ready to start a new set of rows
if (col == 0) {
if (ltr) {
// adjust clip to the left of the first column
clip.x = 0;
} else {
// adjust clip to the right of the first column
clip.x = totalColWidth;
}
// adjust clip to the top of the next set of rows
clip.y += clip.height;
// adjust clip width and height to be zero
clip.width = 0;
clip.height = 0;
// fit as many rows as possible, and at least one
int rowCount = table.getRowCount();
int rowHeight = table.getRowHeight(row);
do {
clip.height += rowHeight;
if (++row >= rowCount) {
break;
}
rowHeight = table.getRowHeight(row);
} while (clip.height + rowHeight <= ph);
}
// we can short-circuit for JTable.PrintMode.FIT_WIDTH since
// we'll always fit all columns on the page
if (printMode == JTable.PrintMode.FIT_WIDTH) {
clip.x = 0;
clip.width = totalColWidth;
return;
}
if (ltr) {
// adjust clip to the left of the next set of columns
clip.x += clip.width;
}
// adjust clip width to be zero
clip.width = 0;
// fit as many columns as possible, and at least one
int colCount = table.getColumnCount();
int colWidth = colModel.getColumn(col).getWidth();
do {
clip.width += colWidth;
if (!ltr) {
clip.x -= colWidth;
}
if (++col >= colCount) {
// reset col to 0 to indicate we're finished all columns
col = 0;
break;
}
colWidth = colModel.getColumn(col).getWidth();
} while (clip.width + colWidth <= pw);
}
}
To print while print buttom clicked
String name = lblCompanyName.getText();
String address = lblAddress.getText();
try {
PrinterJob job = PrinterJob.getPrinterJob();
MessageFormat[] header = new MessageFormat[3];
header[0] = new MessageFormat("line 1");
header[1] = new MessageFormat(" " + name);
header[2] = new MessageFormat(" " + address);
MessageFormat[] footer = new MessageFormat[2];
footer[0] = new MessageFormat("");
footer[1] = new MessageFormat("-{1}-");
job.setPrintable(new MyTablePrintable(table, JTable.PrintMode.FIT_WIDTH, header, footer));
job.printDialog();
job.print();
} catch (Exception ex) {
}
result
Related
So I'm trying to create labels for a generalized purpose by drawing to a Graphics2D object in a BufferedImage and then using ImageIO.write() to write this to a file. Unfortunately I'm encountering an issue where I can't figure out how to make the quality of the Font not 'blurred' (i can't think of the word in English, the edges are a bit cut or jagged, sort of indicative of low render resolution I guess). Is there a way to set it so that the Font renders at a certain DPI/Quality/Resolution?
This is what I get when I render the image. Notice the barcode I generated separately is not 'blurred' per se but the Font is.
/**
*
* #param data Array of Strings passed into label, where data[0] is the string to be barcoded
* #param titles Array of Strings passed into label parallel to data array, containing appropriate titles
* #throws IOException If
* #throws WriterException
*/
public static File createSeedBag(String[] titles,String[] data, Font[] fonts) throws IOException, WriterException{
/**
* TODO: Array of Fonts to customize Font
* Size customization
*/
BufferedImage theLabel;
BufferedImage theBarcode;
Graphics2D theGraphics;
File imageFile,
theFile;
FontMetrics titleMetrics,
theMetrics;
int width = 300;
int height = 450;
theLabel = new BufferedImage(width,height, BufferedImage.TYPE_BYTE_GRAY);
imageFile = LabelPrinter.writeBarCode(data[0], BarcodeSpecs.LARGE);
theGraphics = theLabel.createGraphics();
theGraphics.setPaint(Color.WHITE);
theGraphics.fillRect(0, 0, theLabel.getWidth(), theLabel.getHeight());
theGraphics.setPaint(Color.BLACK);
theGraphics.drawRect(0, 0, theLabel.getWidth()-1, theLabel.getHeight()-1);
/**
* Font objects
*/
fonts[0] = new Font("Bodoni 72", Font.BOLD, 40);
theGraphics.setFont(fonts[0]);
/**
* 1) Read Image File and set to bottom of label
*
* 2)
*/
theBarcode = ImageIO.read(imageFile);
theGraphics.drawImage(theBarcode, 1, height - 61, null);
titleMetrics = theGraphics.getFontMetrics(fonts[0]);
theGraphics.drawString(data[data.length-1], (width/2) - ((titleMetrics.stringWidth(data[data.length-1])) / 2), 50);
fonts[1]= new Font("Bodoni 72",0,20);
theMetrics = theGraphics.getFontMetrics(fonts[1]);
int stringHeight = theMetrics.getAscent();
theGraphics.setFont(fonts[1]);
for(int i = 1; i< data.length; i++) {
theGraphics.drawString(titles[i] + ": " + data[i], 5, 50 + (stringHeight * (i+1)));
}
theFile = new File("currentLabel.png");
ImageIO.write(theLabel, "png", theFile);
imageFile.deleteOnExit();
return theFile;
}
I'm using RSyntaxTextArea for a minimized IDE I'm working on, Everything seems to be really working smoothly except for the line numbering, which I couldn't really make it show:
RSyntaxTextArea textArea = new RSyntaxTextArea(20, 60);
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_C);
textArea.setCodeFoldingEnabled(true);
textArea.setAntiAliasingEnabled(true);
RTextScrollPane sp = new RTextScrollPane(textArea);
sp.setLineNumbersEnabled(true);
sp.setFoldIndicatorEnabled(true);
if ( sp.getLineNumbersEnabled() )
{
System.out.println("Enabled"); // it prints the line but it's not showing
}
contentPane.add(/*textEditorScrollPane*/ textArea, BorderLayout.CENTER);
I can't figure out why it's not showing the line numbers..
It's not showing the scrollbars either, right? Assuming that contentPane is where you want your components, you need to add the RTextScrollPane instance to the contentPane, not the RSyntaxTextArea instance. The Gutter, which displays line numbers, is a part of the RTextScrollPane - an extended JScrollPane.
If you don't add a scroll pane to your GUI, it will not be shown, nor will you be able to scroll around. :P
So try the following:
contentPane.add(sp, BorderLayout.CENTER);
Alternatively, you can use the following class:
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
/**
* This class will display line numbers for a related text component. The text
* component must use the same line height for each line. TextLineNumber
* supports wrapped lines and will highlight the line number of the current
* line in the text component.
* <p>
* This class was designed to be used as a component added to the row header
* of a JScrollPane.
*/
public class TextLineNumber extends JPanel
implements CaretListener, DocumentListener, PropertyChangeListener
{
public final static float LEFT = 0.0f;
public final static float CENTER = 0.5f;
public final static float RIGHT = 1.0f;
private final static Border OUTER = new MatteBorder(0, 0, 0, 2, Color.GRAY);
private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
// Text component this TextTextLineNumber component is in sync with
private JTextComponent component;
// Properties that can be changed
private boolean updateFont;
private int borderGap;
private Color currentLineForeground;
private float digitAlignment;
private int minimumDisplayDigits;
// Keep history information to reduce the number of times the component
// needs to be repainted
private int lastDigits;
private int lastHeight;
private int lastLine;
private HashMap<String, FontMetrics> fonts;
/**
* Create a line number component for a text component. This minimum
* display width will be based on 3 digits.
*
* #param component the related text component
*/
public TextLineNumber(JTextComponent component)
{
this(component, 3);
}
/**
* Create a line number component for a text component.
*
* #param component the related text component
* #param minimumDisplayDigits the number of digits used to calculate
* the minimum width of the component
*/
public TextLineNumber(JTextComponent component, int minimumDisplayDigits)
{
this.component = component;
setFont(component.getFont());
setBorderGap(5);
setCurrentLineForeground(Color.RED);
setDigitAlignment(RIGHT);
setMinimumDisplayDigits(minimumDisplayDigits);
component.getDocument().addDocumentListener(this);
component.addCaretListener(this);
component.addPropertyChangeListener("font", this);
}
/**
* Gets the update font property
*
* #return the update font property
*/
public boolean getUpdateFont()
{
return updateFont;
}
/**
* Set the update font property. Indicates whether this Font should be
* updated automatically when the Font of the related text component
* is changed.
*
* #param updateFont when true update the Font and repaint the line
* numbers, otherwise just repaint the line numbers.
*/
public void setUpdateFont(boolean updateFont)
{
this.updateFont = updateFont;
}
/**
* Gets the border gap
*
* #return the border gap in pixels
*/
public int getBorderGap()
{
return borderGap;
}
/**
* The border gap is used in calculating the left and right insets of the
* border. Default value is 5.
*
* #param borderGap the gap in pixels
*/
public void setBorderGap(int borderGap)
{
this.borderGap = borderGap;
Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
setBorder(new CompoundBorder(OUTER, inner));
lastDigits = 0;
setPreferredWidth();
}
/**
* Gets the current line rendering Color
*
* #return the Color used to render the current line number
*/
public Color getCurrentLineForeground()
{
return currentLineForeground == null ? getForeground() : currentLineForeground;
}
/**
* The Color used to render the current line digits. Default is Coolor.RED.
*
* #param currentLineForeground the Color used to render the current line
*/
public void setCurrentLineForeground(Color currentLineForeground)
{
this.currentLineForeground = currentLineForeground;
}
/**
* Gets the digit alignment
*
* #return the alignment of the painted digits
*/
public float getDigitAlignment()
{
return digitAlignment;
}
/**
* Specify the horizontal alignment of the digits within the component.
* Common values would be:
* <ul>
* <li>TextLineNumber.LEFT
* <li>TextLineNumber.CENTER
* <li>TextLineNumber.RIGHT (default)
* </ul>
*
* #param currentLineForeground the Color used to render the current line
*/
public void setDigitAlignment(float digitAlignment)
{
this.digitAlignment =
digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment;
}
/**
* Gets the minimum display digits
*
* #return the minimum display digits
*/
public int getMinimumDisplayDigits()
{
return minimumDisplayDigits;
}
/**
* Specify the mimimum number of digits used to calculate the preferred
* width of the component. Default is 3.
*
* #param minimumDisplayDigits the number digits used in the preferred
* width calculation
*/
public void setMinimumDisplayDigits(int minimumDisplayDigits)
{
this.minimumDisplayDigits = minimumDisplayDigits;
setPreferredWidth();
}
/**
* Calculate the width needed to display the maximum line number
*/
private void setPreferredWidth()
{
Element root = component.getDocument().getDefaultRootElement();
int lines = root.getElementCount();
int digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits);
// Update sizes when number of digits in the line number changes
if (lastDigits != digits)
{
lastDigits = digits;
FontMetrics fontMetrics = getFontMetrics(getFont());
int width = fontMetrics.charWidth('0') * digits;
Insets insets = getInsets();
int preferredWidth = insets.left + insets.right + width;
Dimension d = getPreferredSize();
d.setSize(preferredWidth, HEIGHT);
setPreferredSize(d);
setSize(d);
}
}
/**
* Draw the line numbers
*/
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// Determine the width of the space available to draw the line number
FontMetrics fontMetrics = component.getFontMetrics(component.getFont());
Insets insets = getInsets();
int availableWidth = getSize().width - insets.left - insets.right;
// Determine the rows to draw within the clipped bounds.
Rectangle clip = g.getClipBounds();
int rowStartOffset = component.viewToModel(new Point(0, clip.y));
int endOffset = component.viewToModel(new Point(0, clip.y + clip.height));
while (rowStartOffset <= endOffset)
{
try
{
if (isCurrentLine(rowStartOffset))
{
g.setColor(getCurrentLineForeground());
} else
{
g.setColor(getForeground());
}
// Get the line number as a string and then determine the
// "X" and "Y" offsets for drawing the string.
String lineNumber = getTextLineNumber(rowStartOffset);
int stringWidth = fontMetrics.stringWidth(lineNumber);
int x = getOffsetX(availableWidth, stringWidth) + insets.left;
int y = getOffsetY(rowStartOffset, fontMetrics);
g.drawString(lineNumber, x, y);
// Move to the next row
rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;
} catch (Exception e)
{
break;
}
}
}
/*
* We need to know if the caret is currently positioned on the line we
* are about to paint so the line number can be highlighted.
*/
private boolean isCurrentLine(int rowStartOffset)
{
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
if (root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition))
{
return true;
} else
{
return false;
}
}
/*
* Get the line number to be drawn. The empty string will be returned
* when a line of text has wrapped.
*/
protected String getTextLineNumber(int rowStartOffset)
{
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
if (line.getStartOffset() == rowStartOffset)
{
return String.valueOf(index + 1);
} else
{
return "";
}
}
/*
* Determine the X offset to properly align the line number when drawn
*/
private int getOffsetX(int availableWidth, int stringWidth)
{
return (int) ((availableWidth - stringWidth) * digitAlignment);
}
/*
* Determine the Y offset for the current row
*/
private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics)
throws BadLocationException
{
// Get the bounding rectangle of the row
Rectangle r = component.modelToView(rowStartOffset);
int lineHeight = fontMetrics.getHeight();
int y = r.y + r.height;
int descent = 0;
// The text needs to be positioned above the bottom of the bounding
// rectangle based on the descent of the font(s) contained on the row.
if (r.height == lineHeight) // default font is being used
{
descent = fontMetrics.getDescent();
} else // We need to check all the attributes for font changes
{
if (fonts == null)
{
fonts = new HashMap<>();
}
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
for (int i = 0; i < line.getElementCount(); i++)
{
Element child = line.getElement(i);
AttributeSet as = child.getAttributes();
String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
String key = fontFamily + fontSize;
FontMetrics fm = fonts.get(key);
if (fm == null)
{
Font font = new Font(fontFamily, Font.PLAIN, fontSize);
fm = component.getFontMetrics(font);
fonts.put(key, fm);
}
descent = Math.max(descent, fm.getDescent());
}
}
return y - descent;
}
//
// Implement CaretListener interface
//
#Override
public void caretUpdate(CaretEvent e)
{
// Get the line the caret is positioned on
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
int currentLine = root.getElementIndex(caretPosition);
// Need to repaint so the correct line number can be highlighted
if (lastLine != currentLine)
{
repaint();
lastLine = currentLine;
}
}
//
// Implement DocumentListener interface
//
#Override
public void changedUpdate(DocumentEvent e)
{
documentChanged();
}
#Override
public void insertUpdate(DocumentEvent e)
{
documentChanged();
}
#Override
public void removeUpdate(DocumentEvent e)
{
documentChanged();
}
/*
* A document change may affect the number of displayed lines of text.
* Therefore the lines numbers will also change.
*/
private void documentChanged()
{
// View of the component has not been updated at the time
// the DocumentEvent is fired
SwingUtilities.invokeLater(() ->
{
try
{
int endPos = component.getDocument().getLength();
Rectangle rect = component.modelToView(endPos);
if (rect != null && rect.y != lastHeight)
{
setPreferredWidth();
repaint();
lastHeight = rect.y;
}
} catch (BadLocationException ex)
{ /* nothing to do */ }
});
}
//
// Implement PropertyChangeListener interface
//
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getNewValue() instanceof Font)
{
if (updateFont)
{
Font newFont = (Font) evt.getNewValue();
setFont(newFont);
lastDigits = 0;
setPreferredWidth();
} else
{
repaint();
}
}
}
}
Then line numbers can be added like this:
TextLineNumber textLineNumber = new TextLineNumber(sourceCodeArea);
sourceCodeAreaScrollPane.setRowHeaderView(textLineNumber);
I am developing photography apps in that I overlay an image with text.
Here is my code:
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.themes11);
// create a mutable bitmap with the same size as the background image's size
bmOverlay = Bitmap.createBitmap(mBitmap.getWidth(),
mBitmap.getHeight(), Bitmap.Config.ARGB_4444);
// create a canvas on which to draw
Canvas canvas = new Canvas(bmOverlay);
TextPaint paint = new TextPaint();
paint.setColor(Color.RED);
paint.setTextSize(40);
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
// if the background image is defined in main.xml, omit this line
canvas.drawBitmap(mBitmap, 0, 0, null);
// draw the text and the point
canvas.drawPoint(50, 100, paint);
// canvas.drawText(InstaTextActivity.CurrentWord, 300, 200, paint);
StaticLayout layout = new StaticLayout(InstaTextActivity.CurrentWord,
paint, display.getHeight(),
android.text.Layout.Alignment.ALIGN_NORMAL, (float) 1.0,
(float) 0.0, true);
canvas.translate(width / 5, height / 5);
layout.draw(canvas);
imageview_img.setImageBitmap(bmOverlay);
In this code I overlay the text on screen width/2 and height/2 it will display on top the image but I want the text to be center-aligned. Also when I write a large text it will align form center to right.
Have a look at the images to see how I want it:
The Image Background:
And the result I want:
Use the below methods to measure the height and width of the text.
Then when drawing the text on canvas
left = width/2 - textWidth/2
top = height/2 - textHeight/2
But if you need a multiple line text for long texts, it will be a bit tricky.
/**
* Method to get the height of the paint
*
* #param brush The TextPaint used to paint the text
* #param text The text which needs to be measured
* #return height of the text
*/
public static int measureTextHeight(Paint brush, String text) {
Rect result = new Rect();
// Measure the text rectangle to get the height
brush.getTextBounds(text, 0, text.length(), result);
return result.height();
}
/**
* Method to get the width of the paint
*
* #param brush The TextPaint used to paint the text
* #param text The text which needs to be measured
* #return width of the text
*/
public static int measureTextWidth(Paint brush, String text) {
Rect result = new Rect();
// Measure the text rectangle to get the height
brush.getTextBounds(text, 0, text.length(), result);
return result.width();
}
i using this. working for me.
public Bitmap drawMultilineTextToBitmap(Context gContext,
int gResId,
String gText) {
// prepare canvas
Resources resources = gContext.getResources();
float scale = resources.getDisplayMetrics().density;
Bitmap bitmap = BitmapFactory.decodeResource(resources, gResId);
android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();
// set default bitmap config if none
if(bitmapConfig == null) {
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
// resource bitmaps are imutable,
// so we need to convert it to mutable one
bitmap = bitmap.copy(bitmapConfig, true);
Canvas canvas = new Canvas(bitmap);
// new antialiased Paint
TextPaint paint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
// text color - #3D3D3D
paint.setColor(Color.rgb(61, 61, 61));
// text size in pixels
paint.setTextSize((int) (14 * scale));
// text shadow
paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);
// set text width to canvas width minus 16dp padding
int textWidth = canvas.getWidth() - (int) (16 * scale);
// init StaticLayout for text
StaticLayout textLayout = new StaticLayout(
gText, paint, textWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
// get height of multiline text
int textHeight = textLayout.getHeight();
// get position of text's top left corner
float x = (bitmap.getWidth() - textWidth)/2;
float y = (bitmap.getHeight() - textHeight)/2;
// draw text to the Canvas center
canvas.save();
canvas.translate(x, y);
textLayout.draw(canvas);
canvas.restore();
return bitmap;
}
source : http://www.skoumal.net/en/android-drawing-multiline-text-on-bitmap/
This is not a very refined approach(Not optimized for memory usage) but does the job. You will need to modify the code in order to split at the string keeping words intact. Hope this helps.
package org.edu.abhi;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
/**
* #author Abhishek_Nandi
*
*/
public class DummyUserSpace extends View {
/***************************************************************************
* Calls superclass constructor. This will call {#link #init(Context)}
*
* #param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
**************************************************************************/
public DummyUserSpace(Context context) {
super(context);
init(context);
}
/***************************************************************************
* Calls superclass constructor. This will call {#link #init(Context)}
*
* #param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
* #param attrs
* The attributes of the XML tag that is inflating the view.
* #param defStyle
* The default style to apply to this view. If 0, no style will
* be applied (beyond what is included in the theme). This may
* either be an attribute resource, whose value will be retrieved
* from the current theme, or an explicit style resource.
**************************************************************************/
public DummyUserSpace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
/***************************************************************************
* Calls superclass constructor. This will call {#link #init(Context)}
*
* #param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
* #param attrs
* The attributes of the XML tag that is inflating the view.
**************************************************************************/
public DummyUserSpace(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* Width of the View
*/
int viewWidth = 0;
/**
* Height of the View
*/
int viewHeight = 0;
/**
* {#link Paint} which is used to draw {#link #text}
*/
Paint textPaint;
/**
* The text which needs to be drawn
*/
String text = "can be spared the burden of having to perform manual memory management. In some languages, memory for the creation of objects is implicitly allocated on the stack, or explicitly allocated and deallocated from the heap. In the latter case the responsibility of managing memory resides with the programmer. If the program does not deallocate an object, a memory leak occurs. If the program attempts to access or deallocate memory that has already been deallocated, the result is undefined and difficult to predict, and the program is likely to become unstable and/or crash. This can be partially remedied by the use of smart pointers, but these add overhead and complexity. Note that garbage collection does not prevent \"logical\" memory leaks, i.e. those where the memory is still referenced but never used.. o have as few implementation dependencies as possible. It is intended to let application developers \"write once, run anywhere\" (WORA), meaning that code that runs on one platform does not need to be recompiled to run on another. Java is as of 2012 one of the most popular programming languages in use, particularly for client-server web applications, with a reported 10 million users";
/**
* Common constructor routine
*
* #param context
* The Context the view is running in. It is used to get the
* density of the device
*/
private void init(Context context) {
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(40);
textPaint.setStrokeWidth(6.0f);
WindowManager manager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(outMetrics);
padding *= outMetrics.density;
lineSpacing *= outMetrics.density;
}
/**
* Split the string and compute the {#link #startY}, {#link #textHeight}
* which is required for drawing the view
*/
private void prepareForDrawing() {
viewWidth = getWidth();
viewHeight = getHeight();
if (viewHeight == 0) {
return;
}
textHeight = Utility.measureTextHeight(textPaint, text);
if (0 == textHeight) {
return;
}
maxLines = (viewHeight - 2 * padding) / (textHeight + lineSpacing);
prepareMultiLineText(textPaint, text);
int noOfLines = splittedText.size();
int section = noOfLines / 2;
int center = viewHeight / 2;
if (noOfLines % 2 == 0) {
center -= lineSpacing / 2;
} else {
center -= textHeight / 2;
}
startY = center - (section - 1) * (textHeight + lineSpacing)
+ lineSpacing;
}
/**
* The starting position from where the view will start drawing
*/
private int startY = 0;
/**
* Height of the text
*/
private int textHeight = 0;
/**
* Maximum lines this view can hold
*/
private int maxLines = 1;
/**
* Padding from the view border
*/
private int padding = 20;
/**
* Spacing between each line
*/
private int lineSpacing = 10;
private final String loadingText = "Loading..";
/*
* (non-Javadoc)
*
* #see android.view.View#onDraw(android.graphics.Canvas)
*/
#Override
protected void onDraw(Canvas canvas) {
if (!computationComplete) {
new DrawComputations().execute();
canvas.drawText(
loadingText,
getWidth() / 2
- Utility.measureTextWidth(textPaint, loadingText)
/ 2,
getHeight() / 2
- Utility.measureTextHeight(textPaint, loadingText)
/ 2, textPaint);
} else {
for(int count = 0; count<splittedText.size() ; count++){
String trimmed = splittedText.get(count);
//System.out.println(trimmed);
canvas.drawText(
trimmed,
viewWidth / 2
- Utility.measureTextWidth(textPaint, trimmed)
/ 2, startY + (count - 1)
* (textHeight + lineSpacing), textPaint);
}
}
}
/**
* Denotes the computation has completed and the view is ready to be drawn
*/
boolean computationComplete = false;
/**
* This {#link AsyncTask} performs the computation in background and updates
* the view when computation is finished and the view is ready to be drawn
*
* #author Abhishek_nandi
* #version 1.0
*/
class DrawComputations extends AsyncTask<Void, Void, Void> {
/*
* (non-Javadoc)
*
* #see android.os.AsyncTask#doInBackground(Params[])
*/
#Override
protected Void doInBackground(Void... params) {
prepareForDrawing();
computationComplete = true;
return null;
}
/*
* (non-Javadoc)
*
* #see android.os.AsyncTask#onPostExecute(java.lang.Object)
*/
#Override
protected void onPostExecute(Void result) {
invalidate();
}
}
/**
* Collection which holds the multi-line text
*/
private List<String> splittedText = new ArrayList<String>();
/**
* This method is responsible for stripping {#link #text} into individual
* lines in order to form the multi line text. This method can be used as a
* utility.
*
* #param paint
* The {#link Paint} which is used to draw the text
* #param str
* The string which needs to be stripped
*/
private void prepareMultiLineText(Paint paint, String str) {
if (str == null || str.trim().length() == 0)
return;
str = str.trim();
String result = str;
int boxWidth = viewWidth;
try {
float textWidth = paint.measureText(str) + 2 * padding;
result = str;
while (textWidth > boxWidth) {
if (result.length() == 0)
break;//keeping the entire word intact//if(result.lastIndexOf(" ")!=-1){result = result.substring(0, result.lastIndexOf(" "))}else
result = result.substring(0, result.length() - 1);
textWidth = paint.measureText(result) + 2 * padding;
}
result = result.trim();
boolean exceeded = false;
if (splittedText.size() == maxLines) {
exceeded = true;
result = result.substring(0, result.length()-2).concat("..");
}
splittedText.add(result);
if (!exceeded && result.length() != str.length()) {
prepareMultiLineText(textPaint, str.substring(result.length()));
}
} catch (Exception e) {
Log.e("CustomView", "prepareMultiLineText", e);
}
}
} // END of class
// END of file
You should use StaticLayout for this. It measures and draws multiline text, handling line wrapping, line spacing, alignment etc.
StaticLayout layout = new StaticLayout("your long text", textPaint,
pixelsToFitWidthTo, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, true);
int boxHeight = layout.getHeight(); // measure
The text can even be HTML formatted, just wrap your text in Html.fromHtml():
Html.fromHtml("your long <b>and bold</b> text");
I have a table called table and its filled with data, I also have a MessageFormat header I want to use as a header to print the JTable this is the MessageFormat:
MessageFormat header = new MessageFormat("Product: "
+ task.getProductName() + " Job: "
+ task.getJobNumber() + " Task: " + task.getTaskID()
);
I want to print 3 lines in the header, one for Product, Job and Task
the way I print this table is like so:
table.print(JTable.PrintMode.FIT_WIDTH, header, null);
I can't seem to figure out how to print the header in 3 seperate lines, I tried using the \n to make a new line but that doesn't seem to work.
It's gonna be long answer (code wise) because the only solution I found was to implement a custom Printable. Of course I didn't write the following code myself, I mostly copied the code I extracted from the jdk sources and made some adjustments.
Here we are:
This is the way you said you invoke the print method:
DefaultTableModel dtm = new DefaultTableModel(new String[] { "Column 1" }, 1);
JTable table = new JTable(dtm) {
#Override
public Printable getPrintable(PrintMode printMode, MessageFormat headerFormat, MessageFormat footerFormat) {
return new TablePrintable(this, printMode, headerFormat, footerFormat);
}
};
where TablePrintable is the following class (sorry for not being concise here):
static class TablePrintable implements Printable {
private final JTable table;
private final JTableHeader header;
private final TableColumnModel colModel;
private final int totalColWidth;
private final JTable.PrintMode printMode;
private final MessageFormat headerFormat;
private final MessageFormat footerFormat;
private int last = -1;
private int row = 0;
private int col = 0;
private final Rectangle clip = new Rectangle(0, 0, 0, 0);
private final Rectangle hclip = new Rectangle(0, 0, 0, 0);
private final Rectangle tempRect = new Rectangle(0, 0, 0, 0);
private static final int H_F_SPACE = 8;
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;
public TablePrintable(JTable table, JTable.PrintMode printMode, MessageFormat headerFormat,
MessageFormat footerFormat) {
this.table = table;
header = table.getTableHeader();
colModel = table.getColumnModel();
totalColWidth = colModel.getTotalColumnWidth();
if (header != null) {
// the header clip height can be set once since it's unchanging
hclip.height = header.getHeight();
}
this.printMode = printMode;
this.headerFormat = headerFormat;
this.footerFormat = footerFormat;
// derive the header and footer font from the table's font
headerFont = table.getFont().deriveFont(Font.BOLD, HEADER_FONT_SIZE);
footerFont = table.getFont().deriveFont(Font.PLAIN, FOOTER_FONT_SIZE);
}
#Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
// for easy access to these values
final int imgWidth = (int) pageFormat.getImageableWidth();
final int imgHeight = (int) pageFormat.getImageableHeight();
if (imgWidth <= 0) {
throw new PrinterException("Width of printable area is too small.");
}
// to pass the page number when formatting the header and footer
// text
Object[] pageNumber = new Object[] { Integer.valueOf(pageIndex + 1) };
// fetch the formatted header text, if any
String headerText = null;
if (headerFormat != null) {
headerText = headerFormat.format(pageNumber);
}
// fetch the formatted footer text, if any
String footerText = null;
if (footerFormat != null) {
footerText = footerFormat.format(pageNumber);
}
// to store the bounds of the header and footer text
Rectangle2D hRect = null;
Rectangle2D fRect = null;
// the amount of vertical space needed for the header and footer
// text
int headerTextSpace = 0;
int footerTextSpace = 0;
// the amount of vertical space available for printing the table
int availableSpace = imgHeight;
// if there's header text, find out how much space is needed for it
// and subtract that from the available space
if (headerText != null) {
graphics.setFont(headerFont);
int nbLines = headerText.split("\n").length;
hRect = graphics.getFontMetrics().getStringBounds(headerText, graphics);
hRect = new Rectangle2D.Double(hRect.getX(), Math.abs(hRect.getY()), hRect.getWidth(),
hRect.getHeight() * nbLines);
headerTextSpace = (int) Math.ceil(hRect.getHeight() * nbLines);
availableSpace -= headerTextSpace + H_F_SPACE;
}
// if there's footer text, find out how much space is needed for it
// and subtract that from the available space
if (footerText != null) {
graphics.setFont(footerFont);
fRect = graphics.getFontMetrics().getStringBounds(footerText, graphics);
footerTextSpace = (int) Math.ceil(fRect.getHeight());
availableSpace -= footerTextSpace + H_F_SPACE;
}
if (availableSpace <= 0) {
throw new PrinterException("Height of printable area is too small.");
}
// depending on the print mode, we may need a scale factor to
// fit the table's entire width on the page
double sf = 1.0D;
if (printMode == JTable.PrintMode.FIT_WIDTH && totalColWidth > imgWidth) {
// if not, we would have thrown an acception previously
assert imgWidth > 0;
// it must be, according to the if-condition, since imgWidth > 0
assert totalColWidth > 1;
sf = (double) imgWidth / (double) totalColWidth;
}
// dictated by the previous two assertions
assert sf > 0;
// This is in a loop for two reasons:
// First, it allows us to catch up in case we're called starting
// with a non-zero pageIndex. Second, we know that we can be called
// for the same page multiple times. The condition of this while
// loop acts as a check, ensuring that we don't attempt to do the
// calculations again when we are called subsequent times for the
// same page.
while (last < pageIndex) {
// if we are finished all columns in all rows
if (row >= table.getRowCount() && col == 0) {
return NO_SUCH_PAGE;
}
// rather than multiplying every row and column by the scale
// factor
// in findNextClip, just pass a width and height that have
// already
// been divided by it
int scaledWidth = (int) (imgWidth / sf);
int scaledHeight = (int) ((availableSpace - hclip.height) / sf);
// calculate the area of the table to be printed for this page
findNextClip(scaledWidth, scaledHeight);
last++;
}
// create a copy of the graphics so we don't affect the one given to
// us
Graphics2D g2d = (Graphics2D) graphics.create();
// translate into the co-ordinate system of the pageFormat
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
// to save and store the transform
AffineTransform oldTrans;
// if there's footer text, print it at the bottom of the imageable
// area
if (footerText != null) {
oldTrans = g2d.getTransform();
g2d.translate(0, imgHeight - footerTextSpace);
String[] lines = footerText.split("\n");
printText(g2d, lines, fRect, footerFont, imgWidth);
g2d.setTransform(oldTrans);
}
// if there's header text, print it at the top of the imageable area
// and then translate downwards
if (headerText != null) {
String[] lines = headerText.split("\n");
printText(g2d, lines, hRect, headerFont, imgWidth);
g2d.translate(0, headerTextSpace + H_F_SPACE);
}
// constrain the table output to the available space
tempRect.x = 0;
tempRect.y = 0;
tempRect.width = imgWidth;
tempRect.height = availableSpace;
g2d.clip(tempRect);
// if we have a scale factor, scale the graphics object to fit
// the entire width
if (sf != 1.0D) {
g2d.scale(sf, sf);
// otherwise, ensure that the current portion of the table is
// centered horizontally
} else {
int diff = (imgWidth - clip.width) / 2;
g2d.translate(diff, 0);
}
// store the old transform and clip for later restoration
oldTrans = g2d.getTransform();
Shape oldClip = g2d.getClip();
// if there's a table header, print the current section and
// then translate downwards
if (header != null) {
hclip.x = clip.x;
hclip.width = clip.width;
g2d.translate(-hclip.x, 0);
g2d.clip(hclip);
header.print(g2d);
// restore the original transform and clip
g2d.setTransform(oldTrans);
g2d.setClip(oldClip);
// translate downwards
g2d.translate(0, hclip.height);
}
// print the current section of the table
g2d.translate(-clip.x, -clip.y);
g2d.clip(clip);
table.print(g2d);
// restore the original transform and clip
g2d.setTransform(oldTrans);
g2d.setClip(oldClip);
// draw a box around the table
g2d.setColor(Color.BLACK);
g2d.drawRect(0, 0, clip.width, hclip.height + clip.height);
// dispose the graphics copy
g2d.dispose();
return PAGE_EXISTS;
}
private void printText(Graphics2D g2d, String[] lines, Rectangle2D rect, Font font, int imgWidth) {
g2d.setColor(Color.BLACK);
g2d.setFont(font);
for (int i = 0; i < lines.length; i++) {
int tx;
// if the text is small enough to fit, center it
if (rect.getWidth() < imgWidth) {
tx = (int) (imgWidth / 2 - g2d.getFontMetrics().getStringBounds(lines[i], g2d).getWidth() / 2);
// otherwise, if the table is LTR, ensure the left side of
// the text shows; the right can be clipped
} else if (table.getComponentOrientation().isLeftToRight()) {
tx = 0;
// otherwise, ensure the right side of the text shows
} else {
tx = -(int) (Math.ceil(rect.getWidth()) - imgWidth);
}
int ty = (int) Math.ceil(Math.abs(rect.getY() + i * rect.getHeight() / lines.length));
g2d.drawString(lines[i], tx, ty);
}
}
private void findNextClip(int pw, int ph) {
final boolean ltr = table.getComponentOrientation().isLeftToRight();
// if we're ready to start a new set of rows
if (col == 0) {
if (ltr) {
// adjust clip to the left of the first column
clip.x = 0;
} else {
// adjust clip to the right of the first column
clip.x = totalColWidth;
}
// adjust clip to the top of the next set of rows
clip.y += clip.height;
// adjust clip width and height to be zero
clip.width = 0;
clip.height = 0;
// fit as many rows as possible, and at least one
int rowCount = table.getRowCount();
int rowHeight = table.getRowHeight(row);
do {
clip.height += rowHeight;
if (++row >= rowCount) {
break;
}
rowHeight = table.getRowHeight(row);
} while (clip.height + rowHeight <= ph);
}
// we can short-circuit for JTable.PrintMode.FIT_WIDTH since
// we'll always fit all columns on the page
if (printMode == JTable.PrintMode.FIT_WIDTH) {
clip.x = 0;
clip.width = totalColWidth;
return;
}
if (ltr) {
// adjust clip to the left of the next set of columns
clip.x += clip.width;
}
// adjust clip width to be zero
clip.width = 0;
// fit as many columns as possible, and at least one
int colCount = table.getColumnCount();
int colWidth = colModel.getColumn(col).getWidth();
do {
clip.width += colWidth;
if (!ltr) {
clip.x -= colWidth;
}
if (++col >= colCount) {
// reset col to 0 to indicate we're finished all columns
col = 0;
break;
}
colWidth = colModel.getColumn(col).getWidth();
} while (clip.width + colWidth <= pw);
}
}
And here is the result (I hope that's what you expect):
You could try
StringBuilder builder = new StringBuilder();
builder.append("Product: ");
builder.append(task.getProductName());
builder.append(System.getProperty("line.separator"));
builder.append("Job: ");
builder.append(task.getJobNumber());
builder.append(System.getProperty("line.separator"));
builder.append("Task: ");
builder.append(task.getTaskID();
MessageFormat header = new MessageFormat(builder.toString());
If this doesn't work, then you're going to have to set up your own printer job, and layout the header precisely as you want it.
If you use the getPrintable method instead without adding a header/footer text, you can then include/decorate the returned Printable in one where you have more control over the header, and where you can specify multi-line headers. See the javadoc of that method which mentions
It is entirely valid for this Printable to be wrapped inside another in order to create complex reports and documents. You may even request that different pages be rendered into different sized printable areas. The implementation must be prepared to handle this (possibly by doing its layout calculations on the fly). However, providing different heights to each page will likely not work well with PrintMode.NORMAL when it has to spread columns across pages.
I have not enough experience with Printables to help you further on how to actually do this
Basically, the answer of #aymeric is correct: there's no way around a custom printable implementation. A way to do it with slightly less c&p is to have a custom implementation that
takes over header/footer printing
delegates to table printing itself to the default printable
The trick in that approach is to fool the delegate tablePrintable into believing that the page is smaller than it actually is, with a custom pageFormat
more details (and code)
I've utilized two MessageFormat arrays as a neat solution to the problem. You'll find below a printout of the end result:
The code is outlined below:
try
{
PrinterJob job = PrinterJob.getPrinterJob();
MessageFormat[] header = new MessageFormat[6];
// Assign the arrays with 6 String values for the headers
header[0] = new MessageFormat("");
header[1] = new MessageFormat(theExamSelection);
header[2] = new MessageFormat("");
header[3] = new MessageFormat("Scrud 60 - Grade Returns - Random Sample");
header[4] = new MessageFormat("");
header[5] = new MessageFormat(theSubjectSelection+" - "+theLevelSelection+" - "+thePaperSelection);
MessageFormat[] footer = new MessageFormat[4];
// Assign the 4 Strings to the footer array
footer[0] = new MessageFormat("Assistant Examiner Signature:______________ Date:___ /___ /_____ ");
footer[1] = new MessageFormat("");
footer[2] = new MessageFormat("");
footer[3] = new MessageFormat("Advising Examiner Signature:______________ Date:___ /___ /_____ ");
//here you place the JTable to print
// in this case its called randomSample_gradeBreakdown_jTable
// along with the header and footer arrays
job.setPrintable(new PrintTableMultiLine(randomSample_gradeBreakdown_jTable, JTable.PrintMode.FIT_WIDTH, header, footer ));
job.print();
}
catch (java.awt.print.PrinterException e)
{
System.err.format("Cannot print %s%n", e.getMessage());
JOptionPane.showMessageDialog(this,
"Check that your printer is working correctly","PRINT ERROR",JOptionPane.ERROR_MESSAGE
);
}
I need to print the data in a JTable without any borders around, I have tried to achieve this by using an empty border for the JTable but still it prints a border around the table.
dataTable = new javax.swing.JTable();
dataTable.setBorder(BorderFactory.createEmptyBorder());
dataTable.setShowHorizontalLines(false); dataTable.setShowVerticalLines(false);
dataTable.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { }, new String [] { "Item ID", "Company Name", "Qty.", "Price", "Total" } ));
jScrollPane1.setViewportView(dataTable);
jScrollPane1.setViewportBorder(BorderFactory.createEmptyBorder());
jScrollPane1.setBorder(BorderFactory.createEmptyBorder());
You can copy the Code from TablePrintable to a own class (MyPrintable) and remove following lines:
// draw a box around the table
g2d.setColor(Color.BLACK);
g2d.drawRect(0, 0, clip.width, hclip.height + clip.height);
And Override the getPrintable-Method in your JTable.
dataTable = new javax.swing.JTable(){
#Override
public Printable getPrintable( PrintMode printMode, MessageFormat headerFormat, MessageFormat footerFormat ) {
return new MyPrintable( this, printMode, headerFormat, footerFormat );
}
};
MyPrintable
/*
* #(#)TablePrintable.java 1.41 05/11/17
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.text.MessageFormat;
import javax.swing.JTable;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumnModel;
/**
* An implementation of <code>Printable</code> for printing
* <code>JTable</code>s.
* <p>
* This implementation spreads table rows naturally in sequence
* across multiple pages, fitting as many rows as possible per page.
* The distribution of columns, on the other hand, is controlled by a
* printing mode parameter passed to the constructor. When
* <code>JTable.PrintMode.NORMAL</code> is used, the implementation
* handles columns in a similar manner to how it handles rows, spreading them
* across multiple pages (in an order consistent with the table's
* <code>ComponentOrientation</code>).
* When <code>JTable.PrintMode.FIT_WIDTH</code> is given, the implementation
* scales the output smaller if necessary, to ensure that all columns fit on
* the page. (Note that width and height are scaled equally, ensuring that the
* aspect ratio remains the same).
* <p>
* The portion of table printed on each page is headed by the
* appropriate section of the table's <code>JTableHeader</code>.
* <p>
* Header and footer text can be added to the output by providing
* <code>MessageFormat</code> instances to the constructor. The
* printing code requests Strings from the formats by calling
* their <code>format</code> method with a single parameter:
* an <code>Object</code> array containing a single element of type
* <code>Integer</code>, representing the current page number.
* <p>
* There are certain circumstances where this <code>Printable</code>
* cannot fit items appropriately, resulting in clipped output.
* These are:
* <ul>
* <li>In any mode, when the header or footer text is too wide to
* fit completely in the printable area. The implementation
* prints as much of the text as possible starting from the beginning,
* as determined by the table's <code>ComponentOrientation</code>.
* <li>In any mode, when a row is too tall to fit in the
* printable area. The upper most portion of the row
* is printed and no lower border is shown.
* <li>In <code>JTable.PrintMode.NORMAL</code> when a column
* is too wide to fit in the printable area. The center of the
* column is printed and no left and right borders are shown.
* </ul>
* <p>
* It is entirely valid for a developer to wrap this <code>Printable</code>
* inside another in order to create complex reports and documents. They may
* even request that different pages be rendered into different sized
* printable areas. The implementation was designed to handle this by
* performing most of its calculations on the fly. However, providing different
* sizes works best when <code>JTable.PrintMode.FIT_WIDTH</code> is used, or
* when only the printable width is changed between pages. This is because when
* it is printing a set of rows in <code>JTable.PrintMode.NORMAL</code> and the
* implementation determines a need to distribute columns across pages,
* it assumes that all of those rows will fit on each subsequent page needed
* to fit the columns.
* <p>
* It is the responsibility of the developer to ensure that the table is not
* modified in any way after this <code>Printable</code> is created (invalid
* modifications include changes in: size, renderers, or underlying data).
* The behavior of this <code>Printable</code> is undefined if the table is
* changed at any time after creation.
*
* #author Shannon Hickey
* #version 1.41 11/17/05
*/
class TablePrintable implements Printable {
/** The table to print. */
private JTable table;
/** For quick reference to the table's header. */
private JTableHeader header;
/** For quick reference to the table's column model. */
private TableColumnModel colModel;
/** To save multiple calculations of total column width. */
private int totalColWidth;
/** The printing mode of this printable. */
private JTable.PrintMode printMode;
/** Provides the header text for the table. */
private MessageFormat headerFormat;
/** Provides the footer text for the table. */
private MessageFormat footerFormat;
/** The most recent page index asked to print. */
private int last = -1;
/** The next row to print. */
private int row = 0;
/** The next column to print. */
private int col = 0;
/** Used to store an area of the table to be printed. */
private final Rectangle clip = new Rectangle(0, 0, 0, 0);
/** Used to store an area of the table's header to be printed. */
private final Rectangle hclip = new Rectangle(0, 0, 0, 0);
/** Saves the creation of multiple rectangles. */
private final Rectangle tempRect = new Rectangle(0, 0, 0, 0);
/** Vertical space to leave between table and header/footer text. */
private static final int H_F_SPACE = 8;
/** Font size for the header text. */
private static final float HEADER_FONT_SIZE = 18.0f;
/** Font size for the footer text. */
private static final float FOOTER_FONT_SIZE = 12.0f;
/** The font to use in rendering header text. */
private Font headerFont;
/** The font to use in rendering footer text. */
private Font footerFont;
/**
* Create a new <code>TablePrintable</code> for the given
* <code>JTable</code>. Header and footer text can be specified using the
* two <code>MessageFormat</code> parameters. When called upon to provide
* a String, each format is given the current page number.
*
* #param table the table to print
* #param printMode the printing mode for this printable
* #param headerFormat a <code>MessageFormat</code> specifying the text to
* be used in printing a header, or null for none
* #param footerFormat a <code>MessageFormat</code> specifying the text to
* be used in printing a footer, or null for none
* #throws IllegalArgumentException if passed an invalid print mode
*/
public TablePrintable(JTable table,
JTable.PrintMode printMode,
MessageFormat headerFormat,
MessageFormat footerFormat) {
this.table = table;
header = table.getTableHeader();
colModel = table.getColumnModel();
totalColWidth = colModel.getTotalColumnWidth();
if (header != null) {
// the header clip height can be set once since it's unchanging
hclip.height = header.getHeight();
}
this.printMode = printMode;
this.headerFormat = headerFormat;
this.footerFormat = footerFormat;
// derive the header and footer font from the table's font
headerFont = table.getFont().deriveFont(Font.BOLD,
HEADER_FONT_SIZE);
footerFont = table.getFont().deriveFont(Font.PLAIN,
FOOTER_FONT_SIZE);
}
/**
* Prints the specified page of the table into the given {#link Graphics}
* context, in the specified format.
*
* #param graphics the context into which the page is drawn
* #param pageFormat the size and orientation of the page being drawn
* #param pageIndex the zero based index of the page to be drawn
* #return PAGE_EXISTS if the page is rendered successfully, or
* NO_SUCH_PAGE if a non-existent page index is specified
* #throws PrinterException if an error causes printing to be aborted
*/
#Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
throws PrinterException {
// for easy access to these values
final int imgWidth = (int)pageFormat.getImageableWidth();
final int imgHeight = (int)pageFormat.getImageableHeight();
if (imgWidth <= 0) {
throw new PrinterException("Width of printable area is too small.");
}
// to pass the page number when formatting the header and footer text
Object[] pageNumber = new Object[]{new Integer(pageIndex + 1)};
// fetch the formatted header text, if any
String headerText = null;
if (headerFormat != null) {
headerText = headerFormat.format(pageNumber);
}
// fetch the formatted footer text, if any
String footerText = null;
if (footerFormat != null) {
footerText = footerFormat.format(pageNumber);
}
// to store the bounds of the header and footer text
Rectangle2D hRect = null;
Rectangle2D fRect = null;
// the amount of vertical space needed for the header and footer text
int headerTextSpace = 0;
int footerTextSpace = 0;
// the amount of vertical space available for printing the table
int availableSpace = imgHeight;
// if there's header text, find out how much space is needed for it
// and subtract that from the available space
if (headerText != null) {
graphics.setFont(headerFont);
hRect = graphics.getFontMetrics().getStringBounds(headerText,
graphics);
headerTextSpace = (int)Math.ceil(hRect.getHeight());
availableSpace -= headerTextSpace + H_F_SPACE;
}
// if there's footer text, find out how much space is needed for it
// and subtract that from the available space
if (footerText != null) {
graphics.setFont(footerFont);
fRect = graphics.getFontMetrics().getStringBounds(footerText,
graphics);
footerTextSpace = (int)Math.ceil(fRect.getHeight());
availableSpace -= footerTextSpace + H_F_SPACE;
}
if (availableSpace <= 0) {
throw new PrinterException("Height of printable area is too small.");
}
// depending on the print mode, we may need a scale factor to
// fit the table's entire width on the page
double sf = 1.0D;
if (printMode == JTable.PrintMode.FIT_WIDTH &&
totalColWidth > imgWidth) {
// if not, we would have thrown an acception previously
assert imgWidth > 0;
// it must be, according to the if-condition, since imgWidth > 0
assert totalColWidth > 1;
sf = (double)imgWidth / (double)totalColWidth;
}
// dictated by the previous two assertions
assert sf > 0;
// This is in a loop for two reasons:
// First, it allows us to catch up in case we're called starting
// with a non-zero pageIndex. Second, we know that we can be called
// for the same page multiple times. The condition of this while
// loop acts as a check, ensuring that we don't attempt to do the
// calculations again when we are called subsequent times for the
// same page.
while (last < pageIndex) {
// if we are finished all columns in all rows
if (row >= table.getRowCount() && col == 0) {
return NO_SUCH_PAGE;
}
// rather than multiplying every row and column by the scale factor
// in findNextClip, just pass a width and height that have already
// been divided by it
int scaledWidth = (int)(imgWidth / sf);
int scaledHeight = (int)((availableSpace - hclip.height) / sf);
// calculate the area of the table to be printed for this page
findNextClip(scaledWidth, scaledHeight);
last++;
}
// translate into the co-ordinate system of the pageFormat
Graphics2D g2d = (Graphics2D)graphics;
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
// to save and store the transform
AffineTransform oldTrans;
// if there's footer text, print it at the bottom of the imageable area
if (footerText != null) {
oldTrans = g2d.getTransform();
g2d.translate(0, imgHeight - footerTextSpace);
printText(g2d, footerText, fRect, footerFont, imgWidth);
g2d.setTransform(oldTrans);
}
// if there's header text, print it at the top of the imageable area
// and then translate downwards
if (headerText != null) {
printText(g2d, headerText, hRect, headerFont, imgWidth);
g2d.translate(0, headerTextSpace + H_F_SPACE);
}
// constrain the table output to the available space
tempRect.x = 0;
tempRect.y = 0;
tempRect.width = imgWidth;
tempRect.height = availableSpace;
g2d.clip(tempRect);
// if we have a scale factor, scale the graphics object to fit
// the entire width
if (sf != 1.0D) {
g2d.scale(sf, sf);
// otherwise, ensure that the current portion of the table is
// centered horizontally
} else {
int diff = (imgWidth - clip.width) / 2;
g2d.translate(diff, 0);
}
// store the old transform and clip for later restoration
oldTrans = g2d.getTransform();
Shape oldClip = g2d.getClip();
// if there's a table header, print the current section and
// then translate downwards
if (header != null) {
hclip.x = clip.x;
hclip.width = clip.width;
g2d.translate(-hclip.x, 0);
g2d.clip(hclip);
header.print(g2d);
// restore the original transform and clip
g2d.setTransform(oldTrans);
g2d.setClip(oldClip);
// translate downwards
g2d.translate(0, hclip.height);
}
// print the current section of the table
g2d.translate(-clip.x, -clip.y);
g2d.clip(clip);
table.print(g2d);
// restore the original transform and clip
g2d.setTransform(oldTrans);
g2d.setClip(oldClip);
// draw a box around the table
g2d.setColor(Color.BLACK);
g2d.drawRect(0, 0, clip.width, hclip.height + clip.height);
return PAGE_EXISTS;
}
/**
* A helper method that encapsulates common code for rendering the
* header and footer text.
*
* #param g2d the graphics to draw into
* #param text the text to draw, non null
* #param rect the bounding rectangle for this text,
* as calculated at the given font, non null
* #param font the font to draw the text in, non null
* #param imgWidth the width of the area to draw into
*/
private void printText(Graphics2D g2d,
String text,
Rectangle2D rect,
Font font,
int imgWidth) {
int tx;
// if the text is small enough to fit, center it
if (rect.getWidth() < imgWidth) {
tx = (int)((imgWidth - rect.getWidth()) / 2);
// otherwise, if the table is LTR, ensure the left side of
// the text shows; the right can be clipped
} else if (table.getComponentOrientation().isLeftToRight()) {
tx = 0;
// otherwise, ensure the right side of the text shows
} else {
tx = -(int)(Math.ceil(rect.getWidth()) - imgWidth);
}
int ty = (int)Math.ceil(Math.abs(rect.getY()));
g2d.setColor(Color.BLACK);
g2d.setFont(font);
g2d.drawString(text, tx, ty);
}
/**
* Calculate the area of the table to be printed for
* the next page. This should only be called if there
* are rows and columns left to print.
*
* To avoid an infinite loop in printing, this will
* always put at least one cell on each page.
*
* #param pw the width of the area to print in
* #param ph the height of the area to print in
*/
private void findNextClip(int pw, int ph) {
final boolean ltr = table.getComponentOrientation().isLeftToRight();
// if we're ready to start a new set of rows
if (col == 0) {
if (ltr) {
// adjust clip to the left of the first column
clip.x = 0;
} else {
// adjust clip to the right of the first column
clip.x = totalColWidth;
}
// adjust clip to the top of the next set of rows
clip.y += clip.height;
// adjust clip width and height to be zero
clip.width = 0;
clip.height = 0;
// fit as many rows as possible, and at least one
int rowCount = table.getRowCount();
int rowHeight = table.getRowHeight(row);
do {
clip.height += rowHeight;
if (++row >= rowCount) {
break;
}
rowHeight = table.getRowHeight(row);
} while (clip.height + rowHeight <= ph);
}
// we can short-circuit for JTable.PrintMode.FIT_WIDTH since
// we'll always fit all columns on the page
if (printMode == JTable.PrintMode.FIT_WIDTH) {
clip.x = 0;
clip.width = totalColWidth;
return;
}
if (ltr) {
// adjust clip to the left of the next set of columns
clip.x += clip.width;
}
// adjust clip width to be zero
clip.width = 0;
// fit as many columns as possible, and at least one
int colCount = table.getColumnCount();
int colWidth = colModel.getColumn(col).getWidth();
do {
clip.width += colWidth;
if (!ltr) {
clip.x -= colWidth;
}
if (++col >= colCount) {
// reset col to 0 to indicate we're finished all columns
col = 0;
break;
}
colWidth = colModel.getColumn(col).getWidth();
} while (clip.width + colWidth <= pw);
}
}