【第3章 文本】3.3 文本的定位

article/2025/8/26 10:13:19

文章目录

  • 水平与垂直定位
    • 示例
    • textAlign
    • textBaseline
  • 将文本居中
  • 文本的度量
  • 绘制坐标轴旁边的文本标签
  • 在圆弧周围绘制文本


水平与垂直定位

在canvas中使用 strokeText() 或 fillText() 绘制文本时,需要指定所绘文本的 X 和 Y 的坐标,然而,浏览器具体将文本绘制在何处,则要看 textAlign 和 textBaseline 这两个属性。

属性描述
textAlign决定文本在水平方向上的对齐方式。start、left、center、right、end,默认值start
textBaseline决定文本在垂直方向上的对齐方式。top、bottom、middle、alphabetic、ideographic、hanging,默认值alphabetic

示例

应用程序中的实心矩形表示传递给 fillText() 方法的 X 与 Y 坐标,显示的每个字符串都表示了一种 textAlign 与 textBaseline 属性值的组合。

在这里插入图片描述

<!DOCTYPE html>
<html lang="zh-CN"><head><!-- 设置文档字符编码为UTF-8 --><meta charset="UTF-8" /><title>3-4-利用textAlign与textBaseline属性来定位文本</title><style>/* 页面背景色 */body {background: #eeeeee;}/* 画布样式设置 */#canvas {background: #ffffff;margin-top: 5px;margin-left: 10px;/* 兼容不同浏览器的阴影效果 */-webkit-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);-moz-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);/* 边框样式 */border: 1px solid rgba(0, 0, 0, 0.2);}</style></head><body><!-- 画布元素,不支持时显示提示 --><canvas id="canvas" width="780" height="440">canvas not supports</canvas><script>// 使用严格模式'use strict'// 获取画布和绘图上下文let canvas = document.getElementById('canvas'),context = canvas.getContext('2d'),// 字体高度fontHeight = 24,// 文本水平对齐方式数组alignValues = ['start', 'center', 'end'],// 文本垂直对齐基线数组baseLineValue = ['top', 'middle', 'bottom', 'alphabetic', 'ideographic', 'hanging'],// 文本坐标x,y// 函数定义.../*** 绘制网格线* @param {string} color - 网格线颜色* @param {number} stepx - 水平间距* @param {number} stepy - 垂直间距*/let drawGrid = (color, stepx, stepy) => {context.save()// 设置网格样式context.strokeStyle = colorcontext.fillStyle = '#ffffff'context.lineWidth = 0.5// 填充白色背景context.fillRect(0, 0, context.canvas.width, context.canvas.height)// 绘制垂直网格线for (let i = stepx + 0.5; i < context.canvas.width; i += stepx) {context.beginPath()context.moveTo(i, 0)context.lineTo(i, context.canvas.height)context.stroke()}// 绘制水平网格线for (let i = stepy + 0.5; i < context.canvas.height; i += stepy) {context.beginPath()context.moveTo(0, i)context.lineTo(context.canvas.width, i)context.stroke()}context.restore()}/*** 在(x,y)位置绘制标记点*/let drawTextMarker = () => {context.fillStyle = 'yellow'// 绘制黄色小方块context.fillRect(x, y, 7, 7)// 绘制小方块边框context.strokeRect(x, y, 7, 7)}/*** 绘制文本* @param {string} text - 要绘制的文本* @param {string} textAlign - 水平对齐方式* @param {string} textBaseLine - 垂直基线对齐方式*/let drawText = (text, textAlign, textBaseLine) => {// 设置文本对齐方式if (textAlign) context.textAlign = textAlignif (textBaseLine) context.textBaseline = textBaseLine// 绘制蓝色文本context.fillStyle = 'cornflowerblue'context.fillText(text, x, y)}/*** 绘制文本参考线*/let drawTextLine = () => {context.strokeStyle = 'gray'// 绘制水平参考线context.beginPath()context.moveTo(x, y)context.lineTo(x + 738, y)context.stroke()}// 初始化...// 设置字体样式context.font = 'oblique normal bold 24px palatino'// 绘制网格drawGrid('lightgray', 10, 10)// 遍历所有对齐组合for (let align = 0; align < alignValues.length; ++align) {for (let baseline = 0; baseline < baseLineValue.length; baseline++) {// 计算文本位置x = 20 + align * fontHeight * 15y = 20 + baseline * fontHeight * 3// 绘制文本和标记drawText(alignValues[align] + '/' + baseLineValue[baseline], alignValues[align], baseLineValue[baseline])drawTextMarker()drawTextLine()}}</script></body>
</html>

textAlign

textAlign 属性默认值是 start,它对应了基于 X 的位置,类似于“向初始看齐”、“向中间看齐”、“向结尾看齐”。这里如果当 canvas 元素的 dir 属性值是ltr时,也就是说浏览器是按照从左至右的方向来显示文本时,left效果与start相同,right 效果与 end 相同。同理,如果 dir 属性值是 rtl,浏览器从右至左来显示文本,那么 right 效果与 start 相同,left 效果与 end 一致。图示应用程序是从左至右来显示文本的。

在这里插入图片描述

textBaseline

textBaseline 的属性默认值是 alphabetic,改值用于绘制由基于拉丁字母的语言所组成的字符串。ideographic 值则用于绘制中文字符串或日文。hanging 绘制各种印度语字符串。top、bottom、middle与特定语言不相关,代表文本周围的边界框之内的某个位置,这个边界框也叫做“字符方框”(em square)

在这里插入图片描述


将文本居中

下面应用程序分别将 textAlign 与 textBaseline 属性的值设置为 center 与 middle,然后对文本进行描边与填充。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>3-5-将文本居中与某个点</title><style>#canvas {background: #eee;}</style></head><body><canvas id="canvas" width="600" height="400"> canvas not supported </canvas><script>const canvas = document.getElementById('canvas'),context = canvas.getContext('2d')context.textAlign = 'center'context.textBaseline = 'middle' // 文本基线,默认是alphabetic,即文本的基线是字母的基线context.font = '64px Arial' // 字体大小和字体,必须在设置textAligcontext.fillStyle = 'blue'context.strokeStyle = 'yellow'context.fillText('Centered', canvas.width / 2, canvas.height / 2)context.strokeText('Centered', canvas.width / 2, canvas.height / 2)</script></body>
</html>

文本的度量

只要你做的事情与文本有关,你就得设法获取某个字符串的宽度和高度,Canvas 绘图环境对象提供了一个名为 measureText() 的方法,用以度量某个字符串的宽度。该方法所返回的 TextMetrics 对象中包含了一个名为 width 的属性,这个属性就是字符串的宽度。

警告:在调用 measureText() 方法之前先设置好字型
在使用measureText()方法时,常见的错误就是在调用完该方法之后,才去设置字型。因为measureText()方法是基于当前字型来计算字符串宽度的,因此调用方法后采取改变字型,所返回的宽度不能反映出那种字型来度量的是实际文本宽度。

在这里插入图片描述

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><title>3-6-计算文本行的宽度</title><style>body {background: #eeeeee;}#canvas {background: #ffffff;margin-top: 5px;margin-left: 10px;-webkit-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);-moz-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);border: 1px solid rgba(0, 0, 0, 0.2);}</style></head><body><canvas id="canvas">canvas not supports</canvas><p>width展示的是在Canvas画布上的字符串'Hello'的宽度</p><script>'use strict'let canvas = document.getElementById('canvas'),context = canvas.getContext('2d')context.font = '24px Arial'let word = 'Hello'context.fillText(word, 10, 50)context.fillText('width:' + context.measureText(word).width, 10, 100)</script></body>
</html>

绘制坐标轴旁边的文本标签

绘制垂直于水平坐标轴的文本标签是非常简单的。应用程序根据坐标轴的位置、坐标轴上每条刻度线的长度,以及坐标轴于文本标签之间的距离,来决定每个文本标签的绘制坐标。

在这里插入图片描述

请注意,应用程序会设置textAlign和textBaseline的值,在绘制水平坐标轴上文本标签分别设置”center“和”top“,在绘制垂直坐标轴时,则设置“right”和”middle“。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><title>3-7-为坐标轴加上文本标签</title></head><body><!-- 创建画布元素 --><canvas id="canvas" width="1200" height="600"></canvas></body><script>// 获取画布和绘图上下文let canvas = document.getElementById('canvas'),context = canvas.getContext('2d'),// 坐标轴外边距AXIS_MARGIN = 40.5,// 坐标原点位置(左下角)AXIS_ORIGIN = { x: AXIS_MARGIN, y: canvas.height - AXIS_MARGIN },// y轴顶点位置AXIS_TOP = AXIS_MARGIN,// x轴顶点位置AXIS_RIGHT = canvas.width - AXIS_MARGIN,// 横向刻度线间距HORIZONTAL_TICK_SPACING = 10,// 垂直刻度线间距VERTICAL_TICK_SPACING = 10,// X轴长度AXIS_WIDTH = AXIS_RIGHT - AXIS_ORIGIN.x,// y轴长度AXIS_HEIGHT = AXIS_ORIGIN.y - AXIS_TOP,// y轴刻度数量NUM_VERTICAL_TICKS = AXIS_HEIGHT / VERTICAL_TICK_SPACING,// x轴刻度数量NUM_HORIZONTAL_TICKS = AXIS_WIDTH / HORIZONTAL_TICK_SPACING,// 刻度线宽度TICK_WIDTH = 10,// 刻度线线宽TICKS_LINEWIDTH = 0.5,// 刻度线颜色TICK_COLOR = 'navy',// 坐标轴线宽AXIS_LINEWIDTH = 1.0,// 坐标轴颜色AXIS_COLOR = 'blue'// 标签与坐标轴的间距SPACE_BETWEEN_LABELS_AND_AXIS = 15/*** 绘制背景网格线* @param {string} color - 网格线颜色* @param {number} stepX - 水平间距* @param {number} stepY - 垂直间距*/let drawGrid = (color, stepX, stepY) => {context.strokeStyle = colorcontext.lineWidth = 0.5// 绘制垂直网格线for (let i = stepX + 0.5; i < context.canvas.width; i += stepX) {context.beginPath()context.moveTo(i, 0)context.lineTo(i, context.canvas.height)context.stroke()}// 绘制水平网格线for (let i = stepY + 0.5; i < context.canvas.height; i += stepY) {context.beginPath()context.moveTo(0, i)context.lineTo(context.canvas.width, i)context.stroke()}}/*** 绘制坐标轴(包含x轴和y轴)*/let drawAxis = () => {context.save()context.strokeStyle = AXIS_COLORcontext.lineWidth = AXIS_LINEWIDTH// 绘制x轴和y轴drawHorizontalAxis()drawVerticalAxis()// 设置刻度线样式context.lineWidth = TICKS_LINEWIDTHcontext.strokeStyle = TICK_COLOR// 绘制刻度线drawVerticalAxisTicks()drawHorizontalAxisTicks()context.restore()}/*** 绘制x轴*/let drawHorizontalAxis = () => {context.beginPath()context.moveTo(AXIS_ORIGIN.x, AXIS_ORIGIN.y)context.lineTo(AXIS_RIGHT, AXIS_ORIGIN.y)context.stroke()}/*** 绘制y轴*/let drawVerticalAxis = () => {context.beginPath()context.moveTo(AXIS_ORIGIN.x, AXIS_ORIGIN.y)context.lineTo(AXIS_ORIGIN.x, AXIS_TOP)context.stroke()}/*** 绘制y轴刻度线*/let drawVerticalAxisTicks = () => {let deltaYfor (let i = 1; i < NUM_VERTICAL_TICKS; i++) {context.beginPath()// 每5个刻度画一个长刻度线deltaY = i % 5 === 0 ? TICK_WIDTH : TICK_WIDTH / 2context.moveTo(AXIS_ORIGIN.x - deltaY, AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING)context.lineTo(AXIS_ORIGIN.x + deltaY, AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING)context.stroke()}}/*** 绘制x轴刻度线*/let drawHorizontalAxisTicks = () => {let deltaYfor (let i = 1; i < NUM_HORIZONTAL_TICKS; i++) {context.beginPath()// 每5个刻度画一个长刻度线deltaY = i % 5 === 0 ? TICK_WIDTH : TICK_WIDTH / 2context.moveTo(AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING, AXIS_ORIGIN.y - deltaY)context.lineTo(AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING, AXIS_ORIGIN.y + deltaY)context.stroke()}}/*** 绘制坐标轴标签*/let drawAxisLabels = () => {context.fillStyle = 'blue'drawHorizontalAxisLabels()drawVerticalAxisLabels()}/*** 绘制x轴标签*/let drawHorizontalAxisLabels = () => {context.textAlign = 'center'context.textBaseline = 'top'// 每5个刻度添加一个标签for (let i = 0; i <= NUM_HORIZONTAL_TICKS; ++i) {if (i % 5 === 0) {context.fillText(i,AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING,AXIS_ORIGIN.y + SPACE_BETWEEN_LABELS_AND_AXIS)}}}/*** 绘制y轴标签*/let drawVerticalAxisLabels = () => {context.textAlign = 'right'context.textBaseline = 'middle'// 每5个刻度添加一个标签for (let i = 0; i <= NUM_VERTICAL_TICKS; ++i) {if (i % 5 === 0) {context.fillText(i, AXIS_ORIGIN.x - SPACE_BETWEEN_LABELS_AND_AXIS, AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING)}}}// 初始化绘制drawGrid('lightgray', 10, 10) // 绘制背景网格drawAxis() // 绘制坐标轴drawAxisLabels() // 绘制坐标轴标签</script>
</html>

在圆弧周围绘制文本

应用程序通过如下步骤来绘制文本:

  1. 计算圆弧周围每个字符的绘制坐标
  2. 将坐标系平移至绘制字符的地点
  3. 将坐标系旋转 π/2 - angle 度
  4. 对字符进行描边或填充操作

在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>3-9-沿着圆弧绘制文本</title></head><body><canvas id="canvas" width="800" height="600"> canvas not supports </canvas><script>const canvas = document.getElementById('canvas'),context = canvas.getContext('2d')const TEXT_FILL_STYLE = 'rgba(100, 130, 240, 0.5)',TEXT_STROKE_STYLE = 'rgba(255, 255, 255, 0.8)',TEXT_SIZE = 64const circle = {x: canvas.width / 2,y: canvas.height / 2,radius: 200,}// Functions ......const drawCircleCenterPoint = () => {context.save()context.beginPath()context.arc(circle.x, circle.y, 4, 0, Math.PI * 2, false)context.fillStyle = 'red'context.fill()context.restore()}/*** 1. 计算圆弧周围每个字符的绘制坐标* 2. 将坐标轴平移至绘制字符的地点* 3. 旋转坐标轴,使字符绘制方向与圆弧方向一致 π/2 - angle* 4. 对字符进行描边或填充操作* */const drawCircleText = (text, startAngle, endAngle) => {const radius = circle.radius,angleDecrement = (startAngle - endAngle) / (text.length - 1)let character,angle = parseFloat(startAngle),index = 0context.save()context.fillStyle = TEXT_FILL_STYLEcontext.strokeStyle = TEXT_STROKE_STYLEcontext.font = `${TEXT_SIZE}px Arial`while (index < text.length) {character = text[index]context.save()context.beginPath()context.translate(circle.x + Math.cos(angle) * radius, circle.y - Math.sin(angle) * radius)context.rotate(Math.PI / 2 - angle)context.fillText(character, 0, 0)context.strokeText(character, 0, 0)angle -= angleDecrementindex++context.restore()}context.restore()}// Initialization......drawCircleCenterPoint()context.textAlign = 'center'context.textBaseline = 'middle'drawCircleText('Clockwise around the circle', Math.PI * 2, Math.PI / 8)</script></body>
</html>

http://www.hkcw.cn/article/NlYacNlmRv.shtml

相关文章

C++哈希

一.哈希概念 哈希又叫做散列。本质就是通过哈希函数把关键字key和存储位置建立映射关系&#xff0c;查找时通过这个哈希函数计算出key存储的位置&#xff0c;进行快速查找。 上述概念可能不那么好懂&#xff0c;下面的例子可以辅助我们理解。 无论是数组还是链表&#xff0c;查…

Java中的设计模式实战:单例、工厂、策略模式的最佳实践

Java中的设计模式实战&#xff1a;单例、工厂、策略模式的最佳实践 在Java开发中&#xff0c;设计模式是构建高效、可维护、可扩展应用程序的关键。本文将深入探讨三种常见且实用的设计模式&#xff1a;单例模式、工厂模式和策略模式&#xff0c;并通过详细代码实例&#xff0…

QT6搭建和使用MQTT

QT6搭建和使用MQTT 1.搭建MQTT环境1.下载源码2.CMake 编译 Qt MQTT 模块3.添加QT MQTT模块4.验证测试 2.MQTT的使用 1.搭建MQTT环境 1.下载源码 1.在GitHub下载对应qt版本的源码 git clone git://code.qt.io/qt/qtmqtt.git -b 6.5.3 这里以6.5.3版本的为例。 这里使用的是VS…

深入了解 C# 异步编程库 AsyncEx

在现代应用程序开发中&#xff0c;异步编程已经成为提升性能和响应能力的关键&#xff0c;尤其在处理网络请求、I/O 操作和其他耗时任务时&#xff0c;异步编程可以有效避免阻塞主线程&#xff0c;提升程序的响应速度和并发处理能力。C# 提供了内建的异步编程支持&#xff08;通…

使用 Azure DevOps 管道部署到本地服务器

Azure DevOps 是一个帮助改进 SDLC(软件开发生命周期)的平台。 在本文中,我们将使用 Azure Pipelines 创建自动化部署。 Azure DevOps 团队将 Azure Pipelines 定义为“使用 CI/CD 构建、测试和部署,适用于任何语言、平台和云平台”。 在这里,我将解释如何在 Azure Dev…

NSSCTF-[青海民族大学 2025 新生赛]wenshilou

下载附件得到jpeg图片 放到kali里面用binwalk命令进行分离 分离之后得到文件 点击zip文件里面有个flag&#xff0c;打开得到base64编码 直接放到随波逐流里面解码 得到flag NSSCTF{welcometoQinhaiminzudaxue}

React 编译器

&#x1f916; 作者简介&#xff1a;水煮白菜王&#xff0c;一位前端劝退师 &#x1f47b; &#x1f440; 文章专栏&#xff1a; 前端专栏 &#xff0c;记录一下平时在博客写作中&#xff0c;总结出的一些开发技巧和知识归纳总结✍。 感谢支持&#x1f495;&#x1f495;&#…

【机器学习基础】机器学习入门核心算法:K均值(K-Means)

机器学习入门核心算法&#xff1a;K均值&#xff08;K-Means&#xff09; 1. 算法逻辑2. 算法原理与数学推导2.1 目标函数2.2 数学推导2.3 时间复杂度 3. 模型评估内部评估指标外部评估指标&#xff08;需真实标签&#xff09; 4. 应用案例4.1 客户细分4.2 图像压缩4.3 文档聚类…

力扣热题100之二叉树的最大深度

题目 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 代码 方法一&#xff1a;递归 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightN…

【C++编程】C++学习笔记【更新ing】

C学习笔记 作者&#xff1a;齐花Guyc(CAUC) 文章目录 C学习笔记Chapter.1 面向对象编程&#xff08;OOP&#xff09;1.类&#xff08;class&#xff09;2.对象&#xff08;object&#xff09;3.封装&#xff08;Encapsulation&#xff09;4.继承&#xff08;Inheritance&#…

华为OD机试真题——矩形相交的面积(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现

2025 A卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C++、GO六种语言的最佳实现方式; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析; 本文收录于专栏:《2025华为OD真题目录+全流程解析+备考攻略+经验分…

STM32F407VET6学习笔记7:Bootloader跳转APP程序

boot跳转APP的程序 目录 Flash分区设定&#xff1a; 工程文件地址设置&#xff1a; Bootloader工程文件&#xff1a; 测试的APP程序工程文件&#xff1a; Bootloader跳转程序&#xff1a; APP程序&#xff1a; Flash分区设定&#xff1a; 参考手册的分区&#xff1a; 工程文件…

5.29 打卡

DAY 39 图像数据与显存 知识点回顾 图像数据的格式&#xff1a;灰度和彩色数据模型的定义显存占用的4种地方 模型参数梯度参数优化器参数数据批量所占显存神经元输出中间状态 batchisize和训练的关系 作业&#xff1a;今日代码较少&#xff0c;理解内容即可 # 打印一张彩色图像…

关于scrapy在pycharm中run可以运行,但是debug不行的问题

关于scrapy在pycharm中run模式可以运行&#xff0c;但是debug模式不行的问题 文章目录 关于scrapy在pycharm中run模式可以运行&#xff0c;但是debug模式不行的问题查了下原因 点击run就可以运行&#xff0c;但是debug就是运行不了 一点击debug就报这个错&#xff0c;也不知道啥…

第7讲、Odoo 18 源码深度分析

Odoo 作为全球知名的开源 ERP 系统&#xff0c;其底层架构由众多核心 Python 文件共同支撑。本文将围绕 Odoo 18 版本中 的 api.py、exceptions.py、fields.py、http.py、loglevels.py、models.py、netsvc.py、release.py、sql_db.py 等关键文件&#xff0c;进行源码结构与实现…

【春秋云镜】CVE-2022-26965 靶场writeup

知识点 网站的主题或者模块位置一般是可以上传文件的&#xff0c;不过一般为压缩包形式主题或者模块可以上github上找到和cms匹配的源码主题被解压后会放到加入到对应的文件夹中&#xff0c;而且还会自动执行对应的info.php文件(需要主题和cms配套才行)我这里取巧了&#xff0…

JUC多线程核心知识点深度解析

最近正在复习Java八股&#xff0c;所以会将一些热门的八股问题&#xff0c;结合ai与自身理解写成博客便于记忆 本文将从以上10个经典面试问题来做juc多线程的解析 一、线程状态与流转机制 1. 六种线程状态&#xff08;Java定义&#xff09; public enum State {NEW, …

设计模式学习笔记

设计模式 一&#xff1a;分类&#xff1a; 创建型模式 用于描述“怎样创建对象”&#xff0c;它的主要特点是“将对象的创建与使用分离”。GoF&#xff08;四人组&#xff09;书中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。 结构型模式 用于描述如何将…

【地图】腾讯地图页面卡顿问题解决

目录 背景问题排查解决1. 页面是否使用 keep-alive 进行路由缓存2. 离开地图页面时&#xff0c;是否将地图清除 总结 背景 有的电脑没有显卡会出现如下问题&#xff1a; 系统打开有地图的页面&#xff0c;CPU 占用直线飙升到100%下不来&#xff0c;切到非地图页面&#xff0c;C…

一起看 I/O | Android 性能相关最新动态

作者 / Ben Weiss 过去几年来&#xff0c;我们一直致力于让性能提升工作变得更易上手、回报更高。我们将在本文中分享这一领域的最新发展动态。为您介绍基准配置文件、Android Studio 中的工具改进、库&#xff0c;以及我们如何让这项技术更好地在后台为您服务。此外&#xff0…