Rust 赋能前端:PDF 分页/关键词标注/转图片/抽取文本/抽取图片/翻转...

我从不幻想成功。我只会为了成功努力实践

大家好,我是柒八九。一个专注于前端开发技术/RustAI应用知识分享Coder

此篇文章所涉及到的技术有

  1. WebAssembly
  2. Mupdf
  3. Pdf操作( 分页展示/文本抽离/文本标注/获取超链接/Pdf转图片/翻转/截取)

因为,行文字数所限,有些概念可能会一带而过亦或者提供对应的学习资料。请大家酌情观看。


前言

从上次发文SSE打扮你的AI应用,让它美美哒已经差不多过了快2个月了。

这期间呢,发生了很多事情。

足足有8周的时间。

一周就是一年的2%

那就是这一年的16%的时间,确实抽不出时间来写文章。这里向广大的读者说一句抱歉。

不过呢,这段时间内,也没闲着。虽然,没时间写文章,但是做了很多有趣的功能。

  1. 操作 PDF(就是这篇文章的主要内容)
  2. EPub分页展示
  3. 视频抽帧( Rust+ WebAssembly)
  4. 图片OCR识别( AI模型+ Rust+ WebAssembly)
  5. 图片基于关键词进行标注( Rust+ WebAssembly)
  6. 音频文件识别(AI模型+ Python)
  7. SRT文件标注
  8. 文本翻译(AI模型+ Python)
  9. leafletjs [1](GIS相关)

大家不要着急,这些内容都准备写成文章。

老粉都知道,之前我们在Rust 赋能前端 -- 写一个 File 转 Img 的功能/AI 赋能前端 -- 文本内容概要生成介绍过"文档操作"的功能。

而就在之后,我们其中一个需求中,又新增了一个对PDF分页展示和关键词标注的功能点。

也就是说,我们无法直接使用iframe亦或者pdfjs-dist[2]PDF常规解决方案来实现上述操作。

这已经超出了正常前端的技能范畴了,那么我们就需要把视角移到其他语言环境(C/C++/Rust)是否有成熟的解决方案。然后配套WebAssembly来实现我们的目的。

而今天,我们就来讲讲。在前端如何使用WebAssembly来拓展前端应用的功能,实现之前不能或者不好实现的功能。

之前呢,我们在Rust 赋能前端 -- 写一个 File 转 Img 的功能就介绍过mupdf

上次呢,我们使用mupdf-js[3]Pdf进行了一些操作。例如,将PDF转成text/png/svg/html。但是呢,在使用mupdf-js有一个弊端就是,有些高级功能,例如(翻转/文本标注/获取pdf中的图片等)无法实现。

所以,今天我们绕过mupdf-js而是直接使用mupdf[4]的编译好的wasm文档来执行相关操作。

效果展示

首先,我们会使用mupdf为大家讲解下面的各种操作。

例如:

  1. '获取元数据'
  2. '页数'
  3. '结构化文本'
  4. '抽取图片'
  5. '获取标注信息'
  6. '文本查询'
  7. '获取文档中超链接'
  8. '获取文档大小'
  9. 'pdf转图片'
  10. '添加文本'
  11. '翻转'
  12. '截取'
  13. '文档分割'
alt

其次,我们会基于上面的各种能力,来实现一个Pdf分页关键词标注的操作。

alt

好了,天不早了,干点正事哇。

alt

我们能所学到的知识点

  1. 项目初始化
  2. 使用Mupdf 蹂躏 PDF
  3. PDF 分页展示和文本标注

1. 项目初始化

从上面的演示效果,是不是有种似曾相识的感觉,对呢。我们还是基于f_cli_f[5]来构建的前端Vite+React+TS项目。

f_cli_f,我们后期打算升级一版,敬请期待。

当我们通过yarn/npm安装好对应的包时。我们就可以在pages新建一个Pdf2Img的目录。

然后构建如下的目录结构

├── KeyWordsHight.tsx 
├── PdfShow.tsx  
├── index.tsx
└── pdf.ts

这里呢,我们没有使用Web Worker或者Comlink[6]。是因为,在之前Rust 赋能前端 -- 写一个 File 转 Img 的功能/AI 赋能前端 -- 文本内容概要生成就有过相关的解释。所有,这里为了行文方便,就选择了最简单的方式 - Promise来处理针对PDF的相关操作。

