目录

Go使用pdfcpu操作pdf文件

Go使用pdfcpu操作pdf文件

最近有一个新需求是移除pdf的logo和部分文本,也就了解了一下用go操作pdf。unidoc/unipdf这个库好像功能最强大,但是商业化使用需要授权,开源库里也是混淆代码。最后选用的是[pdfcpu/pdfcpu],能够一定程度上处理pdf,但仍不能实现我对需求,只能借助这个库的现有功能手动写了。

首先是移除logo,通过pdfcpu images list -p=1 .\test.pdf可以看到pdf内的图片信息:

pdfcpu images list -p=1 .\test.pdf

.\test.pdf:
1 images available (11 KB)
Page │ Obj# │ Id   │ Type  SoftMask ImgMask │ Width │ Height │ ColorSpace Comp bpc Interp │  Size │ Filters
━━━━━┿━━━━━━┿━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━┿━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━┿━━━━━━━━━━━━
113 │ img1 │ image    *             │   735160 │     CalRGB    3   811 KB │ FlateDecode

不过pdfcpu并没有提供主动删除图片的方式。通过解析处理图片的相关方法可以了解到图片相关信息存在ctx.XRefTable.Table中,这是一个map[int]*model.XRefTableEntry类型的映射。再结合上面得到的图片信息,从中删除logo图片的对象id就行。

func DeleteObjFromPdf(rs io.ReadSeeker, w io.Writer, objNr int) error {
    if rs == nil {
        return errors.New("pdfcpu: Optimize: missing rs")
    }

    conf := model.NewDefaultConfiguration()

    ctx, err := api.ReadValidateAndOptimize(rs, conf)
    if err != nil {
        return err
    }

    delete(ctx.XRefTable.Table, objNr)

    if err = api.WriteContext(ctx, w); err != nil {
        return err
    }

    return nil
}

至于删除文本,我原本想的是提取原始内容流,删除相应文本后再写回去:

func RemoveTextFromPage(ctx *model.Context, pageNum int, textToRemove string) error {
    // 提取原始内容流
    r, err := pdfcpu.ExtractPageContent(ctx, pageNum)
    if err != nil || r == nil {
        return err
    }

    data, err := io.ReadAll(r)
    if err != nil {
        return err
    }

    // TODO: 删除特定文本

    // TODO: 写回ctx

    return nil
}

但解析出来的的内容流太复杂,我又不懂pdf语法,只好退而求其次,用一个纯色图章遮住目标文本凑合用:

func hideTextWithStamp(rs io.ReadSeeker, w io.Writer) error {
    text := " "
    descs := []string{
        "fillc:white",
        "bgcolor:white",
        "pos:c",
        "off:0 250",
        "points:36",
        "scale:1 abs",
        "ma:8 250",
        "rot:0.0",
    }
    desc := strings.Join(descs, ",")
    onTop := true
    update := false
    wm, err := api.TextWatermark(text, desc, onTop, update, types.POINTS)
    if err != nil {
        return err
    }
    selectedPages := []string{"1"}
    if err := api.AddWatermarks(rs, w, selectedPages, wm, nil); err != nil {
        return err
    }
    return nil
}

就这样,勉强算是解决了这个需求。感觉进阶的话可以了解一下pdf语法,再尝试去手动编辑内容流。