org.unitime.timetable.webutil.timegrid.PdfTimetableGridTable.java Source code

Java tutorial

Introduction

Here is the source code for org.unitime.timetable.webutil.timegrid.PdfTimetableGridTable.java

Source

/*
 * UniTime 3.2 - 3.5 (University Timetabling Application)
 * Copyright (C) 2008 - 2013, UniTime LLC, and individual contributors
 * as indicated by the @authors tag.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
*/
package org.unitime.timetable.webutil.timegrid;

import java.awt.Color;
import java.io.OutputStream;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
import java.util.StringTokenizer;

import org.unitime.timetable.model.PreferenceLevel;
import org.unitime.timetable.util.Constants;
import org.unitime.timetable.util.Formats;
import org.unitime.timetable.util.PdfEventHandler;
import org.unitime.timetable.util.PdfFont;

import com.lowagie.text.Chunk;
import com.lowagie.text.Document;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.Image;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfTemplate;
import com.lowagie.text.pdf.PdfWriter;

/**
 * @author Tomas Muller
 */
public class PdfTimetableGridTable {
    protected static Formats.Format<Date> sDF = Formats.getDateFormat(Formats.Pattern.DATE_EVENT_SHORT);
    private TimetableGridTable iTable = null;
    private PdfWriter iWriter = null;
    private Document iDocument = null;
    private PdfPTable iPdfTable = null;
    private int iDay = 0;

    protected PdfTimetableGridTable(TimetableGridTable table) {
        iTable = table;
    }

    public static void export2Pdf(TimetableGridTable table, OutputStream out) throws Exception {
        PdfTimetableGridTable x = new PdfTimetableGridTable(table);
        x.export(out);
    }

    public void export(OutputStream out) throws Exception {
        int nrCols = getNrColumns();
        iDocument = (iTable.isDispModePerWeekVertical()
                ? new Document(new Rectangle(60.0f + 7.0f * nrCols, 60.0f + 9.3f * nrCols), 30, 30, 30, 30)
                : new Document(new Rectangle(60.0f + 10.0f * nrCols, 60.0f + 7.5f * nrCols), 30, 30, 30, 30));

        iWriter = PdfEventHandler.initFooter(iDocument, out);
        iDocument.open();

        if (iTable.isDispModeInRow()) {
            for (iDay = iTable.startDay(); iDay <= iTable.endDay(); iDay++) {
                int rowNumber = 0;
                for (Enumeration e = iTable.models().elements(); e.hasMoreElements(); rowNumber++) {
                    printToPdf((TimetableGridModel) e.nextElement(), rowNumber);
                }
                flushTable();
            }
        } else {
            int rowNumber = 0;
            for (Enumeration e = iTable.models().elements(); e.hasMoreElements(); rowNumber++) {
                printToPdf((TimetableGridModel) e.nextElement(), rowNumber);
            }
        }

        printLegend();

        iDocument.close();
    }

    public int getNrColumns() {
        return 12 + (1 + iTable.lastSlot() - iTable.firstSlot());
        /*
        int nrCols = (iTable.isDispModePerWeekVertical()?1:12);
        if (iTable.isDispModePerWeekVertical()) {
           for (int day=iTable.startDay(); day<=iTable.endDay(); day++) 
        nrCols++;
        } else {//isDispModeInRow() || isDispModePerWeekVertical()
           for (int day=iTable.startDay();(iTable.isDispModeInRow() && day<=iTable.endDay()) || (iTable.isDispModePerWeek() && day==iTable.startDay());day++) {
        for (int slot=iTable.firstSlot();slot<=iTable.lastSlot();slot+=iTable.nrSlotsPerPeriod()) {
           nrCols+=iTable.nrSlotsPerPeriod();
        }
           }
        }
        return nrCols;
        */
    }

    private static Color sBorderColor = new Color(100, 100, 100);

    public PdfPCell createCell() {
        PdfPCell cell = new PdfPCell();
        cell.setBorderColor(sBorderColor);
        cell.setPadding(3);
        cell.setBorderWidth(0);
        cell.setVerticalAlignment(Element.ALIGN_TOP);
        cell.setHorizontalAlignment(Element.ALIGN_CENTER);
        cell.setBorderWidthTop(1);
        cell.setBorderWidthBottom(1);
        cell.setBorderWidthLeft(1);
        cell.setBorderWidthRight(1);
        return cell;
    }