引入mupdf的wasm

我们在前端项目中,新建一个wasm来存放在前端项目中要用到的各种wasm

然后,我们创建一个mupdf的文件夹,这里面是存放mupdf编译后的文件内容。

├── mupdf-wasm.js
├── mupdf-wasm.wasm
├── mupdf.d.ts
└── mupdf.js

具体针对mupdfwasm文件从哪里来,我们可以通过mupdf_github亦或者npm包来获取。当然,之前也介绍过,如果你还想使用更高级的功能,我们也可以自己通过命令来编译。

下面,我们就以功能点来各自介绍它们的作用。


2. 使用 Mupdf 蹂躏 PDF

这个标题确实吓人,但是看了下面的操作后,发现这个词真是很贴切。

下面,我们就来讲讲在前言出现过的mupdf的各种能力。

alt

在这节中,我们就直接使用代码来演示mupdf赋予我们的能力。如果想了解更多关于Mupdf在前端环境的使用方式,可以翻看mupdf_core API[7]

初始化mudpf实例

我们将初始化mupdf放置在pdf.ts文件中。然后,我们在pdf.ts存储一个全局变量(doc)来表示mupdf的实例。

let doc: mupdf.Document;

export function getInstance(buffer: ArrayBuffer{
  doc = mupdf.Document.openDocument(buffer, 'application/pdf');
}

这样,在页面中,我们可以通过inputonChange或者通过fetch来获取pdfArrayBuffer资源。

而我们这里是通过input来处理pdf。在index.tsx中,我们有一个inputonChange的回调。

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = e.target.files;
    if (files && files.length > 0) {
      const file = files[0];
      const reader = new FileReader();

      reader.onload = (event) => {
        const arrayBuffer = event.target?.result as ArrayBuffer;
        getInstance(arrayBuffer);
      };

      reader.readAsArrayBuffer(file);
    }
  };

这样,我们在每次接收到arrayBuffer后,就会实例化对于的mupdf

获取元数据

我们可以通过如下代码来获取一个PDF的元数据信息。

export function getMetadata(): Promise<string{
  return new Promise((res) => {
    const format = doc.getMetaData('format');
    const modificationDate = doc.getMetaData('info:ModDate');
    const encryption = doc.getMetaData('encryption');
    const author = doc.getMetaData('info:Author');
    const title = doc.getMetaData('info:Title');
    res(`format-${format}
        encryption-${encryption}
        modificationDate-${modificationDate}
        title-${title}
        author-${author}
        `
);
  });
}

当然,我们还可以获取其他的元数据信息。

alt

我们还可以通过setMetaData(key: string, value: string)来对某个元数据设定值。

效果展示

alt

页数

我们可以通过doc.countPages()来获取pdf的总页数。

那既然,PDF的总页面信息有了,是不是我们可以将其做分页处理。这个我们在下一节中会讲到。

有些功能,我们为了行文的方便,在介绍一些功能时,只对其某个page页面进行处理。其实,如果你想通过下面的功能获取全部pdf的相关信息,可以通过遍历doc.countPages()来对全文档进行操作处理。

效果展示

alt

结构化文本

我们可以通过toStructuredText来抽离指定页面的文本内容。

export function getStructuredText(page: number): Promise<string{
  return new Promise((res) => {
    const pageContent = doc.loadPage(page);
    const json = pageContent.toStructuredText('preserve-whitespace').asJSON();
    res(json);
  });
}

通过toStructuredText返回了一个StructuredText类型的数据。

export declare class StructuredText extends Userdata<'fz_stext_page'> {
  static readonly _drop: (p: Pointer<'fz_stext_page'>) => void;
  static readonly SELECT_CHARS = 0;
  static readonly SELECT_WORDS = 1;
  static readonly SELECT_LINES = 2;
  walk(walker: StructuredTextWalker): void;
  asJSON(scale?: number): string;
  copy(p: Point, q: Point): string;
  highlight(p: Point, q: Point, max_hits?: number): Quad[];
  search(needle: string, max_hits?: number): Quad[][];
}

从上面定义我们可以看到,我们可以基于StructuredText做更精细化的操作。(walk/search等)

效果展示

alt

抽取图片

pdf.ts中定义如下代码

