PDF文本指令解析与文本水印去除

上次我在《PDF批量加水印 与 去除水印实践》一文中完成了对图片水印和文字水印的去除。

链接:https://xxmdmst.blog.csdn.net/article/details/139483535

但是对于页面对象的内容对象是单层,不是数组的情况,无法去除水印。今天我们专门研究PDF的文本绘制指令,并尝试去除这种水印。

PDF文本显示操作符

文本显示操作符有TJTj两种,还有单引号和双引号两种。引号类的指令表示移动到下一行并显示文本,对于水印文本不可能使用这类指令。所以今天我们仅研究TJTj两种指令。

TJ指令(或称操作符)用于显示一个数组中的文本字符串,每个字符串可能有插值调整。

例如:

[ (\\0319\\047) -3 <00180102> 14 (\\001\\232) 17 (\\001\\002\\001\\017) 4 (\\001\\002\\001\\220) 6 (\\001\\036) 9 <037f> ] TJ
  • [] 包围的区域表示一个数组。
  • 数组中的元素可以是文本字符串或者数字,其中数字表示字符间距调整,单位是千分之一的字体单位。
  • 括号 () 和括号<>包围的内容表示字符串。

括号 () 内的字符串,反斜杠 \表示后面三位数是8进制字符,\\0319\\047可以理解为\\031,9,\\047三部分组成。

括号<>内的内容是用十六进制表示的字符串。

Tj指令则表示单个文本,对于TJ指令数组中其中一个文本元素。

PDF文本指令的解析

首先我们打印一下指令的完整内容:

import PyPDF2reader = PyPDF2.PdfReader(r"mysql【带水印】.pdf")
page = reader.pages[0]
page_content = page.get_contents()
page_data = page_content.get_data()
for line in page_data.splitlines():i = line.rfind(b" ")operator = line[i+1:]operand = line[:i]if operator in (b'TJ', b'Tj'):print(line)

截取一部分指令展示一下:

b'[ (\\033\\240\\031\\236\\024\xc3) 11 <2cb40a16> 11 (\\017F\\0040) 11 (\\057\xfd\\011j) 11 (\\0106\\033\xe9) 11 (\\025\\077\\002\xd6) ] TJ'
b'[ (\\016\\052) 11 (\\004\xbe\\021\\210) 11 (\\006\xd8\\004\xfb) 11 (CX) ] TJ'
b'[ (\\021\\210\\006\xd8\\004\xfb) 11 (CX\\0106) 11 (\\004j\\004T) 11 (\\057\xfd\\002\xd6) 11 (\\056\xf1\\055\\010) 11 (\\012\xbc\\007\xb5\\021\\210) ] TJ'
b'[ (\\007\\2433\\053) ] TJ'
b'[ (\\015\\273) 11 (\\033\\240\\031\\236) 11 (\\024\xc3) ] TJ'
b'[ (M\\216\\007\\2433\\053) 11 (\\015\\273\\033\\240) 11 (\\031\\236\\024\xc3) ] TJ'
b'[ (\\002\xd6\\021\\210) 11 (\\006\xd8\\015X) 11 (\\007\xb5\\021\\210\\004\\135) ] TJ'
b'(\\200\\200\\201\\202CSDN\\203https\\072\\057\\057blog\\056csdn\\056net\\057as604049322) Tj'

解析TJTj指令,我的方法如下:

def parse_operand(operand):data = b""for part in re.findall(b"\(.+?\)|<.+?>", operand):s = part[1:-1]if chr(part[0]) == "(":data += re.sub(rb'\\([0-7]{3})',lambda m: chr(int(m.group(1), 8)).encode("charmap"), s)elif chr(part[0]) == "<":data += bytes.fromhex(s.decode())return data

尝试解析上面展示的最后一个指令:

data = parse_operand(operand)
print(data)

结果:

b'\x80\x80\x81\x82CSDN\x83https://blog.csdn.net/as604049322'

这样我们就解析出来原始的字节,要解析出原始的文本还需要解析Tf指令,使用对应的charmap编码表进行二次转换。

编码表构建

首先我们得到所有的编码表:

from PyPDF2._cmap import build_char_mapcmaps = {}
for f in page['/Resources']['/Font']:cmaps[f] = build_char_map(f, 200.0, page)

这里build_char_map如何实现,本文不作深究。

然后需要解析Tf指令,构建当前文本所使用的char_map,解析函数为:

