0

I have a itextsharp Table, with a strict height and width of the Cells. I then get a string that can differ in word count and work length. How can i set the Font size automaticaly to go as big as possible, and only allow breaks after a word has ended, just like in WPF's Viewboxes.

currently i am working with this:

Cell.AddElement(new Paragraph(text, new Font(Font.FontFamily.COURIER, 
Convert.ToInt32(FZ), 1)){Alignment = Element.ALIGN_CENTER});
            Cell.VerticalAlignment = Element.ALIGN_MIDDLE;
            Cell.HorizontalAlignment = Element.ALIGN_MIDDLE;
            Cell.Padding = 4;
            Cell.FixedHeight = Utilities.MillimetersToPoints(46);
            return Cell;

FZ is the Fontsize I am taking from the viewboxes directly, but a straight conversion doesnt work

Thanks in Advance!

nuuse
  • 109
  • 1
  • 11
  • You (can) know the width of each cell (e.g. `w`) because that width is defined the moment you create a `PdfPTable` object. You should use that width to determine the font size. You know which text you want add, hence you can calculate the width needed to draw the text as a value in *glyph space* (e.g. `wg`). Based on the value of `w` and `wg`, you can calculate the font size. – Bruno Lowagie Nov 20 '17 at 13:48
  • I know I had already explained all of this before. Please read my answer to the duplicate question. It explains what *glyph space* is about, and how one can determine a font size based on a `string`, a `BaseFont`, and an available width. – Bruno Lowagie Nov 20 '17 at 13:53
  • This is not a duplicate. did you read my whole question? Also you did not say anything about Linebreaks midword.. – nuuse Nov 20 '17 at 14:16
  • OK, I reopened the question. As for "allow breaks after a word has ended, just like in WPF's Viewboxes", you need to explain what you mean by that because I have no idea what "WPF's Viewboxes" are. The question is very unclear. Maybe I should have down-voted it instead of giving it a close vote. – Bruno Lowagie Nov 20 '17 at 14:29
  • If you have a textblock inside a viewbox, the text will shrink instead of going out of the viewboxes bounds. – nuuse Nov 20 '17 at 14:32
  • If that is what you mean, you should rephrase your question. While you rephrase your question, please also correct errors such as talking about Table when you mean to say `PdfPTable`. Also: we don't talk about iTextSharp anymore. Today, we talk about *iText for .NET* instead of about *iTextSharp*. You should also consider upgrading to iText 7. With iText 7, you can override the renderer to achieve what you want. Solving your problem with iText 5 will be more difficult (I won't even attempt to think of an answer, and I'm the guy who initially wrote iText). – Bruno Lowagie Nov 20 '17 at 14:37
  • Ok, thank you for your advice. I have now upgrade to itext7 and shifted my code over aswell. Took some time! Now im sitting at the exact same problem, I want my Paragraphs to size down the fontsize and not do any linebreaks on its own at all. How can i achieve that? Is there a parameter i can just set to false? – nuuse Nov 21 '17 at 10:06
  • I would extend the `Cell` class as is done in [chapter 5](https://developers.itextpdf.com/content/itext-7-building-blocks/chapter-5-adding-abstractelement-objects-part-2) and override the `makeNewRenderer()` method: `protected IRenderer makeNewRenderer() { return new FitContentCellRenderer(this); }` I would then create a `FitContentRenderer` class and completely change the behavior of the code that draws the content. Plenty of methods will be simplified (no need for `splitRenderer`s), but quite some work to do the glyph Math. – Bruno Lowagie Nov 21 '17 at 10:59

1 Answers1

1

I've created a standalone application with a lot of "test", "test\ntest", "test test test test", etc... cells. The result looks like this:

enter image description here

I have made several assumptions:

  • I assume that cells are created using String values. Internally, this will result in a List that contains a single Paragraph. This Paragraph will contain a single Text object.
  • I assume that no font was defined, that's why I introduce the default font (Helvetica).
  • I assume that no leading was defined, hence I introduce a multiplied leading of 1.
  • I assume that no padding was defined, hence I introduce a padding of 1

This is my code:

import java.io.File;
import java.io.IOException;

import com.itextpdf.io.font.FontProgram;
import com.itextpdf.io.font.FontProgramFactory;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.element.Text;
import com.itextpdf.layout.renderer.CellRenderer;
import com.itextpdf.layout.renderer.IRenderer;

public class FitCell {

    public static final String[] TEST = {
        "test",
        "test test test test test test test",
        "test test",
        "test",
        "test test test test\ntest test test test test test test test test test test",
        "test",
        "test",
        "test",
        "test\ntest\ntest\ntest",
        "test",
        "test",
        "test",
        "test",
        "test",
        "test",
        "test",
        "test",
        "test\ntest",
        "test",
        "test",
        "test",
        "test",
        "test",
        "test",
        "test test test test test test test test test test test test test test"
    };

    private class FitContentCellRenderer extends CellRenderer {
        public FitContentCellRenderer(Cell modelElement) {
            super(modelElement);
            try {
                // We assume that the Cell is created by passing a String
                // This means that the Cell consists of one Paragraph
                Paragraph p = (Paragraph)modelElement.getChildren().get(0);
                // And that the Paragraph has one Text object
                Text t = (Text)p.getChildren().get(0);
                // Using the default font
                FontProgram f = FontProgramFactory.createFont();

                // We get the content from the Text object
                String content = t.getText();
                // Define a default font size (to make sure that you don't have text that is excessively big).
                float fs = 100;
                // Define a font size depending on the number of lines and the height
                int[] fontBbox = f.getFontMetrics().getBbox();
                int fh = (fontBbox[2] - fontBbox[1]);
                float padding = 1;
                modelElement.setPadding(padding);
                float ch = modelElement.getHeight() - 2 * padding;
                String text[] = content.split("\\r\\n|\\n|\\r");
                fs = Math.min(fs, ch / (fh * text.length) * FontProgram.UNITS_NORMALIZATION);
                // Loop over all the lines
                PdfFont font = PdfFontFactory.createFont(f);
                float cw = modelElement.getWidth().getValue() - 2 * padding;
                for (String l : text) {
                    float w = font.getWidth(l, 1);
                    fs = Math.min(fs, cw / w);
                }
                p.setFontSize(fs);
                p.setMultipliedLeading(1);
            }
            catch(IOException ioe) {
                // occurs when font program can't be created; this never happens
            }
        }
        @Override
        public IRenderer getNextRenderer(){
            return new FitContentCellRenderer(getModelElement());
        }
    }

    private class FitContentCell extends Cell {
        public FitContentCell() {
            super();
            this.setWidth(50);
        }

        @Override
        protected IRenderer makeNewRenderer() {
            return new FitContentCellRenderer(this);
        }
    }

    public static final String DEST = "results/tables/cell_fit_content.pdf";

    public static void main(String args[]) throws IOException {
        File file = new File(DEST);
        file.getParentFile().mkdirs();
        new FitCell().createPdf(DEST);
    }

    public void createPdf(String dest) throws IOException {
        PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
        Document document = new Document(pdf);
        Table table = new Table(new float[]{1, 1, 1, 1, 1, 1, 1});
        int testcount = 0;
        for (int i = 0; i < 70; i++) {
            if (testcount >= TEST.length) {
                testcount = 0;
            }
            table.addCell(new FitContentCell().add(TEST[testcount++]).setHeight(20 + 10 * (i / 10)));
        }
        document.add(table);
        document.close();
    }
}

In this code, I change the behavior of the Cell object in a subclass called FitCell. The behavior of FitCell is changed by overriding the CellRenderer.

Actually, I'm not really changing the behavior of the renderer, but I'm changing some things the moment the renderer instance is created (before anything else happens). I define a padding for the cell, I define a font and a leading for the Paragraph and I calculate the font size of the Paragraph so that the text fits the cell. I first define a font size of 100 (which is huge), but none of the content will eventually have a font size of 100, because I first make sure that all the text fits the height of the cell; then I make sure that all the text fits the width of the cell.

The result is that all the text fits the cell.

Bruno Lowagie
  • 75,994
  • 9
  • 109
  • 165