type imagesType = {
  bbox: [numbernumbernumbernumber];
  matrix: [numbernumbernumbernumbernumbernumber];
  image: mupdf.Image;
};

export function getImages(page: number): Promise<imagesType[]> {
  return new Promise((res) => {
    const result: imagesType[] = [];
    const pageContent = doc.loadPage(page);
    pageContent.toStructuredText('preserve-images').walk({
      onImageBlock(bbox, matrix, image) {
        result.push({ bbox, matrix, image });
      },
    });
    res(result);
  });
}

然后在页面中的指定函数中,处理getImages返回的数据信息

 if (ability === '抽取图片') {
        res = await getImages(page);
        const arr = [];
        res.map(async (item) => {
          const image = item.image;
          const pngUint8Array = image.toPixmap().asPNG();
          const blob = new Blob([pngUint8Array], { type'image/svg' });
          const url = await BlobToObjectURL(blob);
          arr.push(url);
        });

        setImgUrlArr(arr);
      }

上面的代码就是用于抽取某页pdf中图片信息。

效果展示

alt

文本查询

pdf.ts中定义如下代码

type Quad = [numbernumbernumbernumbernumbernumbernumbernumber];
export function search(page: number, keywords: string): Promise<Quad[][]> {
  return new Promise((res) => {
    const pageContent = doc.loadPage(page);
    const result = pageContent.search(keywords);
    res(result);
  });
}

效果展示

我们通过search可以获得对应keywords在原文档中的位置。那么,就给我提供了一个机会,用于该文本的标注处理。这个我们在下一节中介绍。

alt

获取文档中超链接

pdf.ts中定义如下代码

export function getLinks(page: number): Promise<mupdf.Link[]> {
  return new Promise((res) => {
    const pageContent = doc.loadPage(page);
    const links = pageContent.getLinks();
    res(links);
  });
}

效果展示

alt

我们可以看到,它能准确识别出pdf文档中的超链接。


获取文档大小

type Rect = [numbernumbernumbernumber];
export function getBounds(page: number): Promise<Rect{
  return new Promise((res) => {
    const pageContent = doc.loadPage(page);
    const bounds = pageContent.getBounds();
    res(bounds);
  });
}

效果展示

alt

pdf转图片

export function getContentByPage(page: number): Promise<string{
  return new Promise((res) => {
    const pageContent = doc.loadPage(page);
    const pixmap = pageContent.toPixmap(
      mupdf.Matrix.identity,
      mupdf.ColorSpace.DeviceRGB,
      false,
      true
    );
    const pngUint8Array = pixmap.asPNG();
    const blob = new Blob([pngUint8Array], { type'image/svg' });
    blobToBase64Uri(blob).then((base64) => {
      res(base64);
    });
  });
}

效果展示

alt

添加文本

export type textType = {
  text: string;
  x: number;
  y: number;
  fontFamily: string;
  fontSize: number;
};

export function addText(pageNumber: number, textInfo: textType): Promise<Uint8Array{
  return new Promise((res) => {
    const { text, x, y, fontFamily, fontSize } = textInfo;
    const page = doc.loadPage(pageNumber) as mupdf.PDFPage;
    const pageObj = page.getObject();

    const pdfDocument = doc as mupdf.PDFDocument;

    const font = pdfDocument.addSimpleFont(new mupdf.Font(fontFamily));

    let resources = pageObj.get('Resources');
    if (!resources.isDictionary())
      pageObj.put('Resources', (resources = pdfDocument.newDictionary()));

    let resFonts = resources.get('Font');
    if (!resFonts.isDictionary()) resources.put('Font', (resFonts = pdfDocument.newDictionary()));

    resFonts.put('F1', font);

    // create drawing operations
    const extra_contents = pdfDocument.addStream(
      'BT /F1 ' + fontSize + ' Tf 1 0 0 1 ' + x + ' ' + y + ' Tm (' + text + ') Tj ET',
      {}
    );

    // add drawing operations to page contents
    const page_contents = pageObj.get('Contents');
    if (page_contents.isArray()) {
      // Contents is already an array, so append our new buffer object.
      page_contents.push(extra_contents);
    } else {
      // Contents is not an array, so change it into an array
      // and then append our new buffer object.
      const new_page_contents = pdfDocument.newArray();
      new_page_contents.push(page_contents);
      new_page_contents.push(extra_contents);
      pageObj.put('Contents', new_page_contents);
    }

    const outputBuffer = pdfDocument.saveToBuffer('incremental');
    res(outputBuffer.asUint8Array());
  });
}

