如何查找 PDF 中所有出现的特定文本并在上方插入分页符?

2023-12-10

我对 PDF 有一个棘手的要求

我需要在 pdf 中搜索特定字符串 - 属性编号:

每次找到这个,我都需要在上面添加一个分页符

我可以访问 IText 和 Spire.PDF,我首先查看 IText

我从这里的其他帖子中确定我需要使用 PDF Stamper

下面的逻辑添加了一个有效的新页面

但是,就我而言,我只需要分页符而不是空白页

var newFile = @"c:\temp\full.pdf";
var dest = @"c:\temp\dest.pdf";
var reader = new PdfReader(newFile);
if (File.Exists(dest))
{
  File.Delete(dest);
}

var stamper = new PdfStamper(reader, new FileStream(dest, FileMode.CreateNew));
var total = reader.NumberOfPages + 1;
for (var pageNumber = total; pageNumber > 0; pageNumber--)
{
  var pageContent = reader.GetPageContent(pageNumber);
  stamper.InsertPage(pageNumber, PageSize.A4);
}

stamper.Close();
reader.Close();

下图显示了一个示例,因此这实际上是 3 页,即现有页面,在第一次出现的属性编号上方插入一个新分页符:

在第二次出现的上方需要另一个分页符

enter image description here


这个答案分享了一个概念验证查找 PDF 中所有出现的特定文本并在上方插入分页符使用 iText 和 Java。将其移植到 iTextSharp 和 C# 应该不会太困难。

此外,对于生产使用,必须添加一些额外的代码,因为当前代码做出了一些假设,例如假定非旋转页面。此外,它根本不处理注释。

该任务实际上是两个任务的组合,finding插入分页符,因此我们需要

  1. 一些自定义文本位置的提取策略和
  2. 剪切页面的工具。

搜索文本位置提取策略

为了提取自定义文本的位置,我们扩展了 iTextLocationTextExtractionStrategy还允许提取自定义文本文本字符串的位置,实际上是正则表达式的匹配项:

public class SearchTextLocationExtractionStrategy extends LocationTextExtractionStrategy {
    public SearchTextLocationExtractionStrategy(Pattern pattern) {
        super(new TextChunkLocationStrategy() {
            public TextChunkLocation createLocation(TextRenderInfo renderInfo, LineSegment baseline) {
                // while baseLine has been changed to not neutralize
                // effects of rise, ascentLine and descentLine explicitly
                // have not: We want the actual positions.
                return new AscentDescentTextChunkLocation(baseline, renderInfo.getAscentLine(),
                        renderInfo.getDescentLine(), renderInfo.getSingleSpaceWidth());
            }
        });
        this.pattern = pattern;
    }

