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

news2024/9/20 6:38:57

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

大家好,我是柒八九。一个专注于前端开发技术/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.coloradmin.cn/o/2106860.html

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

相关文章

Chrome 浏览器插件获取网页 window 对象(方案一)

前言 最近有个需求&#xff0c;是在浏览器插件中获取 window 对象下的某个数据&#xff0c;当时觉得很简单&#xff0c;和 document 一样&#xff0c;直接通过嵌入 content_scripts 直接获取&#xff0c;然后使用 sendMessage 发送数据到插件就行了&#xff0c;结果发现不是这…

【unplugin-vue-router】超级实用的自动路由,具体使用教程!

一、安装 vite 创建vue项目的时候选择 auto import 【推荐】 二、使用 原理&#xff1a;参考 vite 官网 来看一个简单的例子&#xff1a; 这是项目的pages目录结构 src/pages/ ├── index.vue ├── about.vue └── users/├── index.vue└── [id].vueunplugin-…

C语言08--指针数组结合

前言&#xff1a; 这次的指针数组结合乃作者呕心沥血之作&#xff0c;大家翻翻进度条就知道了&#xff0c;内容十分干货&#xff0c;各位读者若能全部耐心解析读懂了&#xff0c;想必也能理解掌握C语言中的数组指针这两把利剑了。 指针数组结合&#xff1a; 指针数组 概念&a…

异步编程学习

UniTask UniTask 访问UniTask的GitHub的主页可以直接下载unity Package进行导入&#xff0c;或者通过 Package Manager导入&#xff0c;导入完成之后根据文档加一下 宏定义“UNITASK_DOTWEEN_SUPPORT” 这样就可以正常的控制DoTween了 2.UniTask 的简单使用 // UniTask 是可以作…

lambda表达式用法——C#学习笔记

“Lambda 表达式”是一个匿名函数&#xff0c;它可以包含表达式和语句&#xff0c;并且可用于创建委托或表达式目录树类型。 实例如下&#xff1a; 代码如下&#xff1a; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.…

景联文科技:专业视频标注服务助力计算机视觉应用升级

视频标注是指对视频内容进行分析&#xff0c;并在视频中的特定对象、行为或事件上添加标签的过程。 视频标注包括&#xff1a; 1. 对象检测与跟踪 •对象检测&#xff1a;在每一帧中识别并定位特定的对象&#xff0c;如人、车、动物等。 •对象跟踪&#xff1a;跟踪这些对象…

你知道吗?Python现在这么火爆的真相!

Python之所以如此火爆&#xff0c;主要得益于其多方面的优势和广泛的应用场景。以下是对Python火爆原因的详细归纳&#xff1a; 1. 易学易用 语法简洁&#xff1a;Python的语法设计非常简洁、直观&#xff0c;易于学习和理解。初学者能够快速上手&#xff0c;减少编写代码时的…

解决linux云服务器ping不通另一台linux云服务器的问题

这里以华为云服务器为例 1、找到云主机详情&#xff1a;xxx实例 / 实例概览 / 服务器详情&#xff0c;找到安全组 2、找到云主机当前在使用的安全组&#xff0c;添加入向规则 注意这里要选择 ICMP 协议&#xff0c;因为 ping 的协议是基于 ICMP 协议工作的 3、再次ping即可通 …

多目标应用:四种多目标优化算法(NSGA2、NSPSO、NSDBO、NSCOA)求解柔性作业车间调度问题(FJSP),MATLAB代码

一、柔性作业车间调度问题 柔性作业车间调度问题(Flexible Job Scheduling Problem, FJSP) 的描述如下&#xff1a;n个工件 { J , J 2 , . . , J n } \{J,J_2,..,J_n\} {J,J2​,..,Jn​}要在 m m m 台机器 { M 1 , M 2 , . . , M m } \{M_1,M_2,..,M_m\} {M1​,M2​,..,Mm​} …

linux基础IO——动静态库——实现与应用学习、原理深度解析

前言&#xff1a;本节内容是基础IO部分的动静态库。 本节内容&#xff0c; 我们将站在实现者的角度上自己实现一下动静态库&#xff0c; 并且会站在使用者的角度上使用我们自己实现的库。过程中牵扯到许多新的知识&#xff0c; 最后我们会重谈一下我们的进程。 理解一下有了动静…