    public PdfPCell createCellNoBorder() {
        PdfPCell cell = new PdfPCell();
        cell.setBorderColor(sBorderColor);
        cell.setPadding(3);
        cell.setBorderWidth(0);
        cell.setVerticalAlignment(Element.ALIGN_TOP);
        cell.setHorizontalAlignment(Element.ALIGN_CENTER);
        return cell;
    }

    public void addText(PdfPCell cell, String text) {
        if (text == null)
            return;
        addText(cell, text, false);
    }

    public void addText(PdfPCell cell, String text, boolean bold) {
        if (text == null)
            return;
        if (text.indexOf("<span") >= 0)
            text = text.replaceAll("</span>", "").replaceAll("<span .*>", "");
        if (cell.getPhrase() == null) {
            cell.setPhrase(new Paragraph(text, PdfFont.getSmallFont(bold)));
            cell.setVerticalAlignment(Element.ALIGN_TOP);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
        } else {
            cell.getPhrase().add(new Chunk("\n" + text, PdfFont.getSmallFont(bold)));
        }
    }

    public void addTextVertical(PdfPCell cell, String text) throws Exception {
        if (text == null)
            return;
        addTextVertical(cell, text, false);
    }

    public void addTextVertical(PdfPCell cell, String text, boolean bold) throws Exception {
        if (text == null)
            return;
        if (text.indexOf("<span") >= 0)
            text = text.replaceAll("</span>", "").replaceAll("<span .*>", "");
        Font font = PdfFont.getFont(bold);
        BaseFont bf = font.getBaseFont();
        float width = bf.getWidthPoint(text, font.getSize());
        PdfTemplate template = iWriter.getDirectContent().createTemplate(2 * font.getSize() + 4, width);
        template.beginText();
        template.setColorFill(Color.BLACK);
        template.setFontAndSize(bf, font.getSize());
        template.setTextMatrix(0, 2);
        template.showText(text);
        template.endText();
        template.setWidth(width);
        template.setHeight(font.getSize() + 2);
        //make an Image object from the template
        Image img = Image.getInstance(template);
        img.setRotationDegrees(270);
        //embed the image in a Chunk
        Chunk ck = new Chunk(img, 0, 0);

        if (cell.getPhrase() == null) {
            cell.setPhrase(new Paragraph(ck));
            cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
            cell.setHorizontalAlignment(Element.ALIGN_RIGHT);
        } else {
            cell.getPhrase().add(ck);
        }
    }

    public void printHeader(TimetableGridModel model, int rowNumber) throws Exception {
        if (iTable.isDispModePerWeekVertical()) {
            for (int slot = iTable.lastSlot(); slot >= iTable.firstSlot(); slot -= iTable.nrSlotsPerPeriod()) {
                int time = (slot - iTable.nrSlotsPerPeriod() + 1) * Constants.SLOT_LENGTH_MIN
                        + Constants.FIRST_SLOT_TIME_MIN;
                PdfPCell c = createCell();
                c.setColspan(iTable.nrSlotsPerPeriod());
                if (slot < iTable.lastSlot())
                    c.setBorderWidthLeft(0);
                addTextVertical(c, Constants.toTime(time), true);
                iPdfTable.addCell(c);
            }
            PdfPCell c = createCell();
            c.setColspan(12);
            c.setBorderWidthLeft(0);
            addTextVertical(c, model.getName() + (model.getSize() > 0 ? " (" + model.getSize() + ")" : ""), true);
            iPdfTable.addCell(c);
        } else {
            PdfPCell c = createCell();
            c.setColspan(12);
            if (iTable.isDispModeInRow())
                addText(c, Constants.DAY_NAME[iDay], true);
            else
                addText(c, model.getName() + (model.getSize() > 0 ? " (" + model.getSize() + ")" : ""), true);
            iPdfTable.addCell(c);
            for (int slot = iTable.firstSlot(); slot <= iTable.lastSlot(); slot += iTable.nrSlotsPerPeriod()) {
                int time = slot * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
                c = createCell();
                c.setBorderWidthLeft(0);
                c.setColspan(iTable.nrSlotsPerPeriod());
                addText(c, Constants.toTime(time), true);
                iPdfTable.addCell(c);
            }
        }
        iPdfTable.setHeaderRows(1);
    }

