I have a table that has a couple of number columns. I can right align the row columns just fine, but I cannot right alight the header column. The reason, I think, is because the PdfPHeaderCell constructor does not accept a Phrase object so I have to use cell.addElement() which puts it into composite mode ignoring the alignment I specify.
The only way I have found to achieve the effect I am looking for is to create a paragraph, assign the alignment to it, and then put that in the header. That seems a little clunky to me since I dont need any characteristics of a paragraph, I just need to align it.
Is there a cleaner way to do this?
/**
* Builds a basic header cell from the given string
*
* #param content
* #param alignment
* #return PdfPHeaderCell
*/
protected static PdfPHeaderCell getGenericHeaderCell(String content, Integer alignment)
{
PdfPHeaderCell cell = new PdfPHeaderCell();
Paragraph p = new Paragraph();
p.add(new Phrase(content, TABLE_HEADER));
if (alignment != null)
{
p.setAlignment(alignment);
}
cell.addElement(p);
cell.setBorder(Rectangle.BOTTOM);
cell.setBorderColorBottom(TABLE_HEADER_BORDERCOLOR);
cell.setBorderWidthBottom(1);
cell.setPaddingTop(0);
return cell;
}
First this: you need a Phrase but you use a Paragraph, so you can replace:
Paragraph p = new Paragraph();
p.add(new Phrase(content, TABLE_HEADER));
if (alignment != null)
{
p.setAlignment(alignment);
}
with:
Phrase p = new Phrase(content, TABLE_HEADER);
if (alignment != null)
{
p.setAlignment(alignment);
}
The actual error in your code, is the fact that you use addElement(). You claim that you create a cell in text mode, but you forget that using addElement() makes the cell switch to composite mode.
You can fix this by replacing:
cell.addElement(p);
with:
cell.setPhrase(p);
That keeps the cell in text mode.
Related
We have a NatTable with no header and I treated the 1st row as a Header,
- Register CELL_PAINTER to change the visualization to look this row similar to header.
Also registered the CustomCommandHandler which implements ILayerCommandHandler to prevent cell/row selection for the 1st row.
selectionLayer.registerCommandHandler(new CustomCommandHandler());
Cell selection is working fine for other cells.
public boolean doCommand(final ILayer layer, final ILayerCommand command)
{
if (command instanceof ViewportSelectRowCommand)
{
return ((ViewportSelectRowCommand) command).getRowPosition() <= 1;
}
else if (command instanceof SelectCellCommand)
{
return ((SelectCellCommand) command).getRowPosition() <= 1
}
return false;
}
Now how can I select the entire column on selecting cells on 1st row. So that it should not affect the cell selection for other row cells.
Clicking any cells on 1st row should select entire column.
Clicking any cells on other rows should select the same cell. (currently this is happening)
Although I am not quite sure what sense it makes to only have a body that is configured in a complicated way to look and behave like it has headers without really having headers (IMHO this does not make any sense), you need to register a custom handler that checks for the column position and transform the SelectCellCommand into a SelectColumnCommand.
this.selectionLayer.registerCommandHandler(new SelectCellCommandHandler(this.selectionLayer) {
#Override
public boolean doCommand(ILayer targetLayer, SelectCellCommand command) {
if (command.convertToTargetLayer(targetLayer)
&& command.getColumnPosition() == 0) {
return targetLayer.doCommand(
new SelectColumnCommand(
targetLayer,
command.getColumnPosition(),
command.getRowPosition(),
command.isShiftMask(),
command.isControlMask()));
}
return super.doCommand(targetLayer, command);
}
});
But I expect there will be more issues on the way as the immitated headers do not behave like real headers also in other scenarios. You could also try to override getRegionLabelsByXY(int, int) but I am not sure if this would work or cause more problems.
I'm using iTextSharp version 4.1.6.16. I have a PdfPTable with two columns (1 row).
There is a lot of content in right cell so it spans across whole page and reaches next page.
In the left cell I only want to put some small PdfPTable at its bottom. To do it I use cell.VerticalAlignment = Element.ALIGN_BOTTOM; on the left cell of topmost table. This works well if the row is not bigger than a page.
If the row reaches next page, then the content of the left cell is indeed aligned to bottom, but to bottom of first page. So the right cell's content still continues on the next page, but the left cell is empty there (I tested with background color that it spans to next page also).
I tried setting KeepTogether on the inner table but it doesn't have any effect. I also thought about doing it with CellEvent but I couldn't find so far how to position IElement (not only Image or text) absolutely in the cell.
Is it a bug or designed behavior, that the content is aligned to the bottom of the first page the cell occupies? Is there a workaround or some better way to put content at the bottom of the cell no matter where it ends?
I've finally solved the issue with following workaround.
I created a CellEvent that gets PdfPTable and shifts it upwards as much as its content takes:
class BottomFix : IPdfPCellEvent
{
private readonly PdfPTable _content;
public BottomFix(PdfPTable content)
{
_content = content;
}
public void CellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases)
{
_content?.WriteSelectedRows(0, -1, position.Left, position.Top + _content.TotalHeight, canvases[PdfPTable.BACKGROUNDCANVAS]);
}
}
Then to my original two-row table I added second row, and I add there a cell of height equal to 0, where instead of putting content into, I use this CellEvent. The original left cell is left empty, so the content of next row is pushed onto it and it looks as it was originally there. I don't have borders in this table so they are not an issue, but it can also be solved by setting border of this 1px cell to none.
var cell = new PdfPCell();
var table = new PdfPTable(1) { /* some content etc... */ };
cell.FixedHeight = 0;
cell.CellEvent = new BottomFix(table);
// such prepared cell goes to top level table, second row, left column
I found ResizeColumnHideShowLayer class at nattable version 1.6.
(about https://bugs.eclipse.org/bugs/show_bug.cgi?id=521486)
That is work fine for normal column headers only.
But, if I collapse a column group, no adjust size to fit window. (no increasing column size)
How can I solve the problem?
Is there way to resize other columns to fit window automatically increase?
Thank you.
Currently not because the ColumnGroupExpandCollapseLayer is taking care of hiding collapsed columns.
I found solution by myself!
It works fine very well. :-)
I was run based on NatTable v1.6 version.(downloaded yesterday)
I think this is a basic feature, so I hope this feature will be included in the next NatTable version.
In narrow tables, behavior that collapsing column group means that may be someone want to view other column data more widely.
Overview (Problem screen and solved screen)
I explain using two application(before, after) screen shot.
Refer to bottom image if you want understand my issue easily at once.
Problem screen
enter image description here
Improved screen
enter image description here
Solution summary :
Add event listener to ColumnGroupExpandCollapseLayer.
(HideColumnPositionsEvent, ShowColumnPositionsEvent)
Handle above events.
Get column indices which is hidden by collapsed
Execute MultiColumnHideCommand with the indices
Layer structure of my test code
↑ ViewportLayer (top layer)
| SelectionLayer
| ColumnGroupExpandCollapseLayer
| ResizeColumnHideShowLayer
| ColumnGroupReorderLayer
| ColumnReorderLayer
| DataLayer (base layer)
Implementation code is below:
void method() {
...
columnGroupExpandCollapseLayer.addLayerListener(new ILayerListener() {
#Override
public void handleLayerEvent(ILayerEvent event) {
boolean doRedraw = false;
//It works for HideColumnPositionsEvent and ShowColumnPositionsEvent
// triggered by ColumnGroupExpandCollapseCommandHandler
if (event instanceof HideColumnPositionsEvent) {
HideColumnPositionsEvent hideEvent = (HideColumnPositionsEvent)event;
Collection<Range> columnPositionRanges = hideEvent.getColumnPositionRanges();
Collection<Integer> convertIntegerCollection = convertIntegerCollection(columnPositionRanges);
int[] positions = convertIntPrimitiveArray(convertIntegerCollection);
//Execute command to hide columns that was hidden by collapsed column group.
MultiColumnHideCommand multiColumnHideCommand = new MultiColumnHideCommand(resizeColumnHideShowLayer, positions);
resizeColumnHideShowLayer.doCommand(multiColumnHideCommand);
doRedraw = true;
}else if (event instanceof ShowColumnPositionsEvent) {//called by ColumnGroupCollapsedCollapseCommandHandler
ShowColumnPositionsEvent showEvent = (ShowColumnPositionsEvent)event;
Collection<Range> columnPositionRanges = showEvent.getColumnPositionRanges();
Collection<Integer> positions = convertIntegerCollection(columnPositionRanges);
//Execute command to show columns that was hidden by expanded column group.
MultiColumnShowCommand multiColumnShowCommand = new MultiColumnShowCommand(positions);
resizeColumnHideShowLayer.doCommand(multiColumnShowCommand);
//Set whether or not to redraw table
doRedraw = true;
}
if (doRedraw) {
natTable.redraw();
}
}
/**
* Merge position values within several Ranges to Integer collection
*/
private Collection<Integer> convertIntegerCollection(Collection<Range> rangeCollection) {
Iterator<Range> rangeIterator = rangeCollection.iterator();
Set<Integer> mergedPositionSet = new HashSet<Integer>();
while (rangeIterator.hasNext()) {
Range range = rangeIterator.next();
mergedPositionSet.addAll(range.getMembers());
}
return mergedPositionSet;
}
/**
* Convert Integer wrapper object to primitive value
*/
private int [] convertIntPrimitiveArray(Collection<Integer> integerCollection) {
Integer [] integers = (Integer [])integerCollection.toArray(new Integer[integerCollection.size()]);
int [] positionPrimitives = new int[integers.length];
for (int i = 0 ; i < integers.length ; i++) {
positionPrimitives[i] = integers[i].intValue();
}
return positionPrimitives;
}
});
}
I use iTextSharp v5.5.6
I'm creating a large table.
To be consistent in my layout I want to use the DefaultCell class to set some default settings like font, padding and alignment.
I'm not doing something correct because the settings are not applied to my cells.
Here's some code:
var table = new PdfPTable(2)
{ KeepTogether = true, TotalWidth = printWidth, LockedWidth = true,
HorizontalAlignment = 0, SpacingBefore = 0, SpacingAfter = 15f };
// Set default values:
table.DefaultCell.Colspan = 1;
table.DefaultCell.HorizontalAlignment = Element.ALIGN_LEFT;
table.DefaultCell.Padding = 5f;
table.DefaultCell.PaddingLeft = 5f;
table.DefaultCell.PaddingBottom = 5f;
table.DefaultCell.VerticalAlignment = Element.ALIGN_BOTTOM;
table.DefaultCell.BorderWidthBottom = 0f;
table.DefaultCell.Phrase = new Phrase { Font = Blue11BoldFont };
table.DefaultCell.Border = Rectangle.NO_BORDER;
table.AddCell(new PdfPCell(new Phrase("Foo"))
{ HorizontalAlignment = Element.ALIGN_CENTER, MinimumHeight = 20f });
table.AddCell(new PdfPCell(new Phrase("Bar", Black10BoldFont))
{ Colspan = 4, HorizontalAlignment = Element.ALIGN_CENTER });
I would have expected my first cell would use my blue font and padding is applied.
But nothing is applied. In fact when I remove the DefaultCell lines I get the same result.
I've been searching for hours now and most samples I've found use something similar.
Any suggestion is much appreaciated.
You are creating PdfPCell objects yourself. In that case, the default cell is always ignored.
See What is the PdfPTable.DefaultCell property used for?
When creating a PdfPTable, you add cells.
One way is to create a PdfPCell object and to add that cell with the addCell() method. In this case, you are responsible to define the properties of each individual cell.
Another way is to use a short-cut: you don't create a PdfPCell, but you add a String or a Phrase to the table with the addCell() method. In this case, a PdfPCell is created internally using default properties. You can change the default properties by changing the properties of the default cell. The default cell is obtained using the getDefaultCell() method.
This is not a bug, this is by design. You are misinterpreting the meaning of the concept of the "default cell". Note that this concept was explained in the free ebook The Best iText Questions on StackOverflow.
If you want to be consistent in your layout, the best way to do this, is by creating your own createCell() method that creates a PdfPCell to which you apply all the properties for which you were using the default cell.
I am attempting to use TextMarginFinder to prove that odd and even pages back up correctly when printing. I have based my code on:
http://itextpdf.com/examples/iia.php?id=280
The issue I have is that on odd pages I am looking for the box to be aligned to the left showing a 1CM back margin for example, and on an even page I would expect the page box to be aligned to the right also showing a 1CM back margin. Even in the example above this is not the case, but when printed the text does back up perfectly because the Trim Box conforms.
In summary I believe on certain PDF files the TextMarginFinder is incorrectly locating the text width, usually on Even pages. This is evident by the width being greater than the actual text. This is usually the case if there are slug marks outside of the Media Box area.
In the PDF the OP pointed to (margins.pdf from the iText samples themselves) indeed the box is not flush with the text:
If you look into the PDF Content, though, you'll see that many of the lines have a trailing space character, e.g. the first line:
(s I have worn out since I started my ) Tj
These trailing space characters are part of the text and, therefore, the box does not flush with the visible text but it does with the text including such space characters.
If you want to ignore such space characters, you can try doing so by filtering such trailing spaces (or for the sake of simplicity all spaces) before they get fed into the TextMarginFinder. To do this I'd explode the TextRenderInfo instances character-wise and then filter those which trim to empty strings.
A helper class to explode the render info objects:
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
public class TextRenderInfoSplitter implements RenderListener
{
public TextRenderInfoSplitter(RenderListener strategy) {
this.strategy = strategy;
}
public void renderText(TextRenderInfo renderInfo) {
for (TextRenderInfo info : renderInfo.getCharacterRenderInfos()) {
strategy.renderText(info);
}
}
public void beginTextBlock() {
strategy.beginTextBlock();
}
public void endTextBlock() {
strategy.endTextBlock();
}
public void renderImage(ImageRenderInfo renderInfo) {
strategy.renderImage(renderInfo);
}
final RenderListener strategy;
}
Using this helper you can update the iText sample like this:
RenderFilter spaceFilter = new RenderFilter() {
public boolean allowText(TextRenderInfo renderInfo) {
return renderInfo != null && renderInfo.getText().trim().length() > 0;
}
};
PdfReader reader = new PdfReader(src);
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(RESULT));
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
TextMarginFinder finder = new TextMarginFinder();
FilteredRenderListener filtered = new FilteredRenderListener(finder, spaceFilter);
parser.processContent(i, new TextRenderInfoSplitter(filtered));
PdfContentByte cb = stamper.getOverContent(i);
cb.rectangle(finder.getLlx(), finder.getLly(), finder.getWidth(), finder.getHeight());
cb.stroke();
}
stamper.close();
reader.close();
The result:
In case of slug area text etc you might want to filter more, e.g. anything outside the crop box.
Beware, though, there might be fonts in which the space character is not invisible, e.g. a font of boxed characters. Taking the spaces out of the equation in that case would be wrong.