if operator == b'Tf':cmap_name = operand.split()[0].decode()charMapTuple = cmaps[cmap_name]cmap = (charMapTuple[2], charMapTuple[3],cmap_name, charMapTuple[4])

然后就可以使用下面的函数,对文本指令的内容进行解析获取文本:

def pdf_decode_text(tt):encoding = cmap[0]if isinstance(encoding, str):try:t = tt.decode(encoding, "surrogatepass")except Exception:fallback_encoding = "utf-16-be" if encoding == "charmap" else "charmap"t = tt.decode(fallback_encoding, "surrogatepass")else:t = "".join(encoding.get(x, chr(x)) for x in tt)return "".join(cmap[1].get(x, x) for x in t)

完整的文本解析代码如下:

import PyPDF2
from PyPDF2._cmap import build_char_map
import redef parse_operand(operand):data = b""for part in re.findall(b"\(.+?\)|<.+?>", operand):s = part[1:-1]if chr(part[0]) == "(":data += re.sub(rb'\\([0-7]{3})',lambda m: chr(int(m.group(1), 8)).encode("charmap"), s)elif chr(part[0]) == "<":data += bytes.fromhex(s.decode())return datadef pdf_decode_text(tt):encoding = cmap[0]if isinstance(encoding, str):try:t = tt.decode(encoding, "surrogatepass")except Exception:fallback_encoding = "utf-16-be" if encoding == "charmap" else "charmap"t = tt.decode(fallback_encoding, "surrogatepass")else:t = "".join(encoding.get(x, chr(x)) for x in tt)return "".join(cmap[1].get(x, x) for x in t)reader = PyPDF2.PdfReader(r"mysql【带水印】.pdf")
page = reader.pages[0]
cmaps = {}
for f in page['/Resources']['/Font']:cmaps[f] = build_char_map(f, 200.0, page)
page_content = page.get_contents()
page_data = page_content.get_data()
for line in page_data.splitlines():i = line.rfind(b" ")operator = line[i+1:]operand = line[:i]if operator == b'Tf':cmap_name = operand.split()[0].decode()charMapTuple = cmaps[cmap_name]cmap = (charMapTuple[2], charMapTuple[3],cmap_name, charMapTuple[4])elif operator in (b'TJ', b'Tj'):data = parse_operand(operand)text = pdf_decode_text(data)print(operand, text)

image-20240830175601761

可以看到每条文本指令都已完美的解析出原始的文本内容。

去除文本水印实践

我的思路是寻找前10页,非空白文本出现次数最多的对应的文本指令,然后删除这些文本指令即可。

寻找前10页出现次数最多文本指令:

import PyPDF2
from collections import Counter
from PyPDF2._cmap import build_char_map
import redef parse_operand(operand):data = b""for part in re.findall(b"\(.+?\)|<.+?>", operand):s = part[1:-1]if chr(part[0]) == "(":data += re.sub(rb'\\([0-7]{3})',lambda m: chr(int(m.group(1), 8)).encode("charmap"), s)elif chr(part[0]) == "<":data += bytes.fromhex(s.decode())return datadef pdf_decode_text(tt):encoding = cmap[0]if isinstance(encoding, str):try:t = tt.decode(encoding, "surrogatepass")except Exception:fallback_encoding = "utf-16-be" if encoding == "charmap" else "charmap"t = tt.decode(fallback_encoding, "surrogatepass")else:t = "".join(encoding.get(x, chr(x)) for x in tt)return "".join(cmap[1].get(x, x) for x in t)reader = PyPDF2.PdfReader(r"mysql【带水印】.pdf")
counter = Counter()
for page in reader.pages[:10]:cmaps = {}for f in page['/Resources']['/Font']:cmaps[f] = build_char_map(f, 200.0, page)page_content = page.get_contents()page_data = page_content.get_data()for line in page_data.splitlines():i = line.rfind(b" ")operator = line[i+1:]operand = line[:i]if operator == b'Tf':cmap_name = operand.split()[0].decode()charMapTuple = cmaps[cmap_name]cmap = (charMapTuple[2], charMapTuple[3],cmap_name, charMapTuple[4])elif operator in (b'TJ', b'Tj'):data = parse_operand(operand)text = pdf_decode_text(data)if text.strip():counter[(text, line)] += 1
watermark_command = counter.most_common(1)[0][0][1]
watermark_command
b'(\\200\\200\\201\\202CSDN\\203https\\072\\057\\057blog\\056csdn\\056net\\057as604049322) Tj'