效果展示

alt

翻转

export function rotate(pageNumber: number, degrees: number): Promise<Uint8Array{
  return new Promise((res) => {
    const page = doc.loadPage(pageNumber) as mupdf.PDFPage;
    const pageObj = page.getObject();

    const rotate = pageObj.getInheritable('Rotate');
    pageObj.put('Rotate', (rotate as unknown as number) + degrees);

    const outputBuffer = (doc as mupdf.PDFDocument).saveToBuffer('incremental');
    res(outputBuffer.asUint8Array());
  });
}

效果展示

alt

截取

type CropInfo = {
  x: number;
  y: number;
  width: number;
  height: number;
};

export function crop(pageNumber: number, cropInfo: CropInfo): Promise<Uint8Array{
  return new Promise((res) => {
    const { x, y, width, height } = cropInfo;
    const page = doc.loadPage(pageNumber) as mupdf.PDFPage;
    page.setPageBox('CropBox', [x, y, x + width, y + height]);

    const outputBuffer = (doc as mupdf.PDFDocument).saveToBuffer('incremental');
    res(outputBuffer.asUint8Array());
  });
}

效果展示

alt

文档分割

export function split(): Promise<Uint8Array[]> {
  return new Promise((res) => {
    const splitDocuments: Uint8Array[] = [];
    const pdfDocument = doc as mupdf.PDFDocument;

    for (let i = 0; i < pdfDocument.countPages(); i++) {
      const newDoc = new mupdf.PDFDocument();
      newDoc.graftPage(0, pdfDocument, i);
      const buffer = newDoc.saveToBuffer('compress');
      splitDocuments.push(buffer.asUint8Array());
      res(splitDocuments);
    }
  });
}

效果展示

alt

3. PDF 分页展示和文本标注

我们在第二节,展示了如何使用mupdf处理pdf。而现在呢,我们就糅合上面的几种能力来实现一个,PDF分页和文本标注

我们把用到的一些能力放到下面

  1. 获取PDF总页数
  2. PDF转图片
  3. 文本查询

我们的主要逻辑都集中在PdfShow.tsxKeyWordsHight.tsx

PdfShow.tsx

主要代码如下:

import { Pagination, Spin } from 'antd';
import { useEffect, useState } from 'react';
import { getPageCount, getContentByPage, searchKeyWords, SearchResult } from './pdf';
import KeyWordsHight from './KeyWordsHight';
export interface MuFileProps {
  bufferArrayBuffer;
  searchQuery: string;
}
const PdfShow: React.FC<MuFileProps> = (props) => {
  const { buffer, searchQuery } = props;
  const [count, setCount] = useState(0);
  const [loading, setLoading] = useState(false);
  const [page, setPage] = useState(0);
  const [fileContent, setFileContent] = useState('');
  const [keywordsResults, setKeywordsResults] = useState({} as SearchResult);

  useEffect(() => {
    const getFileInfo = async () => {
      setLoading(true);
      const pageCount = await getPageCount(buffer);
      setCount(pageCount);
      setPage(1);
      const pageContent = await getContentByPage(0);
      setFileContent(pageContent);
      setLoading(false);
    };
    buffer && getFileInfo();
  }, [buffer]);

  useEffect(() => {
    const searchKey = async () => {
      const results = await searchKeyWords(searchQuery, 1);
      setKeywordsResults(results);
    };
    if (searchQuery) {
      searchKey();
    }
  }, [searchQuery]);

  
  const onPaginationChange = async (page: number) => {
    setKeywordsResults({} as SearchResult);
    setLoading(true);
    setPage(page);
    const pageContent = await getContentByPage(page - 1);
    setFileContent(pageContent);
    setLoading(false);
  };

  return (
    <Spin spinning={loading}>
      <section style={{ height: '100%', width: '100%' }}>
        <div
          style={{
            position: 'sticky',
            top: 0,
            zIndex: 1,
            backgroundColor: 'white',
            padding: '10px 0px',
          }}
        >

          {count > 0 && (
            <Pagination
              hideOnSinglePage
              defaultCurrent={1}
              defaultPageSize={1}
              total={count}
              showQuickJumper
              showSizeChanger={false}
              onChange={onPaginationChange}
            />

          )}
        </div>
        <KeyWordsHight
          fileContent={fileContent}
          pageNumber={page}
          searchResults={keywordsResults}
        />

      </section>
    </Spin>

  );
};