    private Color getColor(String rgbColor) {
        StringTokenizer x = new StringTokenizer(
                rgbColor.substring("rgb(".length(), rgbColor.length() - ")".length()), ",");
        return new Color(Integer.parseInt(x.nextToken()), Integer.parseInt(x.nextToken()),
                Integer.parseInt(x.nextToken()));
    }

    private void createTable() {
        iPdfTable = new PdfPTable(getNrColumns());
        iPdfTable.setWidthPercentage(100);
        iPdfTable.getDefaultCell().setPadding(3);
        iPdfTable.getDefaultCell().setBorderWidth(1);
        iPdfTable.setSplitRows(false);
        iPdfTable.setSpacingBefore(10);
        if (iTable.isDispModePerWeek()) {
            iPdfTable.setKeepTogether(true);
        }
    }

    private void flushTable() throws Exception {
        iDocument.add(iPdfTable);
    }

    public void printToPdf(TimetableGridModel model, int rowNumber) throws Exception {
        model.clearRendered();

        if (iTable.isDispModePerWeek()) {
            createTable();
            printHeader(model, 0);
        } else {
            if (rowNumber == 0) {
                createTable();
                printHeader(model, 0);
            }
        }

        if (iTable.isDispModeInRow()) {
            int maxIdx = model.getMaxIdxForDay(iDay, iTable.firstSlot(), iTable.lastSlot());
            for (int idx = 0; idx <= maxIdx; idx++) {
                PdfPCell c = createCell();
                c.setBorderWidthTop(0);
                if (idx == 0)
                    addText(c, model.getName() + (model.getSize() > 0 ? " (" + model.getSize() + ")" : ""), true);
                c.setBorderWidthBottom(idx == maxIdx ? 1 : 0);
                c.setColspan(12);
                iPdfTable.addCell(c);
                for (int slot = iTable.firstSlot(); slot <= iTable.lastSlot(); slot++) {
                    int slotsToEnd = iTable.lastSlot() - slot + 1;
                    TimetableGridCell cell = model.getCell(iDay, slot, idx);
                    int length = (cell == null ? 1 : cell.getLength() + cell.getSlot() - slot);
                    int colSpan = (cell == null ? 1 : Math.min(length, slotsToEnd));
                    if (cell == null) {
                        String bgColor = model.getBackground(iDay, slot);
                        if (bgColor == null && !model.isAvailable(iDay, slot))
                            bgColor = TimetableGridCell.sBgColorNotAvailable;
                        c = createCell();
                        c.setBorderWidthTop(0);
                        c.setBorderWidthBottom(
                                idx == maxIdx ? 1 : model.getCell(iDay, slot, idx + 1) != null ? 1 : 0);
                        c.setBorderWidthLeft(0);
                        boolean eod = (slot == iTable.lastSlot());
                        boolean in = !eod && model.getCell(iDay, slot + 1, idx) == null
                                && ((slot + 1 - iTable.firstSlot()) % iTable.nrSlotsPerPeriod()) != 0;
                        c.setBorderWidthRight(eod || !in ? 1 : 0);
                        c.setColspan(colSpan);
                        if (bgColor != null)
                            c.setBackgroundColor(getColor(bgColor));
                        iPdfTable.addCell(c);
                    } else {
                        String bgColor = cell.getBackground();
                        if (iTable.getBgMode() == TimetableGridModel.sBgModeNone) {
                            for (int i = 0; i < length; i++)
                                if (!model.isAvailable(iDay, slot + i)) {
                                    bgColor = TimetableGridCell.sBgColorNotAvailableButAssigned;
                                    break;
                                }
                        }
                        c = createCell();
                        c.setBorderWidthTop(0);
                        c.setBorderWidthLeft(0);
                        c.setColspan(colSpan);
                        c.setBackgroundColor(getColor(bgColor));
                        addText(c, cell.getName());
                        if (iTable.getShowTimes())
                            addText(c, cell.getTime());
                        if (iTable.getResourceType() != TimetableGridModel.sResourceTypeRoom)
                            addText(c, cell.getRoomName());
                        if (iTable.getResourceType() != TimetableGridModel.sResourceTypeInstructor
                                && iTable.getShowInstructors() && !cell.getInstructor().isEmpty())
                            addText(c, cell.getInstructor());
                        if (iTable.getShowComments())
                            addText(c,
                                    cell.getShortCommentNoColors() == null ? null : cell.getShortCommentNoColors());
                        if (iTable.getWeek() == -100 && cell.hasDays()
                                && !cell.getDays().equals(iTable.getDefaultDatePatternName()))
                            addText(c, cell.getDays());
                        iPdfTable.addCell(c);
                        slot += length - 1;
                    }
                }
            }
        } else if (iTable.isDispModePerWeekHorizontal()) {
            for (int day = iTable.startDay(); day <= iTable.endDay(); day++) {
                int maxIdx = model.getMaxIdxForDay(day, iTable.firstSlot(), iTable.lastSlot());
                for (int idx = 0; idx <= maxIdx; idx++) {
                    PdfPCell c = createCell();
                    c.setBorderWidthTop(0);
                    if (idx == 0)
                        addText(c, Constants.DAY_NAME[day], true);
                    c.setBorderWidthBottom(idx == maxIdx ? 1 : 0);
                    c.setColspan(12);
                    iPdfTable.addCell(c);
                    for (int slot = iTable.firstSlot(); slot <= iTable.lastSlot(); slot++) {
                        int slotsToEnd = iTable.lastSlot() - slot + 1;
                        TimetableGridCell cell = model.getCell(day, slot, idx);
                        int length = (cell == null ? 1 : cell.getLength() + cell.getSlot() - slot);
                        int colSpan = (cell == null ? 1 : Math.min(length, slotsToEnd));
                        if (cell == null) {
                            String bgColor = model.getBackground(day, slot);
                            if (bgColor == null && !model.isAvailable(day, slot))
                                bgColor = TimetableGridCell.sBgColorNotAvailable;
                            c = createCell();
                            c.setBorderWidthTop(0);
                            c.setBorderWidthBottom(
                                    idx == maxIdx ? 1 : model.getCell(day, slot, idx + 1) != null ? 1 : 0);
                            c.setBorderWidthLeft(0);
                            boolean eod = (slot == iTable.lastSlot());
                            boolean in = !eod && model.getCell(day, slot + 1, idx) == null
                                    && ((slot + 1 - iTable.firstSlot()) % iTable.nrSlotsPerPeriod()) != 0;
                            c.setBorderWidthRight(eod || !in ? 1 : 0);
                            iPdfTable.addCell(c);
                        } else {
                            String bgColor = cell.getBackground();
                            if (iTable.getBgMode() == TimetableGridModel.sBgModeNone) {
                                for (int i = 0; i < length; i++)
                                    if (!model.isAvailable(day, slot + i)) {
                                        bgColor = TimetableGridCell.sBgColorNotAvailableButAssigned;
                                        break;
                                    }
                            }
                            c = createCell();
                            c.setBorderWidthTop(0);
                            c.setBorderWidthLeft(0);
                            c.setColspan(colSpan);
                            if (bgColor != null)
                                c.setBackgroundColor(getColor(bgColor));
                            addText(c, cell.getName());
                            if (iTable.getShowTimes())
                                addText(c, cell.getTime());
                            if (iTable.getResourceType() != TimetableGridModel.sResourceTypeRoom)
                                addText(c, cell.getRoomName());
                            if (iTable.getResourceType() != TimetableGridModel.sResourceTypeInstructor
                                    && iTable.getShowInstructors() && !cell.getInstructor().isEmpty())
                                addText(c, cell.getInstructor());
                            if (iTable.getShowComments())
                                addText(c, cell.getShortCommentNoColors() == null ? null
                                        : cell.getShortCommentNoColors());
                            if (iTable.getWeek() == -100 && cell.hasDays()
                                    && !cell.getDays().equals(iTable.getDefaultDatePatternName()))
                                addText(c, cell.getDays());
                            iPdfTable.addCell(c);
                            slot += length - 1;
                        }
                    }
                }
            }
        } else if (iTable.isDispModeWeekByWeekHorizontal()) {
            Calendar cal = Calendar.getInstance(Locale.US);
            cal.setTime(iTable.iFirstDate);
            for (int d = 0; d < 365; d++) {
                if (d > 0)
                    cal.add(Calendar.DAY_OF_YEAR, 1);
                int date = d + iTable.iFirstDay;
                if (model.getFirstDay() >= 0 && (date < model.getFirstDay() || date > model.getFirstDay() + 6))
                    continue;
                int day = d % 7;
                if (day < iTable.startDay() || day > iTable.endDay())
                    continue;
                boolean hasClasses = false;
                for (int slot = iTable.firstSlot(); slot <= iTable.lastSlot(); slot++) {
                    if (model.getCell(day, slot, 0, date) != null) {
                        hasClasses = true;
                        break;
                    }
                }
                if (!hasClasses)
                    continue;
                int maxIdx = model.getMaxIdxForDay(day, iTable.firstSlot(), iTable.lastSlot(), date);
                for (int idx = 0; idx <= maxIdx; idx++) {
                    PdfPCell c = createCell();
                    c.setBorderWidthTop(0);
                    if (idx == 0)
                        addText(c, Constants.DAY_NAME[day] + " " + sDF.format(cal.getTime()), true);
                    c.setBorderWidthBottom(idx == maxIdx ? 1 : 0);
                    c.setColspan(12);
                    iPdfTable.addCell(c);
                    for (int slot = iTable.firstSlot(); slot <= iTable.lastSlot(); slot++) {
                        int slotsToEnd = iTable.lastSlot() - slot + 1;
                        TimetableGridCell cell = model.getCell(day, slot, idx, date);
                        int length = (cell == null ? 1 : cell.getLength() + cell.getSlot() - slot);
                        int colSpan = (cell == null ? 1 : Math.min(length, slotsToEnd));
                        if (cell == null) {
                            String bgColor = model.getBackground(day, slot);
                            if (bgColor == null && !model.isAvailable(day, slot))
                                bgColor = TimetableGridCell.sBgColorNotAvailable;
                            c = createCell();
                            c.setBorderWidthTop(0);
                            c.setBorderWidthBottom(
                                    idx == maxIdx ? 1 : model.getCell(day, slot, idx + 1, date) != null ? 1 : 0);
                            c.setBorderWidthLeft(0);
                            boolean eod = (slot == iTable.lastSlot());
                            boolean in = !eod && model.getCell(day, slot + 1, idx, date) == null
                                    && ((slot + 1 - iTable.firstSlot()) % iTable.nrSlotsPerPeriod()) != 0;
                            c.setBorderWidthRight(eod || !in ? 1 : 0);
                            iPdfTable.addCell(c);
                        } else {
                            String bgColor = cell.getBackground();
                            if (iTable.getBgMode() == TimetableGridModel.sBgModeNone) {
                                for (int i = 0; i < length; i++)
                                    if (!model.isAvailable(day, slot + i)) {
                                        bgColor = TimetableGridCell.sBgColorNotAvailableButAssigned;
                                        break;
                                    }
                            }
                            c = createCell();
                            c.setBorderWidthTop(0);
                            c.setBorderWidthLeft(0);
                            c.setColspan(colSpan);
                            if (bgColor != null)
                                c.setBackgroundColor(getColor(bgColor));
                            addText(c, cell.getName());
                            if (iTable.getShowTimes())
                                addText(c, cell.getTime());
                            if (iTable.getResourceType() != TimetableGridModel.sResourceTypeRoom)
                                addText(c, cell.getRoomName());
                            if (iTable.getResourceType() != TimetableGridModel.sResourceTypeInstructor
                                    && iTable.getShowInstructors() && !cell.getInstructor().isEmpty())
                                addText(c, cell.getInstructor());
                            if (iTable.getShowComments())
                                addText(c, cell.getShortCommentNoColors() == null ? null
                                        : cell.getShortCommentNoColors());
                            iPdfTable.addCell(c);
                            slot += length - 1;
                        }
                    }
                }
            }
        } else { //isDispModePerWeekVertical
            for (int day = iTable.startDay(); day <= iTable.endDay(); day++) {
                int maxIdx = model.getMaxIdxForDay(day, iTable.firstSlot(), iTable.lastSlot());
                for (int idx = 0; idx <= maxIdx; idx++) {
                    PdfPCell c = null;
                    for (int slot = iTable.lastSlot(); slot >= iTable.firstSlot(); slot--) {
                        int slotsToEnd = slot + 1 - iTable.firstSlot();
                        TimetableGridCell cell = model.getCell(day, slot, idx);
                        int length = (cell == null ? 1 : 1 + slot - cell.getSlot());
                        int colSpan = (cell == null ? 1 : Math.min(length, slotsToEnd));
                        if (cell == null) {
                            String bgColor = model.getBackground(day, slot);
                            if (bgColor == null && !model.isAvailable(day, slot))
                                bgColor = TimetableGridCell.sBgColorNotAvailable;
                            c = createCell();
                            c.setBorderWidthTop(0);
                            c.setMinimumHeight(100f);
                            c.setBorderWidthBottom(
                                    idx == maxIdx ? 1 : model.getCell(day, slot, idx + 1) != null ? 1 : 0);
                            c.setBorderWidthLeft(slot == iTable.lastSlot() ? 1 : 0);
                            boolean eod = (slot == iTable.firstSlot());
                            boolean in = !eod && model.getCell(day, slot - 1, idx) == null
                                    && ((slot - iTable.firstSlot()) % iTable.nrSlotsPerPeriod()) != 0;
                            c.setBorderWidthRight(eod || !in ? 1 : 0);
                            iPdfTable.addCell(c);
                        } else {
                            String bgColor = cell.getBackground();
                            if (iTable.getBgMode() == TimetableGridModel.sBgModeNone) {
                                for (int i = 0; i < length; i++)
                                    if (!model.isAvailable(day, slot - i)) {
                                        bgColor = TimetableGridCell.sBgColorNotAvailableButAssigned;
                                        break;
                                    }
                            }
                            c = createCell();
                            c.setBorderWidthTop(0);
                            c.setBorderWidthLeft(slot == iTable.lastSlot() ? 1 : 0);
                            c.setColspan(colSpan);
                            if (bgColor != null)
                                c.setBackgroundColor(getColor(bgColor));
                            if (iTable.getWeek() == -100 && cell.hasDays()
                                    && !cell.getDays().equals(iTable.getDefaultDatePatternName()))
                                addTextVertical(c, cell.getDays());
                            if (iTable.getShowTimes())
                                addText(c, cell.getTime());
                            if (iTable.getResourceType() != TimetableGridModel.sResourceTypeRoom)
                                addTextVertical(c, cell.getRoomName());
                            if (iTable.getResourceType() != TimetableGridModel.sResourceTypeInstructor
                                    && iTable.getShowInstructors() && !cell.getInstructor().isEmpty())
                                addTextVertical(c, cell.getInstructor());
                            if (iTable.getShowComments())
                                addTextVertical(c, cell.getShortCommentNoColors() == null ? null
                                        : cell.getShortCommentNoColors());
                            addTextVertical(c, cell.getName());
                            iPdfTable.addCell(c);
                            slot -= length - 1;
                        }
                    }
                    c = createCell();
                    c.setBorderWidthTop(0);
                    c.setBorderWidthLeft(0);
                    if (idx == 0)
                        addTextVertical(c, Constants.DAY_NAME[day], true);
                    c.setBorderWidthBottom(idx == maxIdx ? 1 : 0);
                    c.setColspan(12);
                    iPdfTable.addCell(c);
                }
            }
            /*
            int step = iTable.nrSlotsPerPeriod();
            for (int slot=iTable.firstSlot();slot<=iTable.lastSlot();slot+=step) {
               int time = slot * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
            int slotsToEnd = iTable.lastSlot()-slot+1;
            if ((slot%iTable.nrSlotsPerPeriod()) == 0) {
            c = createCell("TimetableHeadCell"+(slot==iTable.firstSlot()?"":"In")+"Vertical");
            addText(c, Constants.toTime(time), true);
            iPdfTable.addCell(c);
            } else {
               c = createCell("TimetableHeadCellInVertical");
               iPdfTable.addCell(c);
            }
            for (int day=iTable.startDay();day<=iTable.endDay();day++) {
               int maxIdx = model.getMaxIdxForDay(day,iTable.firstSlot(),iTable.lastSlot());
               for (int idx=0;idx<=maxIdx;idx++) {
                   TimetableGridCell cell = model.getCell(day,slot, idx);
                   if (model.isRendered(day,slot,idx)) continue;
              int rowSpan = (cell==null?1:Math.min(cell.getLength()+cell.getSlot()-slot,slotsToEnd));
              int colSpan = (iTable.getResourceType()==TimetableGridModel.sResourceTypeDepartment && cell!=null?1:model.getDepth(day,slot,idx,maxIdx,rowSpan)); 
              model.setRendered(day,slot,idx,colSpan,rowSpan);
              int rowSpanDivStep = (int)Math.ceil(((double)rowSpan)/step);
                       
                   if (cell==null) {
                 String bgColor = model.getBackground(day,slot);
                 if (bgColor==null && !model.isAvailable(day,slot))
                    bgColor=TimetableGridCell.sBgColorNotAvailable;
                        boolean eol = (day==iTable.endDay() && (idx+colSpan-1)==maxIdx);
                 c = createCell("TimetableCell"+(slot==iTable.firstSlot()?"":"In")+"Vertical"+(eol?"EOL":""));
                 c.setColspan(colSpan);
                 //c.setRowspan(rowSpanDivStep);
                 if (bgColor!=null)
                    c.setBackgroundColor(getColor(bgColor));
                 iPdfTable.addCell(c);
                   } else {
                      String bgColor = cell.getBackground();
                      if (iTable.getBgMode()==TimetableGridModel.sBgModeNone) {
                          for (int i=0;i<cell.getLength();i++)
                             if (!model.isAvailable(day,slot+i)) {
                                bgColor = TimetableGridCell.sBgColorNotAvailableButAssigned;
                                break;
                             }
                      }
                      boolean eol = (day==iTable.endDay());
                      c = createCell("TimetableCell"+(slot==iTable.firstSlot()?"":"In")+"Vertical" + (eol?"EOL":""));
                 c.setColspan(colSpan);
                 //c.setRowspan(rowSpanDivStep);
                 if (bgColor!=null)
                    c.setBackgroundColor(getColor(bgColor));
                 addText(c, cell.getName());
                 if (iTable.getResourceType()!=TimetableGridModel.sResourceTypeRoom)
                    addText(c, cell.getRoomName());
                 if (iTable.getShowComments())
                    addText(c, cell.getShortComment()==null?"":cell.getShortComment());
                 if (iTable.getWeek()==-100 && cell.hasDays() && !cell.getDays().equals(iTable.getDefaultDatePatternName()))
                    addText(c, cell.getDays());
                 iPdfTable.addCell(c);
                   }
                }
            }
            }
            */
        }

        if (iTable.isDispModePerWeek()) {
            flushTable();
        }
    }