然后我们批量删除所有页的这行指令,并保存:

writer = PyPDF2.PdfWriter()
for page in reader.pages:page_content = page.get_contents()page_data = page_content.get_data()page_data_without_logo = page_data.replace(watermark_command+b"\n", b"")if page_content.decoded_self is not None:page_content.decoded_self.set_data(page_data_without_logo)else:page_content.set_data(page_data_without_logo)page[PyPDF2.generic.NameObject("/Contents")] = page_contentpage.compress_content_streams()writer.add_page(page)
output_path = "mysql【去水印】.pdf"
with open(output_path, "wb") as output_file:writer.write(output_file)

最终已经成功的完成了对这类文本水印的去除:

image-20240830180101016

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/412891.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

idea付费插件,SequenceDiagram有哪些优点

以下idea付费插件你们都用过哪些呢&#xff1f; SequenceDiagram插件是一种用于绘制时序图的工具。时序图是一种图形化的表示对象之间消息传递顺序的方法。 该插件可以在使用各种编程语言编写代码时&#xff0c;方便地绘制时序图&#xff0c;以帮助开发者更好地理解和描述系统…

<Rust>egui学习之小部件(七):如何在窗口中添加颜色选择器colorpicker部件?

前言 本专栏是关于Rust的GUI库egui的部件讲解及应用实例分析&#xff0c;主要讲解egui的源代码、部件属性、如何应用。 环境配置 系统&#xff1a;windows 平台&#xff1a;visual studio code 语言&#xff1a;rust 库&#xff1a;egui、eframe 概述 本文是本专栏的第七篇博…

IBM是中国IT界的黄埔军校

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 我第一次听说IBM还是小的时候&#xff0c;当时很多人都说IBM是厉害&#xff0c;外号“蓝色巨人”&#xff0c;潜移默化我也知道IBM牛了。 而且当年我买的第一款笔记本电脑就是IBM的ThinkPad系列&#xff0c;花了6…

[Labview]图片叠加下的表格视图拖拽功能:挖坑粗糙版

没错&#xff0c;又是Labview表格T - T 由于项目中用到的表格上有一张用于画框的二维图片&#xff0c;感兴趣可看这篇 [Labview] 表格单元格外边框 二维图片叠加绘图 因此在滚动条与鼠标滚轮的基础上&#xff0c;想再增加一个拖拽移动的功能。 但 [二维图片] 并没有 拖拽开始…

科技与文化的完美碰撞 德施曼玄武•紫禁城K80亮相成都车展

8月30日-9月8日&#xff0c;第二十七届成都国际汽车展览会将在中国西部国际博览城举行&#xff0c;德施曼将与海尔、美的、松下等知名品牌携旗下重磅产品集中参展。大会同期“京东MALL智享家生活”活动也将开启&#xff0c;行业首个与紫禁城IP联名的智能锁产品德施曼玄武•紫禁…

江协科技stm32————10-5 硬件I2C读写MPU6050

步骤 一、配置I2C外设&#xff0c;对I2C2外设进行初始化&#xff08;MyI2C_Init&#xff09; 开启I2C外设和对应的GPIO口的时钟把I2C对应的GPIO口初始化为复用开漏模式使用结构体配置I2CI2C_Cmd,使能I2C I2C_GenerateSTART //生产起始条件 I2C_GenerateSTOP /…

LavaDome:一款基于ShadowDOM的DOM树安全隔离与封装工具

关于LavaDome LavaDome是一款针对HTML代码安全和Web安全的强大工具&#xff0c;该工具基于ShadowDOM实现其功能&#xff0c;可以帮助广大研究人员实现安全的DOM节点/树隔离和封装。 在当今的Web标准下&#xff0c;尚无既定方法可以安全地选择性地隔离DOM子树。换句话说&#x…

Unity获取SceneView尺寸

获取SceneView尺寸 var sceneView SceneView.lastActiveSceneView; var size new Vector2(sceneView.position.width,sceneView.position.height);

设计模式结构型模式之代理模式

结构型模式之代理模式 一、概念和使用场景1、概念2、核心思想3、java实现代理模式的方式4、使用场景 二、示例讲解1. 静态代理2. 动态代理 三、总结1、使用规则2、代理模式的优点包括&#xff1a;3、代理模式的缺点包括&#xff1a; 一、概念和使用场景 1、概念 代理模式是一…

笔记:应用Visual Studio Profiler识别和解决内存泄漏问题