export default PdfShow;

从代码中,我们可以看出。

PdfShow主要是接收buffer数据,然后通过pdf.ts中的各种方法来初始化页面总数(count),进而构建和分页(Pagination)相关的逻辑,在处理page的过程中,通过fileContent来保存mupdf处理后的信息。

针对searchQuery,我们是通过pdf.ts中的searchKeyWords来找到对应的关键词的位置信息,并存储到keywordsResults中。

searchKeyWords的相关逻辑

export type Box = {
  x: number;
  y: number;
  w: number;
  h: number;
};

export type SearchResult = {
  page?: number;
  results: Box[];
  pageWidth?: number;
  pageHeight?: number;
};

export function searchKeyWords(keywords: string, page: number): Promise<SearchResult{
  return new Promise((res) => {
    const pageContent = doc.loadPage(page);
    const hits = pageContent.search(keywords);
    const result = [];
    for (const hit of hits) {
      for (const quad of hit) {
        const [ulx, uly, urx, ury, llx, lly, lrx, lry] = quad;
        result.push({
          x: ulx,
          y: uly,
          w: urx - ulx,
          h: lly - uly,
        });
      }
    }
    res({ results: result });
  });
}

随后,我们将fileContentkeywordsResults都传人到KeyWordsHight组件中。

KeyWordsHight

import { useEffect, useRef, useState } from 'react';
import { Box, SearchResult } from './pdf';
type PngPageProps = {
  fileContent: string;
  pageNumber: number;
  searchResults?: SearchResult;
};

const KeyWordsHight = ({ fileContent, pageNumber, searchResults }: PngPageProps) => {
  const imgRef = useRef<HTMLImageElement>(null);
  const [boxes, setBoxes] = useState([] as Box[]);

  useEffect(() => {
    if (imgRef.current && searchResults?.results?.length) {
      const { results } = searchResults;
      if (results.length) {
        setBoxes(
          results?.map(
            (res) =>
              ({
                x: res.x,
                y: res.y,
                w: res.w,
                h: res.h,
              }) as Box
          )
        );
      }
    }
  }, [searchResults]);
  if (boxes.length) {
    return (
      <div style={{ position: 'relative', margin: '10px' }}>
        <img ref={imgRef} src={fileContent} />
        <div style={{ margin: '10px' }}>
          {boxes.map(({ x, y, w, h }, key) => (
            <div
              key={key}
              style={{
                left: `${x}px`,
                top: `${y}px`,
                width: `${w}px`,
                height: `${h}px`,
                position: 'absolute',
                backgroundColor: 'yellow',
                opacity: 0.5,
              }}
            />

          ))}
        </div>
      </div>

    );
  }

  return (
    <div key={pageNumber} style={{ position: 'relative' }}>
      <img ref={imgRef} src={fileContent} />
    </div>

  );
};

export default KeyWordsHight;

由于我们将pdf转换成了图片资源(fileContent),然后它可以直接给<img/>

alt

然后,我们基于searchResults是否含有boxes信息,来判定是否有标注信息。

alt

后记

分享是一种态度

全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。

alt
Reference
[1]

leafletjs: https://leafletjs.com/

[2]

pdfjs-dist: https://www.jsdelivr.com/package/npm/pdfjs-dist

[3]

mupdf-js: https://www.npmjs.com/package/mupdf-js

[4]

mupdf: https://mupdf.com/

[5]

f_cli_f: https://www.npmjs.com/package/f_cli_f

[6]

Comlink: https://www.npmjs.com/package/comlink

[7]

mupdf_core API: https://mupdfjs.readthedocs.io/en/latest/how-to-guide/node/document/index.html#core-api

本文由 mdnice 多平台发布

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

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

相关文章

新型PyPI攻击技术可能导致超2.2万软件包被劫持

