天道酬勤

<<iText in Action 2nd>>3.3节(Working with the ColumnText object)读书笔记

前言

在这一节中我们将会学习ColumnText对象的使用:如果只是往ColumnText中加入Chunk或者Phrase对象,那么我们就处于文本模式(text mode);如果加入其它高层次的对象那么就处于组合模式(composite mode)。

在listing3.13种我们调用方法ShowMovieInfo来文档上打印一个大大的P字符。我们希望通过相同的方法往矩形中添加电影的标题,但是ShowTextAligned方法不能包裹文本,而且在这个方法中我们不能再string或者Chunk对象中使用换行。现在我们使用ColumnText提供的SetSimpleColumn方法来完成前一节的Film Festival,代码如下:

listing 3.15 MovieCalendar.cs

protected override  void DrawMovieInfo(Screening screening, PdfContentByte directcontent)
{
    base.DrawMovieInfo(screening, directcontent);
    Rectangle rect = GetPosition(screening );

    ColumnText column = new ColumnText(directcontent);
    column.SetSimpleColumn(new Phrase(screening.Movie.Title), rect.Left, rect.Bottom, rect.Right, rect.Top, 18, Element.ALIGN_CENTER);
    column.Go();
}

最终的效果图如下:

MovieCalander

上图有点模糊,具体的效果大家可以自己打印出来看。现在我们的Film Festival已经完成了,但对ColumnText对象的使用还没有结束。在以上的例子中因为我们可以将内容填充在矩形中,所以是没什么问题,但如果文本不能完全填充在矩形中呢?还有在以上代码中我们只是将Phrase对象加入到column并将其绝对定位,那我们能不能添加其他的对象如Paragraph,List或者Image到ColumnText对象中呢?第一个问题会在后续中解释,第二个问题的回答是“yes”,但不是出于文本模式,而是组合模式。

在文本模式使用ColumnText对象

在2.3节中,我们将电影的信息已Paragraph对象的形式组合并打印在文档上。假设你想重复这一过程,但是以column的形式打印出来,就如下图所示:

MovieCOlumn1

除了使用SetSimpleColumn方法将Phrase对象添加进去之外,我们还可以使用AddText方法将一系列的Phrase或者Chunk对象添加进去。

使用AddText方法添加文本

以下的代码就是将电影的信息以列的形式打印在文档上:

listing 3.16 MovieColumns1.cs

List<Movie> movies = PojoFactory.GetMovies(conn);

ColumnText ct = new ColumnText(writer.DirectContent);

foreach (Movie movie in movies)
{
    ct.AddText(CreateMovieInformation(movie));
    ct.AddText(Chunk.NEWLINE);
}

ct.Alignment = Element.ALIGN_JUSTIFIED;
ct.ExtraParagraphSpace = 6;
ct.SetLeading(0, 1.2f);
ct.FollowingIndent = 27;

int linesWritten = 0;
int column = 0;

int status = ColumnText.NO_MORE_COLUMN;

while (ColumnText.HasMoreText(status))
{
    ct.SetSimpleColumn(COLUMNS[column, 0], COLUMNS[column, 1], COLUMNS[column, 2], COLUMNS[column, 3]);
    ct.YLine = COLUMNS[column, 3];
    status = ct.Go();
    linesWritten += ct.LinesWritten;
    column = Math.Abs(column - 1);
    if (column == 0)
    {
        document.NewPage();
    }
}

ct.AddText(new Phrase("Line written: " + linesWritten));
ct.Go();

如同在代码Listing3.15中一样,我们传入一个PdfContentByte对象的实例构建一个ColumnText对象,然后通过AddText方法将Chunk或者Phrase对象添加进去。接下来我们设置了文本的很多属性:文本的对其,行高,段落之间的距离已经特殊的缩减。代码中我们还定义了一些变量来保存状态,如lineWritten保存的就是已经打印的行数,但是变量column(保存的是列的号码)和变量satus更加的重要。因为我们希望将所有的信息全部打印出来,所以在一个while循环语句中我们不断地通过Go方法来输出文本。代码中大家要注意的是我们定义了COLUMNS数组,其就是将页面设置为两列的形式,还有ColumnText的YLine属性,这是文本在一列中被打印出来的y座标。

当我们调用Go方法时,ColumnText里面的内容就会被打印出来,如果文本不能完全填充当前的列,那剩下的文本还是保留在ColumnText对象中。当一列被完全填充后我们需要换到其他的列继续往里面填充内容,最后当前页中没有其他的列的话就需要新的一页。

以上的代码我们还通过Alignment属性设置了文本的对其方式,这个和Paragraph对象的属性是同一个名词,作用也一样。SetLeadingf方法是设置行高,有两个参数,第一个是决定的行高0,第二个是相对的行高1.2,最后的行高就是:0+1.2*12(字体大小)=14.4pt。

COLUMNTEXT的属性

