PDF电子签名A4实现

  • 一、前言
  • 二、工程依赖(maven工程)
  • 三、工程代码示例(java)
  • 四、测试结果日志和签名截图
  • 五、其他说明

一、前言

  • 需要进行电子签名的html文件内容,一般为富文本编辑器对应代码。
  • 用户电子签名完成后的签名图片访问地址,例如:http://*****/hlwft/2_1659677726496.jpg。
  • 当前仅实现了PageSize.A4格式的底部签名和左/中/右对齐。
  • 签名图片访问支持http/https两种形式,http信任所有的证书与主机的客户端。

二、工程依赖(maven工程)

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.3.12.RELEASE</version>
</dependency>
<!-- Lombok -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope>
</dependency>
<!--pdf文件生成-->
<dependency><groupId>com.itextpdf</groupId><artifactId>html2pdf</artifactId><version>4.0.4</version>
</dependency>
<dependency><groupId>com.itextpdf</groupId><artifactId>font-asian</artifactId><version>7.2.4</version>
</dependency>
<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.4</version>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version>
</dependency>
<!--http工具-->
<dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.9.3</version>
</dependency>
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpcore</artifactId><version>4.4.16</version>
</dependency>

三、工程代码示例(java)

  • 图片配置类
public final class ImgLayout {private ImgLayout() {}/*** 图片的宽度* {@value}*/public static final int IMAGE_WIDTH = 40;/*** 图片的高度* {@value}*/public static final int IMAGE_HEIGHT = 40;/*** 边界间距*/public static final int BORDER_INTERVAL = 8;
}
  • 签名配置类
import com.itextpdf.layout.properties.TextAlignment;public final class SignStyle {private SignStyle() {}/*** 是否每页签名:默认最后一页签名* <ul>* <li>{@code Boolean.FALSE}:最后一页签名</li>* <li>{@code Boolean.TRUE}:每一页都生成签名</li>* </ul>*/public static final boolean SIGN_EACH_PAGE = Boolean.FALSE;/*** 对齐方式:默认居中对齐* <strong>* 仅支持三种类型{@link TextAlignment.LEFT}/{@link TextAlignment.CENTER}/{@link TextAlignment.RIGHT}* </strong>*/public static final TextAlignment SIGN_ALIGN = TextAlignment.CENTER;}
  • pdf文件样式配置类
public final class PdfStyle {private PdfStyle() {}/*** PDF页码字号* {@value}*/public static final int PAGE_FONT_SIZE = 14;/*** 字体文件目录* <h4>windows/linux系统字体文件目录一般如下</h4>* ul>*   <li>windows字体文件目录:C:/Windows/Fonts</li>*   <li>linux字体文件目录:/usr/share/fonts</li>* </ul>*/public static final String FONT_PATH = "C:/Windows/Fonts";}
  • UnsafeOkHttp工具类
import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.io.util.StreamUtil;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.http.util.TextUtils;
import org.springframework.util.CollectionUtils;import javax.net.ssl.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;public class UnsafeOkHttp {/*** 信任所有的证书与主机的客户端* {@value}*/public static OkHttpClient UNSAFE_OKHTTP_CLIENT;static {try {final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {@Overridepublic void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {}@Overridepublic void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {}@Overridepublic java.security.cert.X509Certificate[] getAcceptedIssuers() {return new java.security.cert.X509Certificate[]{};}}};SSLContext sslContext = SSLContext.getInstance("TLS");sslContext.init(null, trustAllCerts, new java.security.SecureRandom());SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();OkHttpClient.Builder builder = new OkHttpClient.Builder();builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);builder.hostnameVerifier(new HostnameVerifier() {// 可以将不需要忽略的域名放入数组,也可为空(忽略所有证书)String[] arr = new String[]{};@Overridepublic boolean verify(String hostname, SSLSession session) {if (TextUtils.isEmpty(hostname)) {return false;}return !Arrays.asList(arr).contains(hostname);}});UNSAFE_OKHTTP_CLIENT = builder.build();} catch (Exception e) {throw new RuntimeException(e);}}/*** 发起HTTPGET请求** @param url* @param headerMap* @return* @throws Exception*/public static Response unsafeGet(String url, Map<String, String> headerMap) throws IOException {Request.Builder builder = new Request.Builder().url(url);if (!CollectionUtils.isEmpty(headerMap)) {headerMap.forEach((name, value) -> builder.addHeader(name, value));}Request request = builder.build();return UNSAFE_OKHTTP_CLIENT.newCall(request).execute();}/*** 发起HTTPGET请求,并将返回的内容写入到文件中** @param url* @param headerMap* @param target* @return* @throws Exception*/public static void unsafeGetToFile(String url, Map<String, String> headerMap, File target) throws IOException {Request.Builder builder = new Request.Builder().url(url);if (!CollectionUtils.isEmpty(headerMap)) {headerMap.forEach((name, value) -> builder.addHeader(name, value));}Response response = UNSAFE_OKHTTP_CLIENT.newCall(builder.build()).execute();try (FileOutputStream fileOutputStream = new FileOutputStream(target)) {StreamUtil.transferBytes(response.body().byteStream(), fileOutputStream);} catch (Exception e) {e.printStackTrace();}}/*** 发起HTTPGET请求,并将返回临时文件** @param url* @param headerMap* @return 返回临时文件,使用后需手动删除* @throws Exception*/public static File unsafeGetToFile(String url, Map<String, String> headerMap) throws IOException {Request.Builder builder = new Request.Builder().url(url);if (!CollectionUtils.isEmpty(headerMap)) {headerMap.forEach((name, value) -> builder.addHeader(name, value));}ResponseBody body = UNSAFE_OKHTTP_CLIENT.newCall(builder.build()).execute().body();String fileSuffix = body.contentType().subtype();File target = FileUtil.createTempFile(String.join(".", "target", fileSuffix));try (FileOutputStream fileOutputStream = new FileOutputStream(target)) {StreamUtil.transferBytes(body.byteStream(), fileOutputStream);} catch (Exception e) {e.printStackTrace();}return target;}}
  • pdf签名工具类
import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.io.image.ImageType;
import com.itextpdf.io.util.StreamUtil;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.font.FontProvider;
import com.itextpdf.layout.properties.TextAlignment;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;import java.io.*;
import java.net.URL;
import java.util.List;import static com.example.demo.pdf.ImgLayout.*;
import static com.example.demo.pdf.PdfStyle.PAGE_FONT_SIZE;
import static com.example.demo.pdf.SignStyle.SIGN_EACH_PAGE;/*** pdf签名工具* <h3>注意事项</h3>* <ul>* <li>签名仅支持底部签名</li>* <li>签名对齐仅支持三种类型{@link TextAlignment.LEFT}/{@link TextAlignment.CENTER}/{@link TextAlignment.RIGHT}</li>* <li>仅支持{@code PageSize.A4}格式</li>* <li>签名图片参照配置类{@linkplain ImgLayout},pdf文件参照配置类{@linkplain PdfStyle},签名参照配置类{@linkplain SignStyle}</li>* <li>签名图片访问支持http/https两种形式,http信任所有的证书与主机的客户端,如果需要配置证书需要重新实现{@linkplain UnsafeOkHttp}</li>* </ul>**/
public final class PdfUtil {private PdfUtil() {}/*** 将html写入到文件中** @param html* @param file*/public static final void html2Pdf(String html, File file) {//1.配置html转换字体参数ConverterProperties properties = new ConverterProperties();FontProvider fontProvider = new FontProvider();fontProvider.addDirectory(PdfStyle.FONT_PATH);properties.setFontProvider(fontProvider);//2.转换html2pdftry (FileOutputStream fileOutputStream = new FileOutputStream(file)) {HtmlConverter.convertToPdf(html, fileOutputStream, properties);} catch (Exception e) {e.printStackTrace();}}/*** pdf图片签名** @param source* @param images* @param signPdfName* @return {@literal 签名整合后的pdf文件临时文件}* @throws IOException*/public static final File sign(File source, List<Image> images, String signPdfName) throws IOException {File signPdf = FileUtil.createTempFile(signPdfName);try (FileOutputStream signStream = new FileOutputStream(signPdf);FileInputStream sourceStream = new FileInputStream(source)) {PdfDocument signDocument = new PdfDocument(new PdfWriter(signStream));PdfReader sourceReader = new PdfReader(sourceStream);PdfDocument sourceDocument = new PdfDocument(sourceReader);signDocument.addEventHandler(PdfDocumentEvent.END_PAGE,new PdfPageTailMarker());signDocument.addEventHandler(PdfDocumentEvent.INSERT_PAGE,new PdfSignImgMarker(images,sourceDocument.getNumberOfPages()));for (int i = 1, numberOfPage = sourceDocument.getNumberOfPages(); i <= numberOfPage; i++) {PdfPage sourcePage = sourceDocument.getPage(i);//复制每页内容添加到新的文件中signDocument.addPage(sourcePage.copyTo(signDocument));}signDocument.close();sourceDocument.close();sourceReader.close();} catch (Exception e) {e.printStackTrace();}return signPdf;}/*** 根据图片访问路径创建PDF签名图片对象* <ul>*     <li>itext支持的标准图片文件格式参见{@linkplain ImageType}</li>*     <li>本方法单独针对.jpg格式图片按.jpeg格式处理</li>* </ul>** @param imgUrl* @return* @throws IOException*/public static Image createImage(String imgUrl) throws IOException {if (StringUtils.containsIgnoreCase(imgUrl, "https")) {//1.如果是https请求,则获取文件内容后返回Response response = UnsafeOkHttp.unsafeGet(imgUrl, null);ResponseBody body = response.body();//1.1 如果因为协议问题,响应体格式未自动转换为jpeg,则开启如下代码单独处理
//            if (StringUtils.equalsIgnoreCase(body.contentType().subtype(), "jpg")) {
//                ImageData jpgImage = new JpgImageData(body.bytes());
//                JpgImageHandler.processImage(jpgImage);
//                return new Image(jpgImage);
//            }return new Image(ImageDataFactory.create(body.bytes()));} else {//2.如果是http请求直接返回return new Image(ImageDataFactory.create(new URL(imgUrl)));}}/*** 将源数据转换为分片文件** @param source* @return*/public static final MultipartFile transformFile2MultipartFile(File source) {FileItemFactory factory = new DiskFileItemFactory(16, null);FileItem item = factory.createItem("textField", "text/plain", true, source.getName());try (FileInputStream sourceStream = new FileInputStream(source);OutputStream targetStream = item.getOutputStream()) {StreamUtil.transferBytes(sourceStream, targetStream);} catch (Exception e) {e.printStackTrace();}return new CommonsMultipartFile(item);}/*** pdf页脚处理。pdf配置参照{@linkplain PdfStyle}*/private static class PdfPageTailMarker implements IEventHandler {@Overridepublic void handleEvent(Event event) {PdfDocumentEvent docEvent = (PdfDocumentEvent) event;PdfDocument pdf = docEvent.getDocument();PdfPage page = docEvent.getPage();Rectangle pageSize = page.getPageSize();Canvas canvas = new Canvas(new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf),pageSize);Paragraph p = new Paragraph(String.valueOf(pdf.getPageNumber(page))).setFontSize(PAGE_FONT_SIZE);// 默认底部中间位置canvas.showTextAligned(p,(pageSize.getLeft() + pageSize.getRight()) / 2,pageSize.getBottom() + BORDER_INTERVAL,TextAlignment.CENTER);canvas.close();}}/*** pdf签名图片设置* <strong>* 当pdf文档签名过多时,可能存在页码遮挡、超出边界问题。签名配置参照{@linkplain SignStyle}* </strong>*/@AllArgsConstructorprivate static class PdfSignImgMarker implements IEventHandler {/*** 签名图片*/private List<Image> images;/*** 总页数*/private int pages;@Overridepublic void handleEvent(Event event) {PdfDocumentEvent docEvent = (PdfDocumentEvent) event;PdfDocument pdf = docEvent.getDocument();PdfPage page = docEvent.getPage();Rectangle pageSize = page.getPageSize();PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);Canvas canvas = new Canvas(pdfCanvas, pageSize);if (SIGN_EACH_PAGE) {this.addSignImg(canvas, pdf.getNumberOfPages(), pageSize.getBottom() + BORDER_INTERVAL);} else {if (pages == pdf.getNumberOfPages()) {this.addSignImg(canvas, pdf.getNumberOfPages(), pageSize.getBottom() + BORDER_INTERVAL);}}canvas.close();}/*** 添加居中对齐的签名图片* <strong>* 以页码为中心,先左后右交替向两边扩散。图片配置参数参照{@linkplain ImgLayout}。仅支持{@code PageSize.A4}对齐。* </strong>** @param canvas* @param numberOfPages* @param bottom*/private void addCenterSignImg(Canvas canvas, int numberOfPages, float bottom) {float center = PageSize.A4.getRight() / 2;for (int i = 0,leftStart = (int) (center - BORDER_INTERVAL),rightStart = (int) (center + BORDER_INTERVAL),imageSize = images.size(); i < imageSize; i++) {Image image = images.get(i);if (i % 2 == 0) {//1.偶数向左扩散image.setFixedPosition(numberOfPages, leftStart -= IMAGE_WIDTH, bottom);} else {//2.奇数向右扩散image.setFixedPosition(numberOfPages, rightStart, bottom);rightStart += IMAGE_WIDTH;}image.scaleAbsolute(IMAGE_WIDTH, IMAGE_HEIGHT);// 自定义大小canvas.add(images.get(i));}}/*** 添加左对齐的签名图片* <strong>* 除去默认边距后,从左向右排列签名。图片配置参数参照{@linkplain ImgLayout}。仅支持{@code PageSize.A4}对齐。* </strong>** @param canvas* @param numberOfPages* @param bottom*/private void addLeftSignImg(Canvas canvas, int numberOfPages, float bottom) {for (int i = 0, start = BORDER_INTERVAL, imageSize = images.size(); i < imageSize; i++) {Image image = images.get(i);image.setFixedPosition(numberOfPages, start, bottom);image.scaleAbsolute(IMAGE_WIDTH, IMAGE_HEIGHT);// 自定义大小canvas.add(images.get(i));start += IMAGE_WIDTH;}}/*** 添加右对齐的签名图片* <strong>* 除去默认边距后,从右向左排列签名。图片配置参数参照{@linkplain ImgLayout}。仅支持{@code PageSize.A4}对齐。* </strong>** @param canvas* @param numberOfPages* @param bottom*/private void addRightSignImg(Canvas canvas, int numberOfPages, float bottom) {float width = PageSize.A4.getRight();for (int i = 0, start = (int) (width - BORDER_INTERVAL), imageSize = images.size(); i < imageSize; i++) {Image image = images.get(i);image.setFixedPosition(numberOfPages, start -= IMAGE_WIDTH, bottom);image.scaleAbsolute(IMAGE_WIDTH, IMAGE_HEIGHT);// 自定义大小canvas.add(images.get(i));}}/*** 添加签名图片** @param canvas* @param numberOfPages* @param bottom* @throws IllegalArgumentException*/private void addSignImg(Canvas canvas, int numberOfPages, float bottom) {switch (SignStyle.SIGN_ALIGN) {case LEFT:addLeftSignImg(canvas, numberOfPages, bottom);break;case RIGHT:addRightSignImg(canvas, numberOfPages, bottom);break;case CENTER:addCenterSignImg(canvas, numberOfPages, bottom);break;default:throw new IllegalArgumentException("仅支持 居中、向左、向右对齐参数");}}}}
  • pdf签名测试类
import com.example.demo.Demo1Application;
import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.layout.element.Image;
import org.assertj.core.util.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.io.File;
import java.util.List;import static com.example.demo.pdf.PdfUtil.*;/*** pdf sign test*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Demo1Application.class)
public class PdfSignTest {/*** 测试源*/public static final String SOURCE_HTML = "<p style=\"text-align: center;\"><span style=\"color: black; font-size: 22px; font-family: 宋体;\"><strong>XXX法院</strong></span></p><p style=\"text-align: center;\"><span style=\"color: black; font-size: 22px; font-family: 宋体;\"><strong>法庭审理笔录</strong></span></p><p style=\"text-align: right;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">(2022)陕01100100号</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">案由:清算组成员责任纠纷</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">开庭时间:2023-02-02 23:00:00</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">开庭地点:远程调解</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">审判员:法官2</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">书记员:陈书记</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">记录如下:</span></p>";/*** 签名图片源*/public static final String SIGN_IMG_URL_HTTP = "http://10.10.20.172:9000/hlwft/2_1659677726496.jpg";public static final String SIGN_IMG_URL_HTTPS = "https://hlwft.tongtech.com:20011/m_file/互联网+诉讼_1671435392069.jpg";@Testpublic void testPdfSignHttp() {try {//1.转换html->pdfFile source = new File("test_source_http.pdf");if (!source.exists()){source = FileUtil.createTempFile("test_source_http.pdf");}html2Pdf(SOURCE_HTML, source);//2.pdf签名List<Image> images = Lists.newArrayList(createImage(SIGN_IMG_URL_HTTP));File signPdf = sign(source,images,"test_target_http.pdf");if (source.exists()) {System.out.println(source.getAbsolutePath());}if (signPdf.exists()) {System.out.println(signPdf.getAbsolutePath());}} catch (Exception e) {e.printStackTrace();}}@Testpublic void testPdfSignHttps() {try {//1.转换html->pdfFile source = new File("test_source_https.pdf");if (!source.exists()){source = FileUtil.createTempFile("test_source_https.pdf");}html2Pdf(SOURCE_HTML, source);//2.pdf签名List<Image> images = Lists.newArrayList(createImage(SIGN_IMG_URL_HTTPS));File signPdf = sign(source,images,"test_target_https.pdf");if (source.exists()) {System.out.println(source.getAbsolutePath());}if (signPdf.exists()) {System.out.println(signPdf.getAbsolutePath());}} catch (Exception e) {e.printStackTrace();}}}

四、测试结果日志和签名截图

  • 测试日志
D:\jdk8u281\bin\java.exe -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\IntelliJ IDEA 2020.3.2\lib\idea_rt.jar=61236:D:\IntelliJ IDEA 2020.3.2\bin" -Dfile.encoding=UTF-8 -classpath "D:\IntelliJ IDEA 2020.3.2\lib\idea_rt.jar;D:\IntelliJ IDEA 2020.3.2\plugins\junit\lib\junit5-rt.jar;D:\IntelliJ IDEA 2020.3.2\plugins\junit\lib\junit-rt.jar;D:\jdk8u281\jre\lib\charsets.jar;D:\jdk8u281\jre\lib\deploy.jar;D:\jdk8u281\jre\lib\ext\access-bridge-64.jar;D:\jdk8u281\jre\lib\ext\cldrdata.jar;D:\jdk8u281\jre\lib\ext\dnsns.jar;D:\jdk8u281\jre\lib\ext\jaccess.jar;D:\jdk8u281\jre\lib\ext\jfxrt.jar;D:\jdk8u281\jre\lib\ext\localedata.jar;D:\jdk8u281\jre\lib\ext\nashorn.jar;D:\jdk8u281\jre\lib\ext\sunec.jar;D:\jdk8u281\jre\lib\ext\sunjce_provider.jar;D:\jdk8u281\jre\lib\ext\sunmscapi.jar;D:\jdk8u281\jre\lib\ext\sunpkcs11.jar;D:\jdk8u281\jre\lib\ext\zipfs.jar;D:\jdk8u281\jre\lib\javaws.jar;D:\jdk8u281\jre\lib\jce.jar;D:\jdk8u281\jre\lib\jfr.jar;D:\jdk8u281\jre\lib\jfxswt.jar;D:\jdk8u281\jre\lib\jsse.jar;D:\jdk8u281\jre\lib\management-agent.jar;D:\jdk8u281\jre\lib\plugin.jar;D:\jdk8u281\jre\lib\resources.jar;D:\jdk8u281\jre\lib\rt.jar;E:\jeecg-code\demo1\target\test-classes;E:\jeecg-code\demo1\target\classes;D:\mvn_repository\org\springframework\boot\spring-boot-starter-web\2.3.12.RELEASE\spring-boot-starter-web-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-starter\2.3.12.RELEASE\spring-boot-starter-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot\2.3.12.RELEASE\spring-boot-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-autoconfigure\2.3.12.RELEASE\spring-boot-autoconfigure-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-starter-logging\2.3.12.RELEASE\spring-boot-starter-logging-2.3.12.RELEASE.jar;D:\mvn_repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\mvn_repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\mvn_repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;D:\mvn_repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;D:\mvn_repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;D:\mvn_repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\mvn_repository\org\yaml\snakeyaml\1.26\snakeyaml-1.26.jar;D:\mvn_repository\org\springframework\boot\spring-boot-starter-json\2.3.12.RELEASE\spring-boot-starter-json-2.3.12.RELEASE.jar;D:\mvn_repository\com\fasterxml\jackson\core\jackson-databind\2.11.4\jackson-databind-2.11.4.jar;D:\mvn_repository\com\fasterxml\jackson\core\jackson-annotations\2.11.4\jackson-annotations-2.11.4.jar;D:\mvn_repository\com\fasterxml\jackson\core\jackson-core\2.11.4\jackson-core-2.11.4.jar;D:\mvn_repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.11.4\jackson-datatype-jdk8-2.11.4.jar;D:\mvn_repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.11.4\jackson-datatype-jsr310-2.11.4.jar;D:\mvn_repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.11.4\jackson-module-parameter-names-2.11.4.jar;D:\mvn_repository\org\springframework\boot\spring-boot-starter-tomcat\2.3.12.RELEASE\spring-boot-starter-tomcat-2.3.12.RELEASE.jar;D:\mvn_repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.46\tomcat-embed-core-9.0.46.jar;D:\mvn_repository\org\glassfish\jakarta.el\3.0.3\jakarta.el-3.0.3.jar;D:\mvn_repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.46\tomcat-embed-websocket-9.0.46.jar;D:\mvn_repository\org\springframework\spring-web\5.2.15.RELEASE\spring-web-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-beans\5.2.15.RELEASE\spring-beans-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-webmvc\5.2.15.RELEASE\spring-webmvc-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-aop\5.2.15.RELEASE\spring-aop-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-context\5.2.15.RELEASE\spring-context-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-expression\5.2.15.RELEASE\spring-expression-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-starter-test\2.3.12.RELEASE\spring-boot-starter-test-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-test\2.3.12.RELEASE\spring-boot-test-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-test-autoconfigure\2.3.12.RELEASE\spring-boot-test-autoconfigure-2.3.12.RELEASE.jar;D:\mvn_repository\com\jayway\jsonpath\json-path\2.4.0\json-path-2.4.0.jar;D:\mvn_repository\net\minidev\json-smart\2.3.1\json-smart-2.3.1.jar;D:\mvn_repository\net\minidev\accessors-smart\2.3.1\accessors-smart-2.3.1.jar;D:\mvn_repository\org\ow2\asm\asm\5.0.4\asm-5.0.4.jar;D:\mvn_repository\jakarta\xml\bind\jakarta.xml.bind-api\2.3.3\jakarta.xml.bind-api-2.3.3.jar;D:\mvn_repository\jakarta\activation\jakarta.activation-api\1.2.2\jakarta.activation-api-1.2.2.jar;D:\mvn_repository\org\assertj\assertj-core\3.16.1\assertj-core-3.16.1.jar;D:\mvn_repository\org\hamcrest\hamcrest\2.2\hamcrest-2.2.jar;D:\mvn_repository\org\junit\jupiter\junit-jupiter\5.6.3\junit-jupiter-5.6.3.jar;D:\mvn_repository\org\junit\jupiter\junit-jupiter-api\5.6.3\junit-jupiter-api-5.6.3.jar;D:\mvn_repository\org\opentest4j\opentest4j\1.2.0\opentest4j-1.2.0.jar;D:\mvn_repository\org\junit\platform\junit-platform-commons\1.6.3\junit-platform-commons-1.6.3.jar;D:\mvn_repository\org\junit\jupiter\junit-jupiter-params\5.6.3\junit-jupiter-params-5.6.3.jar;D:\mvn_repository\org\junit\jupiter\junit-jupiter-engine\5.6.3\junit-jupiter-engine-5.6.3.jar;D:\mvn_repository\org\junit\vintage\junit-vintage-engine\5.6.3\junit-vintage-engine-5.6.3.jar;D:\mvn_repository\org\apiguardian\apiguardian-api\1.1.0\apiguardian-api-1.1.0.jar;D:\mvn_repository\org\junit\platform\junit-platform-engine\1.6.3\junit-platform-engine-1.6.3.jar;D:\mvn_repository\junit\junit\4.13.2\junit-4.13.2.jar;D:\mvn_repository\org\mockito\mockito-core\3.3.3\mockito-core-3.3.3.jar;D:\mvn_repository\net\bytebuddy\byte-buddy\1.10.22\byte-buddy-1.10.22.jar;D:\mvn_repository\net\bytebuddy\byte-buddy-agent\1.10.22\byte-buddy-agent-1.10.22.jar;D:\mvn_repository\org\objenesis\objenesis\2.6\objenesis-2.6.jar;D:\mvn_repository\org\mockito\mockito-junit-jupiter\3.3.3\mockito-junit-jupiter-3.3.3.jar;D:\mvn_repository\org\skyscreamer\jsonassert\1.5.0\jsonassert-1.5.0.jar;D:\mvn_repository\com\vaadin\external\google\android-json\0.0.20131108.vaadin1\android-json-0.0.20131108.vaadin1.jar;D:\mvn_repository\org\springframework\spring-core\5.2.15.RELEASE\spring-core-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-jcl\5.2.15.RELEASE\spring-jcl-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-test\5.2.15.RELEASE\spring-test-5.2.15.RELEASE.jar;D:\mvn_repository\org\xmlunit\xmlunit-core\2.7.0\xmlunit-core-2.7.0.jar;D:\mvn_repository\org\projectlombok\lombok\1.18.20\lombok-1.18.20.jar;D:\mvn_repository\com\tencentcloudapi\tencentcloud-sdk-java\3.1.722\tencentcloud-sdk-java-3.1.722.jar;D:\mvn_repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;D:\mvn_repository\com\squareup\okio\okio\3.2.0\okio-3.2.0.jar;D:\mvn_repository\com\squareup\okio\okio-jvm\3.2.0\okio-jvm-3.2.0.jar;D:\mvn_repository\org\jetbrains\kotlin\kotlin-stdlib-jdk8\1.3.72\kotlin-stdlib-jdk8-1.3.72.jar;D:\mvn_repository\org\jetbrains\kotlin\kotlin-stdlib-jdk7\1.3.72\kotlin-stdlib-jdk7-1.3.72.jar;D:\mvn_repository\com\google\code\gson\gson\2.8.7\gson-2.8.7.jar;D:\mvn_repository\javax\xml\bind\jaxb-api\2.3.1\jaxb-api-2.3.1.jar;D:\mvn_repository\javax\activation\javax.activation-api\1.2.0\javax.activation-api-1.2.0.jar;D:\mvn_repository\com\squareup\okhttp3\logging-interceptor\3.14.9\logging-interceptor-3.14.9.jar;D:\mvn_repository\org\ini4j\ini4j\0.5.4\ini4j-0.5.4.jar;D:\mvn_repository\com\itextpdf\html2pdf\4.0.4\html2pdf-4.0.4.jar;D:\mvn_repository\com\itextpdf\forms\7.2.4\forms-7.2.4.jar;D:\mvn_repository\com\itextpdf\kernel\7.2.4\kernel-7.2.4.jar;D:\mvn_repository\com\itextpdf\io\7.2.4\io-7.2.4.jar;D:\mvn_repository\com\itextpdf\commons\7.2.4\commons-7.2.4.jar;D:\mvn_repository\org\bouncycastle\bcpkix-jdk15on\1.70\bcpkix-jdk15on-1.70.jar;D:\mvn_repository\org\bouncycastle\bcutil-jdk15on\1.70\bcutil-jdk15on-1.70.jar;D:\mvn_repository\org\bouncycastle\bcprov-jdk15on\1.70\bcprov-jdk15on-1.70.jar;D:\mvn_repository\com\itextpdf\layout\7.2.4\layout-7.2.4.jar;D:\mvn_repository\com\itextpdf\svg\7.2.4\svg-7.2.4.jar;D:\mvn_repository\com\itextpdf\styled-xml-parser\7.2.4\styled-xml-parser-7.2.4.jar;D:\mvn_repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;D:\mvn_repository\com\itextpdf\font-asian\7.2.4\font-asian-7.2.4.jar;D:\mvn_repository\commons-fileupload\commons-fileupload\1.4\commons-fileupload-1.4.jar;D:\mvn_repository\commons-io\commons-io\2.2\commons-io-2.2.jar;D:\mvn_repository\org\apache\commons\commons-lang3\3.12.0\commons-lang3-3.12.0.jar;D:\mvn_repository\com\squareup\okhttp3\okhttp\4.9.3\okhttp-4.9.3.jar;D:\mvn_repository\org\jetbrains\kotlin\kotlin-stdlib\1.3.72\kotlin-stdlib-1.3.72.jar;D:\mvn_repository\org\jetbrains\kotlin\kotlin-stdlib-common\1.3.72\kotlin-stdlib-common-1.3.72.jar;D:\mvn_repository\org\jetbrains\annotations\13.0\annotations-13.0.jar;D:\mvn_repository\org\apache\httpcomponents\httpcore\4.4.16\httpcore-4.4.16.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 com.example.demo.pdf.PdfSignTest
17:30:21.726 [main] DEBUG org.springframework.test.context.junit4.SpringJUnit4ClassRunner - SpringJUnit4ClassRunner constructor called with [class com.example.demo.pdf.PdfSignTest]
17:30:21.735 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
17:30:21.745 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
17:30:21.795 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.example.demo.pdf.PdfSignTest] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
17:30:21.817 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [com.example.demo.pdf.PdfSignTest], using SpringBootContextLoader
17:30:21.823 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.example.demo.pdf.PdfSignTest]: class path resource [com/example/demo/pdf/PdfSignTest-context.xml] does not exist
17:30:21.824 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.example.demo.pdf.PdfSignTest]: class path resource [com/example/demo/pdf/PdfSignTestContext.groovy] does not exist
17:30:21.824 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.example.demo.pdf.PdfSignTest]: no resource found for suffixes {-context.xml, Context.groovy}.
17:30:21.885 [main] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [com.example.demo.pdf.PdfSignTest]
17:30:22.049 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [com.example.demo.pdf.PdfSignTest]: using defaults.
17:30:22.049 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
17:30:22.062 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
17:30:22.062 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
17:30:22.062 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@479d31f3, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@40ef3420, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@498d318c, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@6e171cd7, org.springframework.test.context.support.DirtiesContextTestExecutionListener@402bba4f, org.springframework.test.context.event.EventPublishingTestExecutionListener@795cd85e, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@59fd97a8, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@f5ac9e4, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@123ef382, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@dbf57b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@384ad17b, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener@61862a7f]
17:30:22.064 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.065 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.067 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.067 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.067 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.067 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.079 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.079 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.080 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.080 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.081 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.081 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.087 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@5158b42f testClass = PdfSignTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@595b007d testClass = PdfSignTest, locations = '{}', classes = '{class com.example.demo.Demo1Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@6043cd28, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@5bfbf16f, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@1b26f7b2, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@13eb8acf, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@3d99d22e], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true]], class annotated with @DirtiesContext [false] with mode [null].
17:30:22.089 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.090 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.122 [main] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}.   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::       (v2.3.12.RELEASE)2023-03-29 17:30:22.526  INFO 2356 --- [           main] com.example.demo.pdf.PdfSignTest         : Starting PdfSignTest on DESKTOP-2F8ODD2 with PID 2356 (started by dell in E:\jeecg-code\demo1)
2023-03-29 17:30:22.527  INFO 2356 --- [           main] com.example.demo.pdf.PdfSignTest         : No active profile set, falling back to default profiles: default
2023-03-29 17:30:22.791  WARN 2356 --- [kground-preinit] o.s.h.c.j.Jackson2ObjectMapperBuilder    : For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
2023-03-29 17:30:30.568  INFO 2356 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2023-03-29 17:30:30.697  INFO 2356 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page: class path resource [static/index.html]
2023-03-29 17:30:30.898  INFO 2356 --- [           main] com.example.demo.pdf.PdfSignTest         : Started PdfSignTest in 8.763 seconds (JVM running for 9.721)
E:\jeecg-code\demo1\test_source_http.pdf
E:\jeecg-code\demo1\test_target_http.pdf
E:\jeecg-code\demo1\test_source_https.pdf
E:\jeecg-code\demo1\test_target_https.pdf
2023-03-29 17:30:32.700  INFO 2356 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'Process finished with exit code 0
  • 签名截图