一种针对 Python 软件包索引&#xff08;PyPI&#xff09;注册表的新型供应链攻击技术已在野外被利用&#xff0c;并且目前正试图渗透到下游组织中。 软件供应链安全公司 JFrog 将其代号定为Revival Hijack&#xff0c;并称这种攻击方法可用于劫持 2.2万个现有 PyPI 软件包&am…

6、LVGL控件-线条、图片、按钮矩阵

本篇文章目录导航 ♠♠ LVGL控件-线条、图片、按钮矩阵 ♣♣♣♣ 一、LVGL 线条部件 ♦♦♦♦♦♦♦♦ 1.1 线条部件组成部分 ♦♦♦♦♦♦♦♦ 1.2 线条部件基本API ♦♦♦♦♦♦♦♦ 1.3 实验小演示 ♣♣♣♣ 二、LVGL 图片部件 ♦♦♦♦♦♦♦♦ 2.1 图片部件组成部分 ♦♦…

前端框架有哪些?

前言 用户体验是每个开发网站的企业中的重中之重。无论后台有多方面的操作和功能&#xff0c;用户的视图和体验都必须是无缝的最友好的。这需要使用前端框架来简化交互式、以用户为中心的网站的开发。 前端框架是一种用于简化Web开发的工具&#xff0c;它提供了一套预定义的代…

基于蜣螂优化最小二乘支持向量机的数据分类预测Matlab程序DBO-LSSVM 多特征输入多类别输出 含基础程序

基于蜣螂优化最小二乘支持向量机的数据分类预测Matlab程序DBO-LSSVM 多特征输入多类别输出 含基础程序 文章目录 一、基本原理DBO&#xff08;Dung Beetle Optimization&#xff09;算法原理LSSVM&#xff08;Least Squares Support Vector Machine&#xff09;模型原理DBO-LSS…

C语言 | Leetcode C语言题解之第388题文件的最长绝对路径

题目&#xff1a; 题解&#xff1a; #define MAX(a, b) ((a) > (b) ? (a) : (b))int lengthLongestPath(char * input){int n strlen(input);int pos 0;int ans 0;int * level (int *)malloc(sizeof(int) * (n 1));memset(level, 0, sizeof(int) * (n 1));while (po…

iOS——Block与内存管理

需要内存管理的情况 1、对象类型的auto变量。 2、引用了 __block 修饰符的变量。 三种block类型 全局类型 &#xff08;NSGlobalBlock&#xff09; 如果一个block里面没有访问普通局部变量(也就是说block里面没有访问任何外部变量或者访问的是静态局部变量或者访问的是全局…

SpringBoot+Vue实现大文件上传(断点续传-后端控制(一))

SpringBootVue实现大文件上传&#xff08;断点续传&#xff09; 1 环境 SpringBoot 3.2.1&#xff0c;Vue 2&#xff0c;ElementUI&#xff0c;spark-md5 2 问题 在前一篇文章&#xff0c;我们写了通过在前端控制的断点续传&#xff0c;但是有两个问题&#xff0c;第一个问题&…

AUTOSAR Adaptive与智能汽车E/E架构发展趋势

AUTOSAR Adaptive是一个面向现代汽车应用需求的标准&#xff0c;特别适用于那些需要高计算能力和灵活性的应用。以下是AUTOSAR Adaptive的典型特性&#xff1a; 高计算能力&#xff1a;AUTOSAR Adaptive支持使用MPU&#xff08;微处理器&#xff09;&#xff0c;这些处理器的性…

嵌入式开发学习路线(25届校招学习) 嵌入式学习路线七年规划:从大一小白到校招大佬 (学习路线汇总)

嵌入式开发学习路线&#xff08;25届校招可以参考&#xff09; 嵌入式系统作为当前最热门且最有发展前途的IT应用领域之一&#xff0c;吸引了大量有志于从事该行业的学习者。为了系统地掌握嵌入式开发技能&#xff0c;以下是一条详细的学习路线&#xff0c;旨在帮助初学者逐步…

CodeSys中动态切换3D模型

文章目录 需求研究结果 需求 在前面的【CodeSys开发3d机械臂显示控件】中&#xff0c;我们已经实现了一个可以显示3d模型的控件。但是这个控件是和使用的3d模型绑定死的&#xff0c;在安装这个控件时就已经将模型文件于控件一起安装到codesys中。 假如我想在不同的工程中&…