    private void addLegendRow(String color, String text) {
        PdfPCell c = createCellNoBorder();
        c.setBorderWidth(1);
        c.setBackgroundColor(getColor(color));
        iPdfTable.addCell(c);
        c = createCellNoBorder();
        addText(c, "  " + text);
        c.setHorizontalAlignment(Element.ALIGN_LEFT);
        iPdfTable.addCell(c);
    }

    public void printLegend() throws Exception {
        iPdfTable = new PdfPTable(2);
        iPdfTable.setWidths(new float[] { 10f, 200f });
        iPdfTable.getDefaultCell().setPadding(3);
        iPdfTable.getDefaultCell().setBorderWidth(1);
        iPdfTable.setHorizontalAlignment(Element.ALIGN_LEFT);
        iPdfTable.setSplitRows(false);
        iPdfTable.setSpacingBefore(10);
        iPdfTable.setKeepTogether(true);

        if (iTable.getBgMode() != TimetableGridModel.sBgModeNone) {
            PdfPCell c = createCellNoBorder();
            c.setColspan(2);
            addText(c, "Assigned classes:");
            c.setHorizontalAlignment(Element.ALIGN_LEFT);
            iPdfTable.addCell(c);
        }
        if (iTable.getBgMode() == TimetableGridModel.sBgModeTimePref) {
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sRequired), "Required time");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyPreferred),
                    "Strongly preferred time");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sPreferred), "Preferred time");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sNeutral), "No time preference");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sDiscouraged), "Discouraged time");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyDiscouraged),
                    "Strongly discouraged time");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sProhibited), "Prohibited time");
        } else if (iTable.getBgMode() == TimetableGridModel.sBgModeRoomPref) {
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sRequired), "Required room");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyPreferred),
                    "Strongly preferred room");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sPreferred), "Preferred room");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sNeutral), "No room preference");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sDiscouraged), "Discouraged room");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyDiscouraged),
                    "Strongly discouraged room");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sProhibited), "Prohibited room");
        } else if (iTable.getBgMode() == TimetableGridModel.sBgModeStudentConf) {
            for (int nrConflicts = 0; nrConflicts <= 15; nrConflicts++) {
                String color = TimetableGridCell.conflicts2color(nrConflicts);
                addLegendRow(color,
                        "" + nrConflicts + " " + (nrConflicts == 15 ? "or more " : "") + "student conflicts");
            }
        } else if (iTable.getBgMode() == TimetableGridModel.sBgModeInstructorBtbPref) {
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sNeutral),
                    "No instructor back-to-back preference (distance=0)");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sDiscouraged),
                    "Discouraged back-to-back (0<distance<=5)");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyDiscouraged),
                    "Strongly discouraged back-to-back (5<distance<=20)");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sProhibited),
                    "Prohibited back-to-back (20<distance)");
        } else if (iTable.getBgMode() == TimetableGridModel.sBgModeDistributionConstPref) {
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sNeutral),
                    "No violated constraint(distance=0)");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sDiscouraged),
                    "Discouraged/preferred constraint violated");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyDiscouraged),
                    "Strongly discouraged/preferred constraint violated");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sProhibited),
                    "Required/prohibited constraint violated");
        } else if (iTable.getBgMode() == TimetableGridModel.sBgModePerturbations) {
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyPreferred), "No change");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sNeutral), "No initial assignment");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sDiscouraged), "Room changed");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyDiscouraged), "Time changed");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sProhibited), "Both time and room changed");
        } else if (iTable.getBgMode() == TimetableGridModel.sBgModePerturbationPenalty) {
            for (int nrConflicts = 0; nrConflicts <= 15; nrConflicts++) {
                String color = TimetableGridCell.conflicts2color(nrConflicts);
                addLegendRow(color,
                        "" + (nrConflicts == 0 ? "Zero perturbation penalty"
                                : nrConflicts == 15 ? "Perturbation penalty above 15"
                                        : "Perturbation penalty below or equal to " + nrConflicts)
                                + "");
            }
        } else if (iTable.getBgMode() == TimetableGridModel.sBgModeHardConflicts) {
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sRequired), "Required time and room");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyPreferred),
                    "Can be moved in room with no hard conflict");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sPreferred),
                    "Can be moved in room (but there is a hard conflict), can be moved in time with no conflict");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sNeutral),
                    "Can be moved in room (but there is a hard conflict)");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sDiscouraged),
                    "Can be moved in time with no hard conflict, cannot be moved in room");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyDiscouraged),
                    "Can be moved in time (but there is a hard conflict), cannot be moved in room");
        } else if (iTable.getBgMode() == TimetableGridModel.sBgModeDepartmentalBalancing) {
            for (int nrConflicts = 0; nrConflicts <= 3; nrConflicts++) {
                String color = TimetableGridCell.conflicts2colorFast(nrConflicts);
                addLegendRow(color,
                        "" + (nrConflicts == 0 ? "Zero penalty"
                                : nrConflicts == 3 ? "Penalty equal or above 3" : "Penalty equal to " + nrConflicts)
                                + "");
            }
        } else if (iTable.getBgMode() == TimetableGridModel.sBgModeTooBigRooms) {
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sRequired),
                    "Assigned room is smaller than room limit of a class");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sNeutral),
                    "Assigned room is not more than 25% bigger than the smallest avaialable room");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sDiscouraged),
                    "Assigned room is not more than 50% bigger than the smallest avaialable room");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyDiscouraged),
                    "Assigned room is more than 50% bigger than the smallest avaialable room");
        }
        PdfPCell c = createCellNoBorder();
        c.setColspan(2);
        addText(c, "Free times:");
        c.setHorizontalAlignment(Element.ALIGN_LEFT);
        iPdfTable.addCell(c);
        addLegendRow(TimetableGridCell.sBgColorNotAvailable, "Time not available");
        addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sNeutral), "No preference");
        if (iTable.getShowUselessTimes()) {
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sDiscouraged),
                    "Standard (MWF or TTh) time pattern is broken (time cannot be used for MW, WF, MF or TTh class)");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sStronglyDiscouraged), "Useless half-hour");
            addLegendRow(TimetableGridCell.pref2color(PreferenceLevel.sProhibited),
                    "Useless half-hour and broken standard time pattern");
        }

        if (iTable.isDispModePerWeekVertical())
            iDocument.newPage();

        iDocument.add(iPdfTable);
    }
}