以上的代码中,虽然没有用到Paragraph对象,但通过设置ColumnText的ExtraParagraphSpace属性,这样就为每一段添加一些格外的空间,其他的如FollowingIndent属性可以设置缩减。在2.2节的时候我们学会如何在PdfWriter层次上改变字符空间比率,但这会影响所有的高层次的对象。在ColumnText中也有SpaceCharRatio属性来修改,这样的话就不至于影响所有的高层次对象。

ADDING CONTENT IN SMALL PORTIONS

在代码listing 3.16种,我们将数据库中所有的电影信息一次性全部填充在ColumnText对象中,然后再通过Go方法将其打印出来。但数据库中有120部电影,对应的ColumnText中就会保存120个Phrase对象的信息。所以竟可能早的频繁调用Go方法可以避免内存的消耗,就如以下代码:

listing 3.17 MovieColumns2.cs

List<Movie> movies = PojoFactory.GetMovies(conn);

ColumnText ct = new ColumnText(writer.DirectContent);
ct.Alignment = Element.ALIGN_JUSTIFIED;
ct.ExtraParagraphSpace = 6;
ct.Leading = 14;
ct.Indent = 10;
ct.RightIndent = 3;
ct.SpaceCharRatio = PdfWriter.NO_SPACE_CHAR_RATIO;

int column = 0;
int status = ColumnText.NO_MORE_COLUMN;
ct.SetSimpleColumn(COLUMNS[column, 0], COLUMNS[column, 1], COLUMNS[column, 2], COLUMNS[column, 3]);

foreach (Movie movie in movies)
{
    ct.AddText(CreateMovieInformation(movie));
    status = ct.Go();

    if (ColumnText.HasMoreText(status))
    {

        column = Math.Abs(column - 1);
        if (column == 0)
        {
            document.NewPage();
        }

        ct.SetSimpleColumn(COLUMNS[column, 0], COLUMNS[column, 1], COLUMNS[column, 2], COLUMNS[column, 3]);
        ct.YLine = COLUMNS[column, 3];
        status = ct.Go();
    }
}

以上代码中,但我们往ColumnText对象中添加内容时就调用Go方法,这样ColumnText中保存的内容就随之减少,但效果是一样的。不过这里有一个问题:如果我们希望将一部电影的信息放在一起,而不是被分隔为两列,那要如何处理?

ADDING CONTENT IN SIMULATION MODE

以下的代码就是解决刚刚提到的问题,这里我们使用了一个特殊的Go方法:

listing 3.18 MovieColumns3.cs

List<Movie> movies = PojoFactory.GetMovies(conn);
ColumnText ct = new ColumnText(writer.DirectContent);

int column = 0;

ct.SetSimpleColumn(COLUMNS[column, 0], COLUMNS[column, 1], COLUMNS[column, 2], COLUMNS[column, 3]);
int status = ColumnText.NO_MORE_COLUMN;

Phrase p;
float y;

foreach (Movie movie in movies)
{
    y = ct.YLine;
    p = CreateMovieInformation(movie);
    ct.AddText(p);
    status = ct.Go(true);

    if (ColumnText.HasMoreText(status))
    {
        column = Math.Abs(column - 1);
        if (column == 0)
        {
            document.NewPage();
        }

        ct.SetSimpleColumn(COLUMNS[column, 0], COLUMNS[column, 1], COLUMNS[column, 2], COLUMNS[column, 3]);
        y = COLUMNS[column, 3];
    }

    ct.YLine = y;
    ct.SetText(p);
    status = ct.Go(false);
}

代码3.18和3.17几乎是相同的,但在3.18种我们调用了两次Go方法:第一次的Go方法是模拟打印内容,实际上没有内容被打印出来。这个时候再去判断内容是否可以在一列中完全填充完,如果不可以就去下一列,当前页中没有其他列就新开一页。然后再设置ColumnText对象的YLine属性使之从刚刚模拟的地方再次添加,不过这里要调用的是SetText方法。SetText方法将在ColumnText对象中已经被打印但又可能还保留的内容清除,这样可以避免内容便添加了两次。最后就在此调用Go方法即可。

IRREGULAR COLUMNS

以上的代码列的实现是通过一个设置两个矩形来完成的,但我们还可以将其设置为不规则的列,以下的代码就是将每一列设置为一个多边形,结果就是文本流动在图中的方框之间。

MovieColumn4

listing 3.19 MovieColumns4.cs

List<Movie> movies = PojoFactory.GetMovies(conn);
ColumnText ct = new ColumnText(canvas);
ct.Alignment = Element.ALIGN_JUSTIFIED;
ct.Leading = 14;

int column = 0;
ct.SetColumns(LEFT[column], RIGHT[column]);
int status = ColumnText.NO_MORE_COLUMN;
Phrase p;
float y;

foreach (Movie movie in movies)
{
    y = ct.YLine;
    p = CreateMovieInformation(movie);
    ct.AddText(p);
    status = ct.Go(true);

    if (ColumnText.HasMoreText(status))
    {

        column = Math.Abs(column - 1);

        if (column == 0)
        {
            document.NewPage();
            DrawRectangle(canvas);
        }
                               
        ct.SetColumns(LEFT[column], RIGHT[column]);
        y = 806;
    }
    ct.YLine = y;
    ct.SetText(p);
    status = ct.Go();
}

