[更文挑战]iText番盘-PDF神器-5

279 阅读4分钟

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战

PDF神器iText英文版翻译与学习

作者: 薛大郎.
英文原版:iText in Action 2nd Edition.pdf

坚持. 分享. 成长. 利他 !

一. 前言

人心里的成见,就像一座大山

每个人的出生背景和成长环境都不同, 价值观和喜好也非常的不同, 不过还好, 佛家说, 是五百次回眸的执着才换来今生的擦肩而过,是千年不变的守候才有了今生的默默相守?十年修得同船渡.百年修得共枕眠. 从这两个角度看来, 对待身边的人就应该像稻盛和夫说的那样, 要心存感恩之心. 不要总是按照自己的固有思维对他人囚禁, 其实有这些成见之人也正是被自己囚禁起来的犯人.

当我走出囚室迈向通往自由的监狱大门时,我已经清楚,自己若不能把痛苦与怨恨留在身后,那么其实我仍在狱中。 ----纳尔逊·罗利赫拉赫拉·曼德拉

二.正文.

4. 学习使用Element高级元素

  1. 首先看下Element的UML关系图

elementUml.png

// 从源码来看可以添加所有上边实现了Element接口的类
public boolean add(Element element) throws DocumentException {
      // 关闭状态的Document是不可以添加元素的 
      if (close) {
           throw new DocumentException(MessageLocalization.getComposedMessage("the.document.has.been.closed.you.can.t.add.any.elements"));
      }
      // 没有打开的状态也不可以添加元素, 大部分的Element#isContent()返回true;
      if (!open && element.isContent()) {
           throw new DocumentException(MessageLocalization.getComposedMessage("the.document.is.not.open.yet.you.can.only.add.meta.information"));
      }
      boolean success = false;
      // 自动添加章节数元素 特殊处理
      if (element instanceof ChapterAutoNumber) {
          chapternumber = ((ChapterAutoNumber)element).setAutomaticNumber(chapternumber);
      }
      // DocListener 也是Element的实现, 需要特殊对待, 而且也可以看出来Document可添加多个文档监听类
      for (DocListener listener : listeners) {
          success |= listener.add(element);
      }
      // 大元素的特殊处理,
      if (element instanceof LargeElement) {
          LargeElement e = (LargeElement)element;
          if (!e.isComplete())
              e.flushContent();
      }
      return success;
  }

当看完Document#add源码的时候, 是不是感觉就懵了. 那我们HelloWorld是怎样添加进Paragraph的. 别急, 其实这也是iText的coder给我们做好的设计, 接下来我们就继续往下看, 但是这里还是要给大家说下, Java作为一个Object Oriented语言, 抽象的特性是我们作为开发必须要去悟道的一个点, 代码结构上的设计, 抽象的设计, 以及设计模式的运用都是优秀源码中我们看到的, 而且我们应该追逐的和理解的. 设计优秀的源码高扩展性, 高内聚低耦合, 优美, 易编码;

    // 通过查看PdfWriter的源码, getInstance重载也都是使用的addDocListener方法添加一个**Documnet的子类PdfDocumnet**来监听document, 从而所有Document的add方法调取PdfDocument#add方法
    public static PdfWriter getInstance(final Document document, final OutputStream os)
        throws DocumentException {
        PdfDocument pdf = new PdfDocument();
        document.addDocListener(pdf);
        PdfWriter writer = new PdfWriter(pdf, os);
        pdf.addWriter(writer);
        return writer;
    }

那接下来看PdfDocument的添加方法, 我想大家也大概都想到了就是各种元素的特殊处理.

// ... 为省略部分代码实现
public boolean add(final Element element) throws DocumentException {
    if (writer != null && writer.isPaused()) {
        return false;
    }
    try {
        if (element.type() != Element.DIV) {
            flushFloatingElements();
        }
       // 源码 => TODO refactor this uber long switch to State/Strategy or something ...
       // 对于目前(5.x版本)的设计来说, 暂时这样也是非常合理的, element.type()返回的为Element的int常量, 对于int的设计大家是不是想起来了Thread的状态int设计呢?
        switch(element.type()) {
            // Information (headers)
            case Element.HEADER:
                info.addkey(((Meta)element).getName(), ((Meta)element).getContent());
                break;
            case Element.TITLE:
                info.addTitle(((Meta)element).getContent());
                break;
            case Element.SUBJECT:
                info.addSubject(((Meta)element).getContent());
                break;
            case Element.KEYWORDS:
                info.addKeywords(((Meta)element).getContent());
                break;
            case Element.AUTHOR:
                info.addAuthor(((Meta)element).getContent());
                break;
            case Element.CREATOR:
                info.addCreator(((Meta)element).getContent());
                break;
            case Element.LANGUAGE:
                setLanguage(((Meta)element).getContent());
                break;
            case Element.PRODUCER:
                ...
                break;
            case Element.CREATIONDATE:
                ...
                break;
            case Element.CHUNK: {
                ...
                break;
            }
            case Element.ANCHOR: {
               ...
                break;
            }
            case Element.ANNOTATION: {
                ...
                break;
            }
            case Element.PHRASE: {
                ...
                break;
            }
            case Element.PARAGRAPH: {
                ...
                break;
            }
            case Element.SECTION:
            case Element.CHAPTER: {
                ...
                break;
            }
            case Element.LIST: {
                ...
                break;
            }
            case Element.LISTITEM: {
                ...
                break;
            }
            case Element.RECTANGLE: {
               ...
                break;
            }
            case Element.PTABLE: {
                ...
                break;
            }
            // 熟悉java 的都知道 这几个case将用同一个逻辑然后再IMGTEMPLATE:break出来.
            case Element.JPEG:
            case Element.JPEG2000:
            case Element.JBIG2:
            case Element.IMGRAW:
            case Element.IMGTEMPLATE: {
                ...
                break;
            }
            case Element.YMARK: {
               ...
                break;
            }
            case Element.MARKED: {
               ...
               break;
            }
            case Element.WRITABLE_DIRECT:
               ...
               break;
            case Element.DIV:
                ...
                break;
            default:
                return false;
        }
        // 记录添加的最后一个元素, 用于处理是否换行和同一个段落要建新页面的逻辑处理
        lastElementType = element.type();
        return true;
    }
    catch(Exception e) {
        throw new DocumentException(e);
    }
}
在本篇最后, 还是想跟读者多聊一些关于我们在平常开发过程中, 所有的需求都很急, 但是不是咱们编写the Mountain of Shit的理由, 当某些逻辑或者判断, 以及配置等等再出现2次时, 我们就需要看是否应该提出来, 要么作为新方法进行稍微优化, 如果出现3次, 那就需要我们很好的设计了, 或者选择相应的设计模式, 或者不想过度设计可以使用简易的工厂模式来处理, 如果出现3次以上, 那就需要我们进行很好的设计;

也许我们不是架构师, 为什么要这样要求自己呢, 可以这样想, 只是不被后来维护者爆出WTF也是不错的.