【Python基础】想学好Python,就必须要知道的Python知识。一篇文章带你了解Python,学好Python!!!

Python知识涵盖面非常广泛&#xff0c;从基础语法到高级特性&#xff0c;再到丰富的库和框架&#xff0c;都是Python学习的重要组成部分。 一、基础语法 变量与数据类型&#xff1a; Python是动态类型语言&#xff0c;变量无需声明类型&#xff0c;直接赋值即可。常见的数据类…

织梦dedecms后台文章列表显示空白或有页码显示不了文章的解决方案

cms系统在用的过程才会发现更多的问题&#xff0c;dedecms也不例外。 问题描述&#xff1a; 可以正常登录&#xff0c;列表正常显示&#xff0c;文章页码也是正常&#xff0c;就是无法显示文章的标题。 分析原因&#xff1a; 1、是否改动源码 2、数据库是否完整 在数据库是…

非标独立设计选型--二十一--滚子链选型计算

链传动 相比于带传动 1、噪音大、震动大---平稳性不加、精度不够 运行速度不要太快…… 2、负载能力强&#xff0c;抗造---大负载、线性传动---抗冲击 【工况1】负载较大&#xff08;几百kg---几吨&#xff09;、运行速度较缓慢的场合 3、预紧力不需要像同步带那样大…

OpenAI 的发展启示录

OpenAI 的发展启示录 前言OpenAI 的发展启示录 前言 在当今科技迅猛发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;正以前所未有的速度改变着我们的生活和工作方式。OpenAI 作为人工智能领域的先驱者&#xff0c;其发展路径和成就备受关注。它的每一次突破和创新&…

信也持续构建集群容器化改造之路

1. 前言 随着应用构建需求增加以及新构建场景引入&#xff0c;公司对构建系统的扩展性、稳定性要求日益提高。多语言构建&#xff08;如Golang、Java、Python、Node.js 等&#xff09;所依赖的环境&#xff0c;部署在同一台物理机上时&#xff0c;使构建机环境维护困难&#xf…

Elasticsearch Mapping 详解

1 概述 映射的基本概念 Mapping 也称之为映射&#xff0c;定义了 ES 的索引结构、字段类型、分词器等属性&#xff0c;是索引必不可少的组成部分。 ES 中的 mapping 有点类似与DB中“表结构”的概念&#xff0c;在 MySQL 中&#xff0c;表结构里包含了字段名称&#xff0c;字…

【gtokentool】元宇宙nft区块链是什么

元宇宙 元宇宙的定义 元宇宙&#xff08;Metaverse&#xff09;这个词起源于Neal Stephenson在1992年出版的小说《雪崩》&#xff0c;Metaverse由Meta&#xff08;意即“超越”、“元”&#xff09;和verse&#xff08;意即“宇宙universe”&#xff09;两个词构成。元宇宙是整…

安卓13带有系统签名的应用不能正常使用webview 调用webview失败 系统应用app apk

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.彩蛋1.前言 android版本高一些的平台,经常会遇到一些权限安全问题,像客户的应用如果带有系统签名,会导致不能正常使用webview问题。 2.问题分析 我们log信息,可以发现下面的提示: Fo…

权威解读|2024固定网国内数据传送业务办理指南

一、固定网国内数据传送业务是什么&#xff1f; 固定网国内数据传送业务&#xff0c;是指互联网数据传送业务以外的&#xff0c;在固定网中以有线方式提供的国内端到端数据传送业务。主要包括基于IP承载网、ATM网、X.25分组交换网、DDN网、帧中继网络的数据传送业务等。 根据…

佰朔资本:股票的买卖点有哪些?如何判断?

1、根据均线找买卖点 当股价跌破5日均线时&#xff0c;投资者可以将其作为卖点&#xff1b;股价向下跌触碰5日均线之后&#xff0c;出现反弹向上运转的痕迹&#xff0c;投资者可以将其作为买点。 2、根据MACD方针和KDJ方针找买卖点 当MACD方针或许KDJ 方针出现高位死叉时&am…