vandeseer / easytable

Small table drawing library built upon Apache PDFBox
MIT License
239 stars 91 forks source link

VerticalTextCell text alignment #122

Closed c4da closed 3 years ago

c4da commented 3 years ago

Hi, It seems that using .horizontalAlignment(HorizontalAlignment.CENTER).verticalAlignment(VerticalAlignment.MIDDLE) on VerticalTextCell has no effect.

The text in VerticalTextCell is always aligned to the bottom left. I have tried this also on the example code. Am I doing something wrong?

private static Table createSimpleTable() {
    final Table.TableBuilder tableBuilder = Table.builder()
            .addColumnsOfWidth(100, 100, 100, 100)
            .fontSize(8)
            .font(HELVETICA);
    tableBuilder
            .addRow(Row.builder()
                    .add(VerticalTextCell.builder().minHeight(80f).borderWidth(1).text("This is a super long text that does not fit in one linexxxx").horizontalAlignment(HorizontalAlignment.CENTER).verticalAlignment(MIDDLE).build())
                    .add(VerticalTextCell.builder().borderWidth(1).text("Two").build())
                    .add(VerticalTextCell.builder().rowSpan(2).borderWidth(1).text("This is again a very long text that will break at one point :)").build())
                    .add(VerticalTextCell.builder().borderWidth(1).text("Four").build())
                    .verticalAlignment(MIDDLE)
                    .horizontalAlignment(HorizontalAlignment.CENTER)
                    .build())
            .addRow(Row.builder()
                    .add(TextCell.builder().borderWidth(1).text("One 1\nFubarbar").build())
                    .add(TextCell.builder().borderWidth(1).text("Abc").build())
                    .add(VerticalTextCell.builder().borderWidth(1).text("Four").build())
                    .build());

    return tableBuilder.build();
}
vandeseer commented 3 years ago

Hey @c4da,

it could easily be the case that there is still something missing here. I implemented the vertical text cells a long long time ago merely for playing around. Therefore I also mention its experimental state.

Without having had a closer look I guess it shouldn't be too hard to correct this bug or missing code.

If I find some time I will look a bit deeper into it.

Best, Stefan

c4da commented 3 years ago

Hi @vandeseer it seems that I have found the missing pieces and fixed it. Should I post the code here?

vandeseer commented 3 years ago

Yes, please! Or just open a pull request :)

c4da commented 3 years ago
package org.vandeseer.easytable.drawing.cell;

import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.vandeseer.easytable.drawing.DrawingContext;
import org.vandeseer.easytable.settings.VerticalAlignment;
import org.vandeseer.easytable.structure.cell.VerticalTextCell;
import org.vandeseer.easytable.util.PdfUtil;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

import static org.vandeseer.easytable.settings.HorizontalAlignment.*;

/**
 * Allows vertical text drawing. Note that this class is still not fully
 * developed, e.g. there is no support for text alignment settings yet.
 */
@NoArgsConstructor
public class VerticalTextCellDrawer extends AbstractCellDrawer<VerticalTextCell> {

    public VerticalTextCellDrawer(VerticalTextCell cell) {
        this.cell = cell;
    }

    /**
     * Does not yet support the settings of alignments.
     *
     * @param drawingContext
     */
    @Override
    @SneakyThrows
    public void drawContent(DrawingContext drawingContext) {
        final float startX = drawingContext.getStartingPoint().x;
        final float startY = drawingContext.getStartingPoint().y;

        final PDFont currentFont = cell.getFont();
        final int currentFontSize = cell.getFontSize();
        final Color currentTextColor = cell.getTextColor();

        float yOffset = startY + cell.getPaddingBottom();

        float height = cell.getRow().getHeight();

        if (cell.getRowSpan() > 1) {
            float rowSpanAdaption = cell.calculateHeightForRowSpan() - cell.getRow().getHeight();
            yOffset -= rowSpanAdaption;
            height = cell.calculateHeightForRowSpan();
        }

        final List<String> lines = cell.isWordBreak()
                ? PdfUtil.getOptimalTextBreakLines(cell.getText(), currentFont, currentFontSize, (height - cell.getVerticalPadding()))
                : Collections.singletonList(cell.getText());

        float textHeight = 0;
        for (String line: lines) {
            float currentHeight = PdfUtil.getStringWidth(line, currentFont, currentFontSize);
            textHeight = currentHeight>textHeight?currentHeight:textHeight;
        }
        if (cell.isVerticallyAligned(VerticalAlignment.MIDDLE)) {
            yOffset += (height - textHeight - cell.getPaddingTop() - cell.getPaddingBottom()) / 2;
        }else if (cell.isVerticallyAligned(VerticalAlignment.TOP)) {
            yOffset += (height - textHeight - cell.getPaddingTop() - cell.getPaddingBottom());
        }

        float xOffset = startX + cell.getPaddingLeft() - PdfUtil.getFontHeight(currentFont, currentFontSize);

        float textWidth = (PdfUtil.getFontHeight(currentFont, currentFontSize) // font height
                + PdfUtil.getFontHeight(currentFont, currentFontSize) * cell.getLineSpacing())*lines.size(); // line spacing;

        if (cell.isHorizontallyAligned(CENTER)) {
            xOffset = xOffset + ((cell.getWidth() - cell.getPaddingRight() - cell.getPaddingLeft())/ 2 - textWidth/2) ;
        } else if (cell.isHorizontallyAligned(RIGHT)) {
            xOffset = xOffset + cell.getWidth() - cell.getPaddingRight() - cell.getPaddingLeft() - textWidth;
        }

        for (int i = 0; i < lines.size(); i++) {
            String line = lines.get(i);

            xOffset += (
                    PdfUtil.getFontHeight(currentFont, currentFontSize) // font height
                            + (i > 0 ? PdfUtil.getFontHeight(currentFont, currentFontSize) * cell.getLineSpacing() : 0f) // line spacing
            );

            drawText(line, currentFont, currentFontSize, currentTextColor, xOffset, yOffset, drawingContext.getContentStream());
        }
    }

    // TODO this is currently not used!
    @Override
    protected float calculateInnerHeight() {
        return 0;
    }

    protected void drawText(String text, PDFont font, int fontSize, Color color, float x, float y, PDPageContentStream contentStream) throws IOException {
        // Rotate by 90 degrees counter clockwise
        final AffineTransform transform = AffineTransform.getTranslateInstance(x, y);
        transform.concatenate(AffineTransform.getRotateInstance(Math.PI * 0.5));
        transform.concatenate(AffineTransform.getTranslateInstance(-x, -y - fontSize));

        contentStream.moveTo(x, y);
        contentStream.beginText();

        // Do the transformation :)
        contentStream.setTextMatrix(transform);

        contentStream.setNonStrokingColor(color);
        contentStream.setFont(font, fontSize);
        contentStream.newLineAtOffset(x, y);
        contentStream.showText(text);
        contentStream.endText();
        contentStream.setCharacterSpacing(0);
    }

}
vandeseer commented 3 years ago

Thanks a lot, I will have look!

vandeseer commented 3 years ago

Looks good to me! :+1: :smiley:

I will push the change to the develop branch and the next release of easytable will contain it. Thanks for your help! Mentioned you in the README kudos section :wink: