6.java程序员必知必会类库之pdf处理库

news2024/11/24 12:38:56

前言

Pdf作为我们办公文件中的一种常用文件格式,很多业务中会涉及到一个功能,是将系统中的某些数据,按照要求的格式生成Pdf文件。比如常见的征信报告,合同文件等等,为此通过java代码,处理PDF格式的文件,是java程序员需要掌握的技能。

1 itextpdf操作pdf

1.1 简介

  1. 适合写文件,相对支持的格式比较多,图片,表格等等

1.2 pom坐标引入

<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
	<groupId>com.itextpdf</groupId>
	<artifactId>itextpdf</artifactId>
	<version>5.5.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
<dependency>
	<groupId>com.itextpdf</groupId>
	<artifactId>itext-asian</artifactId>
	<version>5.2.0</version>
</dependency>

1.3 api使用

关于PDF的读写,合并,拆分,比较常用的是生成PDF。

1.3.1 读

1.3.1.1 样例demo

新建一个word文档,内容如下,里面包含文字和图片,导出为pdf文件
在这里插入图片描述

1.3.1.2 测试代码

@Test
public void testRead() {
    String fileName = "C:\\Users\\newhope\\Desktop\\测试pdf\\测试01.pdf";
    String result = "";
    FileInputStream in = null;
    try {
        in = new FileInputStream(fileName);
        // 新建一个PDF解析器对象
        PdfReader reader = new PdfReader(fileName);
        reader.setAppendable(true);
        // 对PDF文件进行解析,获取PDF文档页码
        int size = reader.getNumberOfPages();
        for (int i = 1; i < size + 1; ) {
            //一页页读取PDF文本
            String pageStr = PdfTextExtractor.getTextFromPage(reader, i);
            result = result + pageStr + "\n" + "PDF解析第" + (i) + "页\n";
            i = i + 1;
        }
        reader.close();
    } catch (Exception e) {
        System.out.println("读取PDF文件" + fileName + "生失败!" + e);
        e.printStackTrace();
    } finally {
        if (in != null) {
            try {
                in.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
    System.out.println(result);
}

代码运行结果如下,可以看到文本数据可以正常解析拿到,但是图片没有正常解析:

我是一段测试的数据
下面是测试图片
PDF解析第1

1.3.2 写(常用)


// 页眉事件
private static class Header extends PdfPageEventHelper {
    public static PdfPTable header;

    public Header() {
    }

    public Header(PdfPTable header) {
        Header.header = header;
    }

    @Override
    public void onEndPage(PdfWriter writer, Document doc) {
        // 把页眉表格定位
        header.writeSelectedRows(0, -1, 30, 840, writer.getDirectContent());
    }

    /**
     * 设置页眉
     *
     * @param writer
     * @throws Exception
     */
    public void setTableHeader(PdfWriter writer, String subject) throws Exception {

        URL url = getClass().getClassLoader().getResource("picture/logo.jpg");// 获取文件的URL
        PdfPTable table = new PdfPTable(2);
        table.setTotalWidth(530);
        PdfPCell cell = new PdfPCell();
        cell.setBorder(0);
        Image image01;
        //image01 = Image.getInstance(PropertyUtil.getProperty("logoPath")); // 图片自己传
        image01 = Image.getInstance(url); // 图片自己传
        image01.scaleAbsolute(30f, 30f);
        cell.addElement(image01);
        cell.setBorderWidthBottom(1);
        cell.setRight(100f);
        table.addCell(cell);


        BaseFont bf;
        Font font = null;
        try {
            bf = BaseFont.createFont( "STSong-Light", "UniGB-UCS2-H",
                    BaseFont.NOT_EMBEDDED);//创建字体
            font = new Font(bf,12);//使用字体
        } catch (DocumentException | IOException e) {
            log.error("页面创建字体异常",e);
        }
        Paragraph p = new Paragraph(subject, font);
        p.setAlignment(1);
        PdfPCell cell0 = new PdfPCell();
        cell0.setBorder(0);
        cell0.setBorderWidthBottom(1);

        table.addCell(cell0);
        Header event = new Header(table);
        writer.setPageEvent(event);
    }
}

@Test
  public void testWrite() throws Exception {
      String filename = "C:\\Users\\newhope\\Desktop\\测试pdf\\测试02.pdf";
      // 创建文件
      Document document = new Document(PageSize.A4);
      // 创建pdf
      PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
      Header header = new Header();
      header.setTableHeader(writer, "");
      document.open();
      BaseFont baseFont = null;
      Font commonFont = null;
      try {
          baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",
                  BaseFont.NOT_EMBEDDED);//创建字体
          commonFont = new Font(baseFont, 10.5f);//使用字体
      } catch (DocumentException | IOException e) {
          log.error("pdf字体创建异常", e);
      }
      /*
       * 标题
       */
      Paragraph paragraph = new Paragraph("标题", new Font(baseFont, 16));
      paragraph.setAlignment(1);
      paragraph.setLeading(2);
      PdfPCell pdfPCellTitle = new PdfPCell(paragraph);
      pdfPCellTitle.setHorizontalAlignment(1);
      pdfPCellTitle.disableBorderSide(15);

      PdfPTable pdfPTableTitle = new PdfPTable(1);
      pdfPTableTitle.addCell(pdfPCellTitle);
      document.add(pdfPTableTitle);
      //注意,这里要设置字体,否则中文会不显示
      document.add(new Paragraph("这是正文,测试pdf写入", commonFont));

      //写一个表格进去
      // 换行
      document.add(new Paragraph(" "));
      // 添加表格,3列 
      PdfPTable table = new PdfPTable(3);
      // 设置表格宽度比例为%100
      table.setWidthPercentage(100);
      // 设置表格上面空白宽度
      table.setSpacingBefore(10f);
      // 设置表格下面空白宽度
      table.setSpacingAfter(10f);
      // 设置表格默认为无边框
      table.getDefaultCell().setBorder(0);
      PdfPCell cell0 = new PdfPCell(new Paragraph("姓名",commonFont));
      // 设置跨两行
      cell0.setRowspan(2);
      // 设置距左边的距离
      cell0.setPaddingLeft(10);
      // 设置高度
      cell0.setFixedHeight(20);
      // 设置内容水平居中显示
      cell0.setHorizontalAlignment(Element.ALIGN_CENTER);
      // 设置垂直居中
      cell0.setVerticalAlignment(Element.ALIGN_MIDDLE);
      table.addCell(cell0);

      PdfPCell cellsex = new PdfPCell(new Paragraph("性别",commonFont));
      // 设置跨两行
      cellsex.setRowspan(2);
      // 设置距左边的距离
      cellsex.setPaddingLeft(10);
      // 设置高度
      cellsex.setFixedHeight(20);
      // 设置内容水平居中显示
      cellsex.setHorizontalAlignment(Element.ALIGN_CENTER);
      // 设置垂直居中
      cellsex.setVerticalAlignment(Element.ALIGN_MIDDLE);
      table.addCell(cellsex);

      PdfPCell cellage = new PdfPCell(new Paragraph("年龄",commonFont));
      // 设置跨两行
      cellage.setRowspan(2);
      // 设置距左边的距离
      cellage.setPaddingLeft(10);
      // 设置高度
      cellage.setFixedHeight(20);
      // 设置内容水平居中显示
      cellage.setHorizontalAlignment(Element.ALIGN_CENTER);
      // 设置垂直居中
      cellage.setVerticalAlignment(Element.ALIGN_MIDDLE);
      table.addCell(cellage);


      document.add(table);
      //注意:通常资源关闭要 try catch 异常后,放到finally里面,否则可能会导致资源没有释放,这里是测试代码,直接关闭
      document.close();

  }

测试代码生成pdf样式如下:
在这里插入图片描述
注意:

  1. 通常资源关闭要放到try catch finally模块,否则前面出现异常,可能会导致资源没有释放,后面不再赘述
  2. 上面例子只是演示了几种常见的数据,图片,表哥,文字,怎么写到pdf里面,具体用的时候,要根据业务需求自己扩展
  3. pom坐标要引入itext-asian ,以及,写pdf的时候指定自己创建的字体,否则汉字可能会显示不出来
  4. 细心观察可以看到,上面测试方法,我们是把一块块数据拼上去的,其实有些内容是可以封装通用的方法的,这样后续拼接数据会方便很多
  5. 上面拼接excel代码,如果设置的列数是3列,但是后面代码没有拼到三个单元格,则生成的pdf里面不会生成对应的表格,也没有报错提示
  6. 在生成pdf的过程中,还有个比较繁琐的可能是调整样式,行距多少合适,宽度多少。。。这个有点像是写前端页面的感觉

1.3.3 拆分

这里用一个比较大的pdf做演示,按照页码拆分,代码如下:

@Test
public void testSplit() throws Exception{
    String fileName = "C:\\Users\\newhope\\Desktop\\测试pdf\\测试03.pdf";
    PdfReader reader = new PdfReader(fileName);
    int n = reader.getNumberOfPages();
    System.out.println ("Number of pages :" + n);
    int i = 0;
    while ( i < n ) {
        String outFile = fileName.substring(0, fileName.indexOf(".pdf"))
                +"-" + String.format("%03d", i + 1) +".pdf";
        System.out.println ("Writing" + outFile);
        Document document = new Document(reader.getPageSizeWithRotation(i+1));
        PdfCopy writer = new PdfCopy(document, new FileOutputStream(outFile));
        document.open();
        PdfImportedPage page = writer.getImportedPage(reader, ++i);
        writer.addPage(page);
        document.close();
        writer.close();
    }
}

在这里插入图片描述

1.3.4 合并

@Test
 public void testMerge() throws Exception{
     List<String> sourceFilePaths = Arrays.asList("C:\\Users\\newhope\\Desktop\\测试pdf\\测试01.pdf","C:\\Users\\newhope\\Desktop\\测试pdf\\测试03.pdf");
     String destFilePath="C:\\Users\\newhope\\Desktop\\测试pdf输出";

     Document document = null;
     PdfCopy copy = null;
     OutputStream os = null;
     try {
         // 创建合并后的新文件的目录
         Path dirPath = Paths.get(destFilePath.substring(0, destFilePath.lastIndexOf(File.separator)));
         Files.createDirectories(dirPath);

         os = new BufferedOutputStream(new FileOutputStream(new File(destFilePath)));
         document = new Document(new PdfReader(sourceFilePaths.get(0)).getPageSize(1));
         copy = new PdfCopy(document, os);
         document.open();
         for (String sourceFilePath : sourceFilePaths) {
             // 如果PDF文件不存在,则跳过
             if (!new File(sourceFilePath).exists()) {
                 continue;
             }

             // 读取需要合并的PDF文件
             PdfReader reader = new PdfReader(sourceFilePath);
             // 获取PDF文件总页数
             int n = reader.getNumberOfPages();
             for (int j = 1; j <= n; j++) {
                 document.newPage();
                 PdfImportedPage page = copy.getImportedPage(reader, j);
                 copy.addPage(page);
             }
         }
     } catch (Exception e) {
         e.printStackTrace();
     } finally {
         if (copy != null) {
             try {
                 copy.close();
             } catch (Exception ex) {
                 /* ignore */
             }
         }
         if (document != null) {
             try {
                 document.close();
             } catch (Exception ex) {
                 /* ignore */
             }
         }
         if (os != null) {
             try {
                 os.close();
             } catch (Exception ex) {
                 /* ignore */
             }
         }
     }
 }

代码运行效果感兴趣可以自己本地测试

2 pdfbox操作pdf

2.1 简介

  1. 适合文件的拆分合并,保存为图片等
  2. 不适合复杂格式的pdf代码拼接处理,但是这种可以通过word转pdf实现,即定制一个word模板,代码根据占位符替换里面数据,比如姓名,金额等,最后将word转为pdf

2.2 pom坐标引入

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.21</version>
</dependency>

2.3 api使用

2.3.1 读

@Test
public void testRead() throws Exception {
    String pdfPath = "C:\\Users\\newhope\\Desktop\\测试pdf\\测试01.pdf";
    String result = readPDF(pdfPath);
    System.out.println(result);
}

public String readPDF(String file) throws IOException {
    String picturePath = "C:\\Users\\newhope\\Desktop\\测试pdf\\";

    StringBuilder result = new StringBuilder();
    FileInputStream is = null;
    is = new FileInputStream(file);
    PDFParser parser = new PDFParser(new RandomAccessBuffer(is));
    parser.parse();
    PDDocument doc = parser.getPDDocument();
    PDFTextStripper textStripper = new PDFTextStripper();
    for (int i = 1; i <= doc.getNumberOfPages(); i++) {
        textStripper.setStartPage(i);
        textStripper.setEndPage(i);
        textStripper.setSortByPosition(true);//按顺序行读
        String s = textStripper.getText(doc);
        result.append(s);
    }
    //读取图片,保存到指定目录,真实业务场景可以上传到文件服务器,方便后续使用
    for (int i = 1; i <= doc.getNumberOfPages(); i++) {
        PDPage page = doc.getPage(i - 1);
        PDResources resources = page.getResources();
        Iterable<COSName> xobjects = resources.getXObjectNames();
        if (xobjects != null) {
            Iterator<COSName> imageIter = xobjects.iterator();
            while (imageIter.hasNext()) {
                COSName cosName = imageIter.next();
                boolean isImageXObject = resources.isImageXObject(cosName);
                if (isImageXObject) {
                    //获取每页资源的图片
                    PDImageXObject ixt = (PDImageXObject) resources.getXObject(cosName);
                    File outputfile = new File(picturePath + cosName.getName() + ".jpg");
                    ImageIO.write(ixt.getImage(), "jpg", outputfile);//可保存图片到本地
                }
            }
        }
    }
    doc.close();
    return result.toString();
}

代码运行结果,文字正常读取打印:

我是一段测试的数据
下面是测试图片

图片正常保存:
在这里插入图片描述

2.3.2 写

@Test
public void testWrite() throws Exception {
    String pdfPath = "C:\\Users\\newhope\\Desktop\\测试pdf\\测试POI写.pdf";
    String data="asfas中文测试dfas";
    PDDocument doc = new PDDocument();
    try {
        PDPage page = new PDPage();
        doc.addPage(page);
        //PDFont font = PDType1Font.HELVETICA_OBLIQUE;
        //这里注意,如果包含中文的话,要导入字体文件,否则要不写报错,要么中文写不出来
        PDFont font =  PDType0Font.load(doc, new File("E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\2023-03\\test\\src\\main\\resources\\font\\test.ttf"));
        PDPageContentStream contents = new PDPageContentStream(doc, page);
        contents.beginText();
        contents.setFont(font, 30);
        contents.newLineAtOffset(50, 700);
        contents.showText(data);
        contents.endText();
        contents.close();
        doc.save(pdfPath);
    }
    finally {
        doc.close();
    }
}

代码运行结果如下:
在这里插入图片描述
注意:

  1. 如果文本包含中文用默认字体行不通,需要自己导入字体文件,字体文件可以从网上找,文末有网站链接
  2. 出现java.lang.NoSuchMethodError 问题,考虑看是不是pdfbox版本不对导致的问题

2.3.3 拆分

@Test
public void testSplit() throws Exception{

    String fileName = "C:\\Users\\newhope\\Desktop\\测试pdf\\测试03.pdf";
    PDDocument pdf = PDDocument.load(new File(fileName));
    //1、将第一个pdf按页码全部拆开
    Splitter splitter = new Splitter();
    List<PDDocument> pdDocuments = splitter.split(pdf);
    for (int i = 0; i < pdDocuments.size(); i++) {
        PDDocument pdDocument = pdDocuments.get(i);
        pdDocument.save("C:\\Users\\newhope\\Desktop\\测试pdf\\测试POI"+i +".pdf");
    }
}

代码运行结果,文件正常拆分:
在这里插入图片描述

2.3.4 合并

@Test
public void testMerge() throws Exception{

    List<String> sourceFilePaths = Arrays.asList("C:\\Users\\newhope\\Desktop\\测试pdf\\测试01.pdf","C:\\Users\\newhope\\Desktop\\测试pdf\\测试03.pdf");
    String destFilePath="C:\\Users\\newhope\\Desktop\\测试pdf\\测试POI合并.pdf";
    PDFMergerUtility pdfMerger = new PDFMergerUtility();
    pdfMerger.setDestinationFileName(destFilePath);
    for (String sourceFilePath : sourceFilePaths) {
        pdfMerger.addSource(sourceFilePath);
    }
    //合并文档,这里会推荐,让你指定合并文件的方式,是在内存中,还是在临时文件中
    //pdfMerger.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
    pdfMerger.mergeDocuments();
}

代码运行结果可以看到,pdf正常合并:
在这里插入图片描述

参考文献:

pdfbox更详细介绍
字体网站

以上,本人菜鸟一枚,如有问题,请不吝指正

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vulnhub项目:Earth

靶机地址&#xff1a;The Planets: Earth ~ VulnHub 渗透过程&#xff1a; 首先查看靶机描述&#xff0c;需要获取2个flag 老样子&#xff0c;确定靶机ip&#xff0c;具体的就不详细写了&#xff0c;看图即可 探测靶机开放端口 如果不进行dns绑定&#xff0c;就会出现下面的…

带你玩转状态机(论点:概念、相关图示、示例代码、适用场景、相关文档)

概念 状态机&#xff08;State Machine&#xff09;是一种用于描述系统在不同状态下的行为及状态之间转换的数学模型。状态机主要由三个部分组成&#xff1a;状态&#xff08;State&#xff09;、事件&#xff08;Event&#xff09;和转换&#xff08;Transition&#xff09;。…

Vue2-黑马(九)

0目录&#xff1a; &#xff08;1&#xff09;router-动态菜单 &#xff08;2&#xff09;vuex-入门 &#xff08;3&#xff09;vuex-mapState &#xff08;1&#xff09;router-动态菜单 我们点击按钮跳转到主页面&#xff0c;主页在制作动态菜单&#xff0c;路由的跳转方…

【PWN】刷题——CTFHub之 简单的 ret2text

萌新第一阶段自然是了解做题的套路、流程&#xff0c;简单题要多做滴 目录 前言 一、checksec查看 二、IDA反汇编 三、exp编写 前言 经典的ret2text流程 一、checksec查看 64位程序&#xff0c;什么保护都没有&#xff0c;No canary found——可以栈溢出控制返回 二、IDA反汇…

SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解、如何添加锁解决缓存击穿问题?分布式情况下如何添加分布式锁

文章目录 1、步骤2、具体过程1、引入pom依赖2、修改配置文件3、单元测试4、测试结果 3、redis运行情况4、项目中实际应用5、加锁解决缓存击穿问题代码一&#xff08;存在问题&#xff09;代码二&#xff08;问题解决&#xff09; 6、新问题7、分布式锁 1、步骤 前提条件&#…

FFmpeg 编译静态库

1. 使用工具 1.1 FFmpeg 官网: 1.2 FFmpeg macOS 官方安装教程: 1.3 Homebreaw 安装网站: 2. Homebreaw 介绍 2.1 简称 brew&#xff0c;在 Mac 平台终端上管理软件包&#xff0c;安装&#xff0c;更新&#xff0c;卸载等软件 2.2 安装 brew&#xff0c;终端执行指令(内部安装…

HTTP协议详解(一)

目录 1.什么是HTTP协议? 2.HTTP的协议格式 使用fiddler抓包工具 理解代理 查看请求内容 3.HTTP请求(Request) 认识URL URL encode 认识method GET方法 POST方法 经典面试题:POST和GET之间的典型区别 其它方法 认识请求 "报头" (header) Host Conte…

Elasticsearch:为日志分析设置安全的 Elasticsearch 管道

在我之前的许多文章中&#xff0c;我已经详细地描述了如何配置如下的管道&#xff1a; 如果你想了解更多&#xff0c;请详细阅读文章&#xff1a; Logstash&#xff1a;Logstash 入门教程 &#xff08;二&#xff09; Elastic&#xff1a;运用 Docker 安装 Elastic Stack 并采…

企业在实施采购管理时需要注意哪些问题?

采购管理是指企业为了获得所需的物资和服务等&#xff0c;通过筛选供应商、谈判合同、执行采购计划等一系列过程来实现目标的管理活动。在实施过程中&#xff0c;采购管理需要注意以下几个问题&#xff1a; 1、采购策略的选择 采购策略的选择是采购管理中非常关键的环节。不同…

分享5款win10小工具,让办公学习井井有条

好用的小工具能让办公学习变得更简单便捷&#xff0c;这里推荐几款实用的Win10小工具。 桌面小工具——Win10 Widgets Win10 Widgets是一款实用的桌面小工具软件&#xff0c;可以让你在桌面上显示各种系统信息。你可以使用Win10 Widgets来查看电源、硬盘、CPU、内存、网络、时…

数据结构_第十三关(1):简单排序算法

【本关目标】 排序的概念常见排序的算法思想和实现排序算法的复杂度以及稳定性分析 目录 【本关目标】 1.排序的概念 2.常见排序的算法思想和实现&#xff08;代码默认都是从小到大排序&#xff09; 2.1插入排序 1&#xff09;直接插入排序 2&#xff09;希尔排序 2.2选…

Java 死锁的原理、检测和解决死锁

什么是死锁 两个或者多个线程互相持有对方所需要的资源&#xff08;锁&#xff09;&#xff0c;都在等待对方执行完毕才能继续往下执行的时候,就称为发生了死锁&#xff0c;结果就是两个进程都陷入了无限的等待中。 一般是有多个锁对象的情况下并且获得锁顺序不一致造成的。 …

微服务+springcloud+springcloud alibaba学习笔记【Spring Cloud Gateway服务网关】(7/9)

Spring Cloud Gateway服务网关 7/9 1、GateWay概述2、GateWay的特性:3、GateWay与zuul的区别:4、zuul1.x的模型:5、什么是webflux:6、GateWay三大概念:6.1,路由:6.2,断言:6.3,过滤: 7、GateWay的工作原理:8、使用GateWay:8.1,建module8.2,修改pom文件8.3,写配置文件8.4,主启动类…

微服务学习——微服务框架

Nacos配置管理 统一配置管理 配置更改热更新 将配置交给Nacos管理的步骤&#xff1a; 在Nacos中添加配置文件在微服务中引入nacos的config依赖在微服务中添加bootstrap.yml&#xff0c;配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个…

Java:JDK对IPv4和IPv6处理介绍

以下以JDK8为例说明对IPv4和IPv6是如何处理的。 一、常用代码 一般情况下&#xff0c;使用如下代码可以获取到域名/主机名对应的多个IP&#xff0c;其中部分是IPv4的&#xff0c;部分是IPv6的&#xff1a; try {InetAddress[] addrs InetAddress.getAllByName(host);for (I…

Quartz框架详解分析

文章目录 1 Quartz框架1.1 入门demo1.2 Job 讲解1.2.1 Job简介1.2.2 Job 并发1.2.3 Job 异常1.2.4 Job 中断 1.3 Trigger 触发器1.3.1 SimpleTrigger1.3.2 CornTrigger 1.4 Listener监听器1.5 Jdbc store1.5.1 简介1.5.2 添加pom依赖1.5.3 建表SQL1.5.4 配置文件quartz.propert…

23-HTTP协议

目录 1.HTTP是什么&#xff1f; 2.HTTP工作过程 3.HTTP协议格式 3.1.抓包工具使用 eg&#xff1a;抓取"必应"的包 PS&#xff1a;HTTP不同版本号之间的区别 3.2.抓包工具原理 3.3.抓包结果分析 ①HTTP 请求&#xff1a; ②HTTP 响应&#xff1a; 3.4.协议…

ArduPilot Kakute F7 AIO DIYF450 without GPS配置

ArduPilot Kakute F7 AIO DIYF450 without GPS配置 1. 源由2. 配置2.1 Kakute F7 AIO相关配置2.1.1 串口规划2.1.2 电传配置2.1.3 GPS配置2.1.4 CRSF接收机配置2.1.5 Compass配置2.1.6 电机配置2.1.7 TX12 遥控器配置 3. 实测效果4. 参考资料 1. 源由 鉴于GPS模块信号质量未达…

3DEXPERIENCE云可以为PLM带来什么?

在消费者领域&#xff0c;云的优势已显而易见&#xff0c;用一个词就可以概括&#xff1a;便利&#xff0c;3DEXPERIENCE云存储服务的用户可以从任何位置在任何设备上访问其数据&#xff0c;只要能够连接到互联网就行了。在一台设备 上所做的更改会立即反映在另一台设备上。 同…

提升10倍写作效率,这5个写作工具,文笔不好的人别错过

记得刚出来上班的时候&#xff0c;我的写作效率很低&#xff0c;经常没有思路&#xff0c;也找不到选题。甚至一两个小时过去了&#xff0c;仍然不知道如何动笔&#xff0c;经常写了删&#xff0c;删了又写。工欲善其事&#xff0c;必先利其器。在写作过程中&#xff0c;需要一…