这里的代码和前面的基本相同,只不同调用了SetColumns方法而不是SetSimpleColumn方法。SetColumns方法的参数如下:

但不规则的列在组合模式中是不容许的。

在组合模式下使用ColumnText对象

目前为止,我们只是调用了COlumnText的AddText和SetText方法,下面我们会用到AddElement方法,当使用这个方法时,ColumnText会自动的从文本模式转换为组合模式。

ADDING CONTENT WITH ADDELEMENT()

下图显示的是四列的一个文档,而且我们将Image,Paragraph,List和Chunk对象加入到里面。

ColumnMovie1

以下是实现的代码:

listing 3.20 ColumnsMovies1.cs

public void AddContent(ColumnText ct, Movie movie, Image img)
{
    ct.AddElement(img);
    ct.AddElement(new Paragraph(movie.Title, FilmFonts.BOLD));
    if (movie.OriginalTitle != null)
    {
        ct.AddElement(new Paragraph(movie.OriginalTitle, FilmFonts.ITALIC));
    }
    
    ct.AddElement(PojoToElementFactory.GetDirectorList(movie));
    ct.AddElement(PojoToElementFactory.GetYearPhrase(movie));
    ct.AddElement(PojoToElementFactory.GetDurationPhrase(movie));
    ct.AddElement(Chunk.NEWLINE);
}

这个AddContent方法和我们前面碰到的基本上差不多。

listing 3.21 ColumnsMovies1.cs (continued)

List<Movie> movies = PojoFactory.GetMovies(conn);
ColumnText ct = new ColumnText(writer.DirectContent);

int column = 0;
ct.SetSimpleColumn(COLUMNS[column][0], COLUMNS[column][1], COLUMNS[column][2], COLUMNS[column][3]);

int status = ColumnText.NO_MORE_COLUMN;
float y;
Image img;

foreach (Movie movie in movies)
{
    y = ct.YLine;

    img = Image.GetInstance(string.Format(RESOURCE, movie.IMDB));
    img.ScaleToFit(80, 1000);
    AddContent(ct, movie, img);
    status = ct.Go(true);

    if (ColumnText.HasMoreText(status))
    {
        column = (column + 1) % 4;
        if (column == 0)
        {
            document.NewPage();
        }

        ct.SetSimpleColumn(COLUMNS[column][0], COLUMNS[column][1], COLUMNS[column][2], COLUMNS[column][3]);
        y = COLUMNS[column][3];
    }

    ct.YLine = y;
    ct.SetText(null);
    AddContent(ct, movie, img);
    status = ct.Go();
}

这里我们又调用了两次的Go方法:一次是模拟添加,一次是实际的添加。这样就可以确保一部电影的信息是在同一列中。这里特别要注意的是以下代码:

ct.SetText(null);

如果我们注释这一行的话你会发现文档中的部分内容被添加了两次。

PROPERTIES OF THE COLUMNTEXT OBJECT VERSUS ELEMENT PROPERTIES

一旦我们调用了AddElement方法,在前面提到的一些ColumnText的属性如行高,对其等就不会起作用了,相反通过AddElement添加的元素自身的属性就会起作用了。如下图中的Paragraph对象就被居中,左对其和两段对其。

MovieColumn2

以上的图我们可以重用listing 3.21的代码,并只要修改方法AddContent即可,修改的代码如下:

listing 3.21 ColumnsMovies2.cs

public void AddContent(ColumnText ct, Movie movie)
{
    Paragraph p;
    p = new Paragraph(new Paragraph(movie.Title, FilmFonts.BOLD));
    p.Alignment = Element.ALIGN_CENTER;
    p.SpacingBefore = 16;
    ct.AddElement(p);

    if (movie.OriginalTitle != null)
    {
        p = new Paragraph(movie.OriginalTitle, FilmFonts.ITALIC);
        p.Alignment = Element.ALIGN_RIGHT;
        ct.AddElement(p);
    }

    p = new Paragraph();
    p.Add(PojoToElementFactory.GetYearPhrase(movie));
    p.Add(" ");
    p.Add(PojoToElementFactory.GetDurationPhrase(movie));
    p.Alignment = Element.ALIGN_JUSTIFIED;
    ct.AddElement(p);

    p = new Paragraph(new Chunk(new StarSeparator()));
    p.SpacingAfter = 12;
    ct.AddElement(p);

}

总结

ColumnText的文本模式和组合模式在我们创建PdfPCell的时候还会再次提到,但现在我们还要对已经创建好的Film Festival进行一些处理。最终打印的效果是完全一样的,不过我们接下来会学会如何通过重用数据来减少文档的大小。最后是本次demo的代码下载

同步

此文章已同步到目录索引:iText in Action 2nd 读书笔记。

posted @ 2012-06-28 22:10  JulyLuo  阅读(4003)  评论(0编辑  收藏  举报