一、目的&#xff1a;应用Visual Studio Profiler识别和解决内存泄漏问题 识别和解决内存泄漏问题是确保应用程序稳定性和性能的关键步骤。 二、实现 以下是如何使用 Visual Studio Profiler 识别和解决内存泄漏问题的详细步骤&#xff1a; 1. 启动内存分析 1. 打开项目&…

Windows环境CP Editor安装使用方法

step0&#xff1a;下载软件包&#xff0c;附件有已经下载的安装包 Download CP Editor | CP Editor step1&#xff1a;下载后双击安装即可(记住安装路径) step2&#xff1a;找到安装目录下的cpeditor\mingw64\bin配置环境变量 step3&#xff1a;双击打开即可使用

《王者荣耀》游戏玩法与部分机制分析

目录 游戏机制 MOBA核心玩法 匹配机制 游戏模式 隐藏分机制 游戏规则 总结 王者荣耀的ELO匹配机制是如何具体工作的&#xff1f; 王者荣耀中隐藏分机制的详细规则是什么&#xff1f;&#xff08;难绷&#xff01;&#xff09; 王者荣耀边境突围和五军对决模式的具体玩…

全局页面数据渲染--SAAS本地化及未来之窗行业应用跨平台架构

一、代码 /* 未来之窗通用数据渲染// 定义了一个名为"未来之窗_人工智能_前端口_数据渲染到界面"的函数 function 未来之窗_人工智能_前端口_数据渲染到界面(obj, 前置参数) {// 开启一个控制台分组&#xff0c;用于组织相关的输出信息console.group("未来之窗…

MySQL集群的基础部署及主从复制详解

一、Msql在服务器中的部署方法 官网&#xff1a;http://www.mysql.com 在企业中90%的服务器操作系统均为Linux 在企业中对于Mysql的安装通常用源码编译的方式来进行 1.1 在Linux下部署MySQL 1.1.1 部署环境 主机IP角色MySQL-node1172.25.254.13masterMySQL-node2172.25.…

Linux--IO模型_多路转接

目录 0.往期文章 1.五种IO模型介绍 概念 调用函数&#xff08;非阻塞IO&#xff09; 2.详解多路转接 之select select函数介绍 设置文件描述符 写一个基于select的TCP服务器 辅助库 基于TCP的Socket封装 服务器代码 测试服务器 小结 3.详解多路转接 之poll poll函…

基于OpenCV+MFC的KCF测速软件

基于OpenCVMFC的KCF测速软件 引言原理介绍使用介绍&#xff08;1&#xff09;主界面&#xff08;2&#xff09;打开视频&#xff08;3&#xff09;点击KCF测速&#xff08;4&#xff09;框选待检测目标&#xff08;5&#xff09;测速结果 资源链接&#xff08;包含源码&#xf…

SpringMVC处理流程介绍

SpringMVC请求处理流程 发起请求到前端控制器(DispatcherServlet)前端控制器请求HandlerMapping查找Handler(可以根据xml配置,注解进行查找) 对应Spring源码 //在类DispatcherServlet里面 protected void doDispatch(HttpServletRequest request, HttpServletResponse respon…

static关键字与单例模式

可以修饰属性变量&#xff0c;方法和代码段 static修饰的属性称为静态属性或类属性&#xff0c; 在类加载时就在方法区为属性开辟存储空间&#xff0c;无论创建多少个对象&#xff0c;静态属性在内存中只有一份。 可以使用 类名.静态属性 的方式引用 static修饰的方法称为静态…

FPGA第 5 篇,FPGA技术优略势,FPGA学习方向,FPGA学习路线(FPGA专业知识的学习方向,FPGA现场可编程门阵列学习路线和方向)

前言 前几篇讲了一下FPGA的发展和应用&#xff0c;以及未来前景。具体详细&#xff0c;请看 FPGA发展和应用&#xff0c;以及未来前景https://blog.csdn.net/weixin_65793170/category_12665249.html 这里我们来&#xff0c;记录一下&#xff0c;FPGA专业知识的学习路线 一.…

第22周:调用Gensim库训练Word2Vec模型

目录 前言 一、Word2vec基本知识 1.1 Word2Vec是什么 1.2 Word2Vec两种主要模型架构 1.2.1 CBOW模型 1.2.2 Skip-gram模型 1.3 实例说明 1.4 调用方法 二、准备工作 2.1 安装Gensim库 2.2 对原始语料分词 2.2 添加自定义停用词 三、训练Word2Vec模型 四、模型应用…