五、其他说明

当https访问网络签名图片格式为jpg时,如果因为协议问题,响应体格式未自动转换为jpeg,在PdfUtil.createImage(String)开启注释代码的情况下,需要引入如下jpg图片解析相关作业类。

  • jpg图片数据类
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageType;import java.net.URL;public class JpgImageData extends ImageData {protected JpgImageData(URL url) {super(url, ImageType.JPEG);}protected JpgImageData(byte[] bytes) {super(bytes, ImageType.JPEG);}
}
  • jpg图片数据处理类
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.io.colors.IccProfile;
import com.itextpdf.io.exceptions.IOException;
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageType;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.io.util.StreamUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.InputStream;public final class JpgImageHandler {private static final Logger LOGGER = LoggerFactory.getLogger(JpgImageHandler.class);/*** This is a type of marker.*/private static final int NOT_A_MARKER = -1;/*** This is a type of marker.*/private static final int VALID_MARKER = 0;/*** Acceptable Jpeg markers.*/private static final int[] VALID_MARKERS = {0xC0, 0xC1, 0xC2};/*** This is a type of marker.*/private static final int UNSUPPORTED_MARKER = 1;/*** Unsupported Jpeg markers.*/private static final int[] UNSUPPORTED_MARKERS = {0xC3, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF};/*** This is a type of marker.*/private static final int NOPARAM_MARKER = 2;/*** Jpeg markers without additional parameters.*/private static final int[] NOPARAM_MARKERS = {0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0x01};/*** Marker value*/private static final int M_APP0 = 0xE0;/*** Marker value*/private static final int M_APP2 = 0xE2;/*** Marker value*/private static final int M_APPE = 0xEE;/*** Marker value for Photoshop IRB*/private static final int M_APPD = 0xED;/*** sequence that is used in all Jpeg files*/private static final byte[] JFIF_ID = {0x4A, 0x46, 0x49, 0x46, 0x00};/*** sequence preceding Photoshop resolution data*/private static final byte[] PS_8BIM_RESO = {0x38, 0x42, 0x49, 0x4d, 0x03, (byte) 0xed};/*** Process the passed Image data as a JPEG image.* Image is loaded and all image attributes are initialized and/or updated.** @param image the image to process as a JPEG image*/public static void processImage(ImageData image) {if (image.getOriginalType() != ImageType.JPEG) {throw new IllegalArgumentException("JPEG image expected");}InputStream jpegStream = null;try {String errorID = "Byte array";jpegStream = new java.io.ByteArrayInputStream(image.getData());processParameters(jpegStream, errorID, image);} catch (java.io.IOException e) {throw new IOException(IOException.JpegImageException, e);} finally {if (jpegStream != null) {try {jpegStream.close();} catch (java.io.IOException ignore) {}}}updateAttributes(image);}static void attemptToSetIccProfileToImage(byte[][] icc, ImageData image) {if (icc != null) {int total = 0;for (final byte[] value : icc) {if (value == null) {return;}total += value.length - 14;}byte[] ficc = new byte[total];total = 0;for (final byte[] bytes : icc) {System.arraycopy(bytes, 14, ficc, total, bytes.length - 14);total += bytes.length - 14;}try {image.setProfile(IccProfile.getInstance(ficc, image.getColorEncodingComponentsNumber()));} catch (Exception e) {LOGGER.error(MessageFormatUtil.format(IoLogMessageConstant.DURING_CONSTRUCTION_OF_ICC_PROFILE_ERROR_OCCURRED,e.getClass().getSimpleName(), e.getMessage()));}}}private static void updateAttributes(ImageData image) {image.setFilter("DCTDecode");if (image.getColorTransform() == 0) {image.getDecodeParms().put("ColorTransform", 0);}int colorComponents = image.getColorEncodingComponentsNumber();if (colorComponents != 1 && colorComponents != 3 && image.isInverted()) {image.setDecode(new float[]{1, 0, 1, 0, 1, 0, 1, 0});}}/*** This method checks if the image is a valid JPEG and processes some parameters.** @throws IOException* @throws java.io.IOException*/private static void processParameters(InputStream jpegStream, String errorID, ImageData image) throws java.io.IOException {byte[][] icc = null;if (jpegStream.read() != 0xFF || jpegStream.read() != 0xD8) {throw new IOException(IOException._1IsNotAValidJpegFile).setMessageParams(errorID);}boolean firstPass = true;int len;while (true) {int v = jpegStream.read();if (v < 0) {throw new IOException(IOException.PrematureEofWhileReadingJpeg);}if (v == 0xFF) {int marker = jpegStream.read();if (firstPass && marker == M_APP0) {firstPass = false;len = getShort(jpegStream);if (len < 16) {StreamUtil.skip(jpegStream, len - 2);continue;}byte[] bcomp = new byte[JFIF_ID.length];int r = jpegStream.read(bcomp);if (r != bcomp.length) {throw new IOException(IOException._1CorruptedJfifMarker).setMessageParams(errorID);}boolean found = true;for (int k = 0; k < bcomp.length; ++k) {if (bcomp[k] != JFIF_ID[k]) {found = false;break;}}if (!found) {StreamUtil.skip(jpegStream, len - 2 - bcomp.length);continue;}StreamUtil.skip(jpegStream, 2);int units = jpegStream.read();int dx = getShort(jpegStream);int dy = getShort(jpegStream);if (units == 1) {image.setDpi(dx, dy);} else if (units == 2) {image.setDpi((int) (dx * 2.54f + 0.5f), (int) (dy * 2.54f + 0.5f));}StreamUtil.skip(jpegStream, len - 2 - bcomp.length - 7);continue;}if (marker == M_APPE) {len = getShort(jpegStream) - 2;byte[] byteappe = new byte[len];for (int k = 0; k < len; ++k) {byteappe[k] = (byte) jpegStream.read();}if (byteappe.length >= 12) {String appe = new String(byteappe, 0, 5, "ISO-8859-1");if (appe.equals("Adobe")) {image.setInverted(true);}}continue;}if (marker == M_APP2) {len = getShort(jpegStream) - 2;byte[] byteapp2 = new byte[len];for (int k = 0; k < len; ++k) {byteapp2[k] = (byte) jpegStream.read();}if (byteapp2.length >= 14) {String app2 = new String(byteapp2, 0, 11, "ISO-8859-1");if (app2.equals("ICC_PROFILE")) {int order = byteapp2[12] & 0xff;int count = byteapp2[13] & 0xff;// some jpeg producers don't know how to count to 1if (order < 1) {order = 1;}if (count < 1) {count = 1;}if (icc == null) {icc = new byte[count][];}icc[order - 1] = byteapp2;}}continue;}if (marker == M_APPD) {len = getShort(jpegStream) - 2;byte[] byteappd = new byte[len];for (int k = 0; k < len; k++) {byteappd[k] = (byte) jpegStream.read();}// search for '8BIM Resolution' markerint k;for (k = 0; k < len - PS_8BIM_RESO.length; k++) {boolean found = true;for (int j = 0; j < PS_8BIM_RESO.length; j++) {if (byteappd[k + j] != PS_8BIM_RESO[j]) {found = false;break;}}if (found) {break;}}k += PS_8BIM_RESO.length;if (k < len - PS_8BIM_RESO.length) {// "PASCAL String" for name, i.e. string prefix with length byte// padded to be even length; 2 null bytes if emptybyte namelength = byteappd[k];// add length bytenamelength++;// add paddingif (namelength % 2 == 1) {namelength++;}// just skip namek += namelength;// size of the resolution dataint resosize = (byteappd[k] << 24) + (byteappd[k + 1] << 16) + (byteappd[k + 2] << 8) + byteappd[k + 3];// should be 16if (resosize != 16) {// fail silently, for now//System.err.println("DEBUG: unsupported resolution IRB size");continue;}k += 4;int dx = (byteappd[k] << 8) + (byteappd[k + 1] & 0xff);k += 2;// skip 2 unknown bytesk += 2;int unitsx = (byteappd[k] << 8) + (byteappd[k + 1] & 0xff);k += 2;// skip 2 unknown bytesk += 2;int dy = (byteappd[k] << 8) + (byteappd[k + 1] & 0xff);k += 2;// skip 2 unknown bytesk += 2;int unitsy = (byteappd[k] << 8) + (byteappd[k + 1] & 0xff);if (unitsx == 1 || unitsx == 2) {dx = (unitsx == 2 ? (int) (dx * 2.54f + 0.5f) : dx);// make sure this is consistent with JFIF dataif (image.getDpiX() != 0 && image.getDpiX() != dx) {LOGGER.debug(MessageFormatUtil.format("Inconsistent metadata (dpiX: {0} vs {1})", image.getDpiX(), dx));} else {image.setDpi(dx, image.getDpiY());}}if (unitsy == 1 || unitsy == 2) {dy = (unitsy == 2 ? (int) (dy * 2.54f + 0.5f) : dy);// make sure this is consistent with JFIF dataif (image.getDpiY() != 0 && image.getDpiY() != dy) {LOGGER.debug(MessageFormatUtil.format("Inconsistent metadata (dpiY: {0} vs {1})", image.getDpiY(), dy));} else {image.setDpi(image.getDpiX(), dx);}}}continue;}firstPass = false;int markertype = marker(marker);if (markertype == VALID_MARKER) {StreamUtil.skip(jpegStream, 2);if (jpegStream.read() != 0x08) {throw new IOException(IOException._1MustHave8BitsPerComponent).setMessageParams(errorID);}image.setHeight(getShort(jpegStream));image.setWidth(getShort(jpegStream));image.setColorEncodingComponentsNumber(jpegStream.read());image.setBpc(8);break;} else if (markertype == UNSUPPORTED_MARKER) {throw new IOException(IOException._1UnsupportedJpegMarker2).setMessageParams(errorID, Integer.toString(marker));} else if (markertype != NOPARAM_MARKER) {StreamUtil.skip(jpegStream, getShort(jpegStream) - 2);}}}attemptToSetIccProfileToImage(icc, image);}/*** Reads a short from the <CODE>InputStream</CODE>.** @param jpegStream the <CODE>InputStream</CODE>* @return an int* @throws java.io.IOException*/private static int getShort(InputStream jpegStream) throws java.io.IOException {return (jpegStream.read() << 8) + jpegStream.read();}/*** Returns a type of marker.** @param marker an int* @return a type: <VAR>VALID_MARKER</VAR>, <VAR>UNSUPPORTED_MARKER</VAR> or <VAR>NOPARAM_MARKER</VAR>*/private static int marker(int marker) {for (int i = 0; i < VALID_MARKERS.length; i++) {if (marker == VALID_MARKERS[i]) {return VALID_MARKER;}}for (int i = 0; i < NOPARAM_MARKERS.length; i++) {if (marker == NOPARAM_MARKERS[i]) {return NOPARAM_MARKER;}}for (int i = 0; i < UNSUPPORTED_MARKERS.length; i++) {if (marker == UNSUPPORTED_MARKERS[i]) {return UNSUPPORTED_MARKER;}}return NOT_A_MARKER;}}

PDF电子签名A4实现相关推荐

  1. 小学语文一年级~六年级生字表合集描红临摹字帖PDF直接A4纸打印版

    小学语文1~6年级生字表合集描红临摹字帖PDF直接A4纸打印版.rarhttps://download.csdn.net/download/usenk/74072054

  2. docusign文档打不开_怎样查看 docusign pdf 电子签名

    满意答案 knpdb4211 2017.09.26 采纳率:47%    等级:11 已帮助:3743人 日前,在福昕软件(Foxit Corporation)发布的英文版Foxit Reader6. ...

  3. PDF电子签名申请与设置方法

    一.签名证书申请 各申请单位或者个人都可以通过上线办理沃通CA证书(包括港澳台或外企) 1.法定代表人个人电子签名申请链接 https://buy.wosign.com/SpecialIVDocSig ...

  4. 通过js进行在线PDF电子签名和小编辑

    html加js实现电子签名 C#将PDF文件放在html页面进行修改并保存 我们公司最近给了个新需求,研发的图纸跟着单子一起审核,并在图纸PDF上进行签名,走线上流程.这就需要在线编辑PDF了,网上有 ...

  5. 如何把PDF中A4页面拆分成两张A5来打印

    我们在平时下载的一些电子发票或银行回执单,都是A5大小的,而一般常用页面大小或打印纸都是A4.那么如何将一张A4上的2个页面分成2张A5进行打印呢? 可能很多人都会想到一个最直接的方式就是打印后将纸张 ...

  6. UniApp 小程序实现PDF电子签名 拖拽

    需求:将线下文件签名做到app上.也就是说需要app实现PDF预览,手写签名,将签名放置pdf文件对应的位置. 1.整理实现步骤 实现PDF在小程序上预览,翻页功能 小程序实现电子签名功能,将签名转换 ...

  7. A3 PDF 转 A4两页 打印

    幺儿网课考试了,学校发给家长PDF的试卷.试卷是A3幅面的,家里只有A4的激光打印机.用软件可以简单实现高清的A3 转 A4两页 打印. 用福昕高级PDF编辑器,就是正常版本,不需要激活.用里面的截图 ...

  8. Vue html转pdf(A4纸)

    一.步骤 1.引入依赖 npm install html2canvas--save npm install jspdf --save 2.在utils文件下创建新文件.可命名为htmlToPdf.js ...

  9. PHP tcpdf实现pdf电子签名

    2021年6月18日10:50:25 官方文档 https://tcpdf.org/examples/example_052/ 几个问题需要注意的问题 /* NOTES:- To create sel ...

最新文章

  1. Kali Linux 安全渗透教程第二更Linux安全渗透简介
  2. SpringBoot 中发布ApplicationEventPublisher,监听ApplicationEvent 异步操作
  3. java agent_GitHub - dingjs/javaagent: 基于javaagent开发的APM工具,收集方法的执行次数和执行时间,定时输出成json格式的日志。...
  4. IDEA中Git操作
  5. java中的final, finally, finalize的区别
  6. 看完这些自动化原理图,有一种豁然开朗的感觉
  7. 从“void*”到指向非“void”的指针的转换要求显式类型转换错误
  8. Deeplab V1、v2要点
  9. 那些长期单身的人,到底在想什么?
  10. 本地音乐上传到网易音乐云盘上
  11. 梦想照进现实|CSDN 实体奖牌 第三期
  12. TalkingData
  13. 坚持定投3年,我赚了多少钱?
  14. 电信主机托管费用_主机托管平均费用与托管流程
  15. 编写一个图片去水印的小工具
  16. SU Podium 渲染插件学用笔记
  17. 酷炫命令行背景图操作步骤
  18. Protel DXP 2009备份
  19. mysql主从切换gtid不一致_reset master导致主从GTID不一致的处理方法
  20. 遗传算法:交叉操作 Position-based Crossover (PBX)

热门文章

  1. 【推荐系统】方法论 | 数据驱动 | 深度学习RS
  2. 一幅图弄清DFT与DTFT,FFT的关系
  3. ctfshow学习记录-web入门(命令执行69-77118)
  4. Java-routine
  5. (kuangbin带你飞--最短路径)MPI Maelstrom(dijstra模板题)
  6. 车联网TBOX硬件定制开发-新能源汽车TBOX硬件软件系统设计
  7. 基于模型预测(MPC)的无人驾驶汽车轨迹跟踪
  8. C++ STL容器 —— map/multimap 用法详解
  9. Python爬虫工具(2)--Requests[socks] --走本地PAC代理的爬虫
  10. 遥测终端RTU边坡监测预警