智能家居系统(基于STM32F103C8T6标准库+FreeRTOS+Qt串口开发实现)

视频演示&#xff1a;基于STM32F103C8T6标准库FreeRTOSQt串口开发实现的智能家居项目_哔哩哔哩_bilibili 基于STM32F103C8T6标准库FreeRTOSQt串口开发实现的智能家居项目: https://pan.baidu.com/s/1f41gAfOOnlcQoKoMx3o84A?pwd6j2g 提取码: 6j2g 注&#xff1a;本项目为学习完…

Meta关闭Spark AR平台:未来规划与影响分析

Meta宣布将关闭其移动AR创作平台Spark AR&#xff0c;这一消息在业界引起了广泛关注。尽管Snap和TikTok在AR滤镜领域取得了巨大成功&#xff0c;但Meta却选择了另一条发展道路。本文将探讨这一决策背后的可能原因及其对未来的影响。 关闭Spark AR平台的背后 硬件为主&#xff…

计算机网络(三) —— 简单Udp网络程序

目录 一&#xff0c;初始化服务器 1.0 辅助文件 1.1 socket函数 1.2 填充sockaddr结构体 1.3 bind绑定函数 1.4 字符串IP和整数IP的转换 二&#xff0c;运行服务器 2.1 接收 2.2 处理 2.3 返回 三&#xff0c;客户端实现 3.1 UdpClient.cc 实现 3.2 Main.cc 实现 …

【Mysql】系统服务启动访问报错问题处理:this is incompatible with sql_mode=only_full_group_by

一、背景&#xff1a; 本来已经正常运行的平台&#xff0c;突然有一天由于对服务器进行部分操作迁移&#xff0c;发现jar可以正常启动&#xff0c;但是访问功能一直报错&#xff0c;监控后台日志后&#xff0c;发现了问题&#xff1a; 报错的具体信息如下&#xff1a; Caused…

Linux编译器--gcc/g++使用

目录 一、预编译指令 1.1预处理功能 1.2指令 1.3问题扩展 二、编译&#xff08;生成汇编&#xff09; 三、汇编&#xff08;生成二进制机器语言&#xff09; 四、链接&#xff08;生成可执行文件或库文件&#xff09; 4.1库文件 4.2目标文件和库的链接 4.3动态库和静态…

【Django-Minio-Storage 使用教程】

Django-Minio-Storage 使用教程 安装 Django-Minio-Storage配置 Django 项目官方文档 安装 Django-Minio-Storage 使用 pip 安装 Django-Minio-Storage pip install django-minio-storage配置 Django 项目 在 Django 项目的 settings.py 文件中进行以下配置 INSTALLED_APPS…

【mysql】mysql修改sql_mode之后无法启动

现象&#xff1a;修改后mysql无法启动&#xff0c;不报错 原因&#xff1a;MySQL在8以后sql_mode已经取消了NO_AUTO_CREATE_USER这个关键字。去掉这个关键字后&#xff0c;启动就可以了 修改前&#xff1a; sql_modeSTRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR…

Bootstrap前端框架Glyphicons字体图标

115工具网收集提供Bootstrap前端框架Glyphicons字体图标库对照表​​​​​​​&#xff0c;Bootstrap前端UI,Glyphicons字体图标调用,Bootstrap按钮字体图标对照表,包括250多个来自Glyphicon Halflings的字体图标.项目中引用Bootstrap相关文件后即可直接调用下列图标class&quo…

Linux CentOS安装PySpark3.5(单机版)详细教程及机器学习实战

目录 一、安装须知 二、安装Spark 1、下载安装包 2、修改配置文件spark-env.sh 3、验证Spark是否安装成功 三、安装py4j 四、配置环境变量 五、基于PySpark的机器学习实战 1、将数据文件上传HDFS 2、创建代码文件 3、提交应用程序 一、安装须知 前置依赖&#xff1…

Acrobat Pro DC 2023 for Mac/Win:全能型PDF编辑器深度解析

Adobe Acrobat Pro DC 2023作为一款跨平台的PDF编辑器&#xff0c;无论是对于Mac还是Windows用户&#xff0c;都提供了极为全面且强大的PDF处理功能。该软件凭借其卓越的性能和丰富的特性&#xff0c;成为了全球范围内用户处理PDF文档的首选工具。 一、强大的编辑功能 Acroba…