    static Field locationalResultField = null;
    static Method filterTextChunksMethod = null;
    static Method startsWithSpaceMethod = null;
    static Method endsWithSpaceMethod = null;
    static Field textChunkTextField = null;
    static Method textChunkSameLineMethod = null;
    static {
        try {
            locationalResultField = LocationTextExtractionStrategy.class.getDeclaredField("locationalResult");
            locationalResultField.setAccessible(true);
            filterTextChunksMethod = LocationTextExtractionStrategy.class.getDeclaredMethod("filterTextChunks",
                    List.class, TextChunkFilter.class);
            filterTextChunksMethod.setAccessible(true);
            startsWithSpaceMethod = LocationTextExtractionStrategy.class.getDeclaredMethod("startsWithSpace",
                    String.class);
            startsWithSpaceMethod.setAccessible(true);
            endsWithSpaceMethod = LocationTextExtractionStrategy.class.getDeclaredMethod("endsWithSpace", String.class);
            endsWithSpaceMethod.setAccessible(true);
            textChunkTextField = TextChunk.class.getDeclaredField("text");
            textChunkTextField.setAccessible(true);
            textChunkSameLineMethod = TextChunk.class.getDeclaredMethod("sameLine", TextChunk.class);
            textChunkSameLineMethod.setAccessible(true);
        } catch (NoSuchFieldException | SecurityException | NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public Collection<TextRectangle> getLocations(TextChunkFilter chunkFilter) {
        Collection<TextRectangle> result = new ArrayList<>();
        try {
            List<TextChunk> filteredTextChunks = (List<TextChunk>) filterTextChunksMethod.invoke(this,
                    locationalResultField.get(this), chunkFilter);
            Collections.sort(filteredTextChunks);

            StringBuilder sb = new StringBuilder();
            List<AscentDescentTextChunkLocation> locations = new ArrayList<>();
            TextChunk lastChunk = null;
            for (TextChunk chunk : filteredTextChunks) {
                String chunkText = (String) textChunkTextField.get(chunk);
                if (lastChunk == null) {
                    // Nothing to compare with at the end
                } else if ((boolean) textChunkSameLineMethod.invoke(chunk, lastChunk)) {
                    // we only insert a blank space if the trailing character of the previous string
                    // wasn't a space,
                    // and the leading character of the current string isn't a space
                    if (isChunkAtWordBoundary(chunk, lastChunk)
                            && !((boolean) startsWithSpaceMethod.invoke(this, chunkText))
                            && !((boolean) endsWithSpaceMethod.invoke(this, chunkText))) {
                        sb.append(' ');
                        LineSegment spaceBaseLine = new LineSegment(lastChunk.getEndLocation(),
                                chunk.getStartLocation());
                        locations.add(new AscentDescentTextChunkLocation(spaceBaseLine, spaceBaseLine, spaceBaseLine,
                                chunk.getCharSpaceWidth()));
                    }
                } else {
                    assert sb.length() == locations.size();
                    Matcher matcher = pattern.matcher(sb);
                    while (matcher.find()) {
                        int i = matcher.start();
                        Vector baseStart = locations.get(i).getStartLocation();
                        TextRectangle textRectangle = new TextRectangle(matcher.group(), baseStart.get(Vector.I1),
                                baseStart.get(Vector.I2));
                        for (; i < matcher.end(); i++) {
                            AscentDescentTextChunkLocation location = locations.get(i);
                            textRectangle.add(location.getAscentLine().getBoundingRectange());
                            textRectangle.add(location.getDescentLine().getBoundingRectange());
                        }

                        result.add(textRectangle);
                    }

                    sb.setLength(0);
                    locations.clear();
                }
                sb.append(chunkText);
                locations.add((AscentDescentTextChunkLocation) chunk.getLocation());
                lastChunk = chunk;
            }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }

    @Override
    public void renderText(TextRenderInfo renderInfo) {
        for (TextRenderInfo info : renderInfo.getCharacterRenderInfos())
            super.renderText(info);
    }

    public static class AscentDescentTextChunkLocation extends TextChunkLocationDefaultImp {
        public AscentDescentTextChunkLocation(LineSegment baseLine, LineSegment ascentLine, LineSegment descentLine,
                float charSpaceWidth) {
            super(baseLine.getStartPoint(), baseLine.getEndPoint(), charSpaceWidth);
            this.ascentLine = ascentLine;
            this.descentLine = descentLine;
        }

        public LineSegment getAscentLine() {
            return ascentLine;
        }

        public LineSegment getDescentLine() {
            return descentLine;
        }

        final LineSegment ascentLine;
        final LineSegment descentLine;
    }

    public class TextRectangle extends Rectangle2D.Float {
        public TextRectangle(final String text, final float xStart, final float yStart) {
            super(xStart, yStart, 0, 0);
            this.text = text;
        }

        public String getText() {
            return text;
        }

        final String text;
    }

    final Pattern pattern;
}

(SearchTextLocationExtractionStrategy.java)

由于基类中一些必要的成员是私有的或包私有的,我们必须使用反射来提取它们。

AbstractPdf页面分割工具

该工具的页面分割功能已从PdfVeryDenseMergeTool from 这个答案。此外,允许自定义分页位置是抽象的。

public abstract class AbstractPdfPageSplittingTool {
    public AbstractPdfPageSplittingTool(Rectangle size, float top) {
        this.pageSize = size;
        this.topMargin = top;
    }

    public void split(OutputStream outputStream, PdfReader... inputs) throws DocumentException, IOException {
        try {
            openDocument(outputStream);
            for (PdfReader reader : inputs) {
                split(reader);
            }
        } finally {
            closeDocument();
        }
    }

    void openDocument(OutputStream outputStream) throws DocumentException {
        final Document document = new Document(pageSize, 36, 36, topMargin, 36);
        final PdfWriter writer = PdfWriter.getInstance(document, outputStream);
        document.open();
        this.document = document;
        this.writer = writer;
        newPage();
    }

    void closeDocument() {
        try {
            document.close();
        } finally {
            this.document = null;
            this.writer = null;
            this.yPosition = 0;
        }
    }

    void newPage() {
        document.newPage();
        yPosition = pageSize.getTop(topMargin);
    }

    void split(PdfReader reader) throws IOException {
        for (int page = 1; page <= reader.getNumberOfPages(); page++) {
            split(reader, page);
        }
    }

    void split(PdfReader reader, int page) throws IOException
    {
        PdfImportedPage importedPage = writer.getImportedPage(reader, page);
        PdfContentByte directContent = writer.getDirectContent();
        yPosition = pageSize.getTop();

        Rectangle pageSizeToImport = reader.getPageSize(page);
        float[] borderPositions = determineSplitPositions(reader, page);
        if (borderPositions == null || borderPositions.length < 2)
            return;

        for (int borderIndex = 0; borderIndex + 1 < borderPositions.length; borderIndex++) {
            float height = borderPositions[borderIndex] - borderPositions[borderIndex + 1];
            if (height <= 0)
                continue;

            directContent.saveState();
            directContent.rectangle(0, yPosition - height, pageSizeToImport.getWidth(), height);
            directContent.clip();
            directContent.newPath();

            writer.getDirectContent().addTemplate(importedPage, 0, yPosition - (borderPositions[borderIndex] - pageSizeToImport.getBottom()));

            directContent.restoreState();
            newPage();
        }
    }

    protected abstract float[] determineSplitPositions(PdfReader reader, int page);

    Document document = null;
    PdfWriter writer = null;
    float yPosition = 0;

    final Rectangle pageSize;
    final float topMargin;
}

(AbstractPdfPageSplittingTool.java)

音乐会中的使用

执行OP的任务:

我需要在 pdf 中搜索特定字符串 - 属性编号:

每次找到这个,我都需要在上面添加一个分页符

可以像这样使用上面的类:

AbstractPdfPageSplittingTool tool = new AbstractPdfPageSplittingTool(PageSize.A4, 36) {
    @Override
    protected float[] determineSplitPositions(PdfReader reader, int page) {
        Collection<TextRectangle> locations = Collections.emptyList();
        try {
            PdfReaderContentParser parser = new PdfReaderContentParser(reader);
            SearchTextLocationExtractionStrategy strategy = new SearchTextLocationExtractionStrategy(
                    Pattern.compile("Property Number"));
            parser.processContent(page, strategy, Collections.emptyMap()).getResultantText();
            locations = strategy.getLocations(null);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        List<Float> borders = new ArrayList<>();
        for (TextRectangle rectangle : locations)
        {
            borders.add((float)rectangle.getMaxY());
        }

        Rectangle pageSize = reader.getPageSize(page);
        borders.add(pageSize.getTop());
        borders.add(pageSize.getBottom());
        Collections.sort(borders, Collections.reverseOrder());

        float[] result = new float[borders.size()];
        for (int i=0; i < result.length; i++)
            result[i] = borders.get(i);
        return result;
    }
};

tool.split(new FileOutputStream(RESULT), new PdfReader(SOURCE));

(SplitPages.java测试方法testSplitDocumentAboveAngestellter)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何查找 PDF 中所有出现的特定文本并在上方插入分页符? 的相关文章

  • 扫描文本文件时如何跳过行?

    我想扫描一个文件并在阅读之前跳过一行文本 我试过 fscanf pointer n struct test i j 但这个语法只是从第一行开始 我可以使用 scanf 使用以下指令跳过行 fscanf config file n n 格式字
  • 为什么opencv videowriter这么慢?

    你好 stackoverflow 社区 我有一个棘手的问题 我需要你的帮助来了解这里发生了什么 我的程序从视频采集卡 Blackmagic 捕获帧 到目前为止 它工作得很好 同时我用 opencv cv imshow 显示捕获的图像 它也工
  • 返回 int& 的函数[重复]

    这个问题在这里已经有答案了 我在网上查了一下发现一篇试图解释的文章std move和右值 http thbecker net articles rvalue references section 01 html并发现了一些我实在无法掌握的东
  • 关闭 XDOCUMENT 的实例

    我收到这个错误 该进程无法访问文件 C test Person xml 因为它是 被另一个进程使用 IOException 未处理 保存文件内容后如何关闭 xml 文件的实例 using System using System Collec
  • 在通过网络发送之前压缩位图

    我正在尝试通过网络发送位图屏幕截图 因此我需要在发送之前对其进行压缩 有一个库或方法可以做到这一点吗 当您将图像保存到流时 您have选择一种格式 几乎所有位图格式 bmp gif jpg png 都使用一种或多种压缩形式 因此 只需选择适
  • C 中的模仿函数重写

    具体来说 函数重写能够调用基本重写方法 这有两部分 一个是预编译的库代码 1 另一个是库的用户代码 2 我在这里实现了一个尽可能最小的经典 Person 和 Employee 示例 非常感谢了解 OOP 概念的铁杆 C 开发人员的回应 我正
  • .net Framework (.net 4.0) 中定义 Base 3 数字的类

    我正在寻找一些可以用来定义 3 基数 三进制数 的类 有什么我可以在 net 框架中使用的东西或者我需要写一些东西吗 谢谢你的帮助 您可以使用解析Convert ToInt32 s base http msdn microsoft com
  • 使用 openssl 检查服务器安全协议

    我有一个框架应用程序 它根据使用方式连接到不同的服务器 对于 https 连接 使用 openssl 我的问题是 我需要知道我连接的服务器是否使用 SSL 还是 TLS 以便我可以创建正确的 SSL 上下文 目前 如果我使用错误的上下文尝试
  • 为什么重载方法在 ref 仅符合 CLS 方面有所不同

    公共语言规范对方法重载非常严格 仅允许根据其参数的数量和类型来重载方法 如果是泛型方法 则根据其泛型参数的数量进行重载 根据 csc 为什么此代码符合 CLS 无 CS3006 警告 using System assembly CLSCom
  • 如何在 C# 中使用 XmlDsigC14NTransform 类

    我正在尝试使用规范化 xml 节点System Security Cryptography Xml XMLDsigC14nTransformC net Framework 2 0 的类 该实例需要三种不同的输入类型 NodeList Str
  • 从包含大量文件的目录中检索文件

    我的目录包含近 14 000 000 个 wav 格式的音频样本 所有普通存储 没有子目录 我想循环浏览文件 但是当我使用DirectoryInfo GetFiles 在该文件夹上 整个应用程序冻结了几分钟 可以用另一种方式完成吗 也许读取
  • 如何将字符串转换为 Indian Money 格式?

    我正在尝试将字符串转换为印度货币格式 例如如果输入为 1234567 则输出应为 12 34 567 我编写了以下代码 但它没有给出预期的输出 CultureInfo hindi new CultureInfo hi IN string t
  • 无法通过 LINQ to Entities 使用某些功能?

    我正在尝试使用 LINQ 查询在项目上实现搜索功能 由于数据有时包含带有重音符号和其他符号的字符 因此我创建了一种方法来删除这些字符以进行搜索 这是我的代码 var addresses from a in db Addresses join
  • 有没有更好的方法来获取每个项目与谓词匹配的子序列?

    假设我有一个 IEnumerable 例如 2 1 42 0 9 6 5 3 8 我需要获得与谓词匹配的项目的 运行 例如 如果我的谓词是 bool isSmallerThanSix int number 我想得到以下输出 2 1 0 5
  • Dynamics Crm:获取状态代码/状态代码映射的元数据

    在 Dynamics CRM 2011 中 在事件实体上 状态原因 选项集 也称为状态代码 与 状态 选项集 也称为状态代码 相关 例如看这个截图 当我使用 API 检索状态原因选项集时 如下所示 RetrieveAttributeRequ
  • 你能解释一下这个C++删除问题吗?

    我有以下代码 std string F WideString ws GetMyWideString std string ret StringUtils ConvertWideStringToUTF8 ws ret return ret W
  • 为什么C语言中可以使用多个分号?

    在 C 中我可以执行以下操作 int main printf HELLO WORLD 它有效 这是为什么 我个人的想法 分号是一个 NO OPERATION 来自维基百科 指示符 拥有一大串分号与拥有一个分号并告诉 C 语句已结束具有相同的
  • 正在获取“未终止 [] 设置”。 C# 中的错误

    我正在 C 中使用以下正则表达式 Regex find new Regex url
  • 如何使用 ASP.NET Web 表单从代码隐藏中访问更新面板内的文本框、标签

    我在更新面板中定义了一些控件 它们绑定到中继器控件 我需要根据匿名字段隐藏和显示用户名和国家 地区 但问题是我无法以编程方式访问更新面板中定义的控件 我如何访问这些控件 我也在网上查找但找不到很多参考资料 下面是来自aspx页面和 cs页面
  • c# 替代方案中 cfusion_encrypt 中填充的密钥是什么?

    我找到了从这里复制 C 中的 cfusion encrypt 函数的答案 ColdFusion cfusion encrypt 和 cfusion decrypt C 替代方案 https stackoverflow com questio

随机推荐

  • 在拖动发生时更改 android 中的dragshadow

    面临让dragshaddow 由创建的拖动阴影生成器 在拖动时对某些东西做出反应 有人知道应该如何做吗 这是我的自定义拖动阴影生成器的完整代码 自定义拖动阴影的要点 然而 正如其他人所说 不可能使用 API 11 中引入的本机功能来修改拖动
  • 使用RAWINPUT区分左右Shift键

    RAWINPUT 提供两个标志 RI KEY E0 and RI KEY E1 来检查是否按下了左键或右键 这对于 CTRL 非常有用 但对于左移和右移则不适用 事实上 两者的标志是相同的 VKey 也相同 VK SHIFT 我怎样才能知道
  • 在 MVC 中显示标准数据表

    也许这是完全错误的 但在 Webform 时代 您将返回一个数据集 然后将其绑定到网格 但现在在 MVC 中 您不应该传递数据表 因为您无法序列化它 并且从技术上讲 它是将对象传递到不属于它的视图中 但是我到底要如何在视图上显示数据呢 我无
  • 像堆栈溢出一样获取“相关标签”的查询是什么

    我有 3 张桌子 links id linkName tags id tagName tagsBridge tagID linkID 我正在尝试支持显示相关标签 例如 SOF 中 因此 如果您单击标签 XYZ 现在我将显示带有标签 XYZ
  • 2个系列/df.columns之间的模糊查找

    基于此链接我试图进行模糊查找 在数据框列中应用模糊匹配并将结果保存在新列中2 个 dfs 之间 import pandas as pd df1 pd DataFrame data Brand var Johnny Walker Guines
  • 在bigquery中,您可以在使用format_date时指定语言 - 日期函数中的本地化

    我找不到格式化日期的方法GCP bigquery使用特定语言 select CONCAT FORMAT DATE Semaine du d B au date trunc current date ISOWEEK FORMAT DATE d
  • 使用 PHP -> ODBC -> MS SQL 插入 Unicode 字符?

    我有以下代码 sql update tbl test set category N resum echo sql rs odbc exec conn sql 其中 conn 是到 MSSQL Server 的 DSN ODBC 连接 问题似
  • cloudstack启动主备存储失败

    我使用2台主机建立我的cloudstack集群 我的所有主机都是使用NFSv3的Ubuntu 12 04 我使用host1作为主存储服务器和辅助存储服务器 管理服务器也在host1中 我可以在host2上挂载host1的主存储和辅助存储 我
  • scanf 格式中的空白字符问题

    我使用 scanf 读取输入stdin因为 scanf 被认为比cin 我发现以下意外行为 for int i 0 i lt 3 i scanf d t printf The input was d n t The d 格式为scanf预计
  • 如何取消winform按钮点击事件?

    我有一个继承自 System Windows Forms Button 的自定义按钮类 我想在我的 winform 项目中使用这个按钮 该类称为 确认按钮 它显示带有 是 或 否 的确认消息 但问题是 当用户选择 否 并带有确认消息时 我不
  • 使用带有属性占位符值的 @Profile 注释

    当我们在 spring 中为任何组件定义 profile 时 我们将其声明为 Profile value Prod 但我想从属性文件中给出该值 是否可以 如果是 怎么办 通过查看Spring的源代码 我得出的结论是 你所要求的是不可能的 为
  • 如何使用ctypes的errcheck?

    The Python 库参考 版本 3 6 5 第 16 16 段 ctypes Python 的外部函数库 给出这个例子 证明输出参数 在部分函数原型 赢32获取窗口矩形功能 WINUSERAPI BOOL WINAPI GetWindo
  • Three.js:纹理全白

    编辑 以下gaitat的修复建议 我收到了一个新错误 现在该框根本不显示 我写过一个新问题来演示此错误 我有一个简单的盒子几何形状 我试图用纹理来装饰它 然而 我得到的只是一个 100 的白盒子 我写过一个简单的测试站点来论证这个问题 这是
  • 鼠标移开时隐藏 div

    我有两个 div 一个用于简短摘要 一个用于长摘要 当我将鼠标悬停在简短摘要上时 简短摘要消失并出现长摘要 当我从长摘要中 鼠标移开 时 它应该消失 而简短摘要应该出现 问题是 当我仍在长摘要的边界内但不在排序摘要的位置时 会发生 mous
  • 生成可种子数据的随机字符串

    我正在寻找一种生成随机字符串的方法nPython 中的字节类似于os urandom 方法 除了提供一种数据生成种子的方法 到目前为止我有 def genRandData size buf chr random randint 0 255
  • 该进程无法访问该文件,因为该文件正在被另一个进程使用

    我有来自特定目录的 tif 文件 我有一个函数可以读取所有 tif 文件并将其转换为文本 转换后 一旦成功转换 我将其移动到名为 Completed 的文件夹 然后将其移动失败文件夹一旦转换失败 问题是当我使用 System IO File
  • 在 C++ 中如何实现从 int 到 object 的赋值?

    class phone public phone int x num x int number void return num void number int x num x private int num int main void ph
  • 找到矩阵中的主对角线 - 方案

    我需要从方阵中提取主对角线 1 2 3 4 5 6 gt 1 5 9 7 8 9 我有以下代码 我需要替换 具有适当的功能 define diag m if null m cons m diag map m Input diag 1 2 3
  • 我如何使用 python 从键盘发送命令。我正在尝试自动化 mac 应用程序 (GUI)

    我正在尝试使用 python 自动化应用程序 我需要帮助通过 python 发送键盘命令 我正在使用 powerBook G4 您可以使用 osascript 工具从 python 脚本调用 AppleScript import os cm
  • 如何查找 PDF 中所有出现的特定文本并在上方插入分页符?

    我对 PDF 有一个棘手的要求 我需要在 pdf 中搜索特定字符串 属性编号 每次找到这个 我都需要在上面添加一个分页符 我可以访问 IText 和 Spire PDF 我首先查看 IText 我从这里的其他帖子中确定我需要使用 PDF S