字玩FontPlayer开发笔记8 Tauri2文件系统
字玩FontPlayer开发笔记8 Tauri2文件系统
字玩FontPlayer是笔者开源的一款字体设计工具,使用Vue3 + ElementUI开发,源代码:
github: https://github.com/HiToysMaker/fontplayer
gitee: https://gitee.com/toysmaker/fontplayer
笔记
字玩目前是用Electron进行桌面端应用打包,但是性能体验不太好,一直想替换成Tauri。Tauri的功能和Electron类似,都可以把前端代码打包生成桌面端(比如Windows和Mac)应用。Tauri只使用系统提供的WebView,不像Electron一样内置Chromium和Node.js,性能体验更佳。
近几天开始着手将Electron替换成Tauri,前两天完成了系统原生菜单的基本设置,今天将菜单功能实装。笔者项目中菜单功能最常用的就是文件存储和读取,所以今天主要学习了文件系统的内容。Tauri2提供了js端可调用的plugin,可以方便前端轻松实现文件操作。
权限配置
文件操作需要进行权限配置,Tauri2去掉了tauri.conf.json中的allowList一项,变成在src-tauri/capabilities/default.json中进行权限设置。
具体权限对应的选项在文档中Permission Table一栏中有详述:https://tauri.app/plugin/file-system/
笔者的配置:
src-tauri/capabilities/default.json
{"$schema": "../gen/schemas/desktop-schema.json","identifier": "default","description": "enables the default permissions","windows": ["main"],"permissions": [{"identifier": "fs:scope","allow": [{"path": "$APPDATA"},{"path": "$APPDATA/**"}]},"core:default","fs:read-files","fs:write-files","fs:allow-appdata-read-recursive","fs:allow-appdata-write-recursive","fs:default","dialog:default"]
}
文件选择对话框
文件选择对话框的插件和文件操作的插件是分开的,首先安装对话框插件:
npm run tauri add dialog
打开文件选择窗口:
import { open } from '@tauri-apps/plugin-dialog'const file = await open({multiple: false,directory: false,
})
打开文件存储窗口:
import { save } from '@tauri-apps/plugin-dialog'const path = await save({defaultPath: 'untitled',filters: [{name: 'My Filter',extensions: ['png', 'jpeg'],},],
});
文件操作
文件操作封装在fs插件中,插件分别提供对纯文本读写和二进制读写的方法。
安装插件:
npm run tauri add fs
读取纯文本:
import { readTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'const configToml = await readTextFile('config.toml', {baseDir: BaseDirectory.AppConfig,
})
读取二进制文本:
const icon = await readFile('icon.png', {baseDir: BaseDirectory.Resources,
})
写入纯文本:
import { writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'const contents = JSON.stringify({ notifications: true });
await writeTextFile('config.json', contents, {baseDir: BaseDirectory.AppConfig,
});
写入二进制:
import { writeFile, BaseDirectory } from '@tauri-apps/plugin-fs'const contents = new Uint8Array();
await writeFile('config', contents, {baseDir: BaseDirectory.AppConfig,
});
涉及文件操作的具体逻辑实现
保存文本文件
const nativeSaveText = async (data, filename, formats) => {const path = await save({defaultPath: filename,filters: [{name: 'Filter',extensions: formats,},],})if (path) {await writeTextFile(path, data)}
}
保存二进制文件
const nativeSaveBinary = async (data, filename, formats) => {const path = await save({defaultPath: filename,filters: [{name: 'Filter',extensions: formats,},],})if (path) {await writeFile(path, data)}
}
打开文本文件
const nativeImportTextFile = async (formats) => {const path = await open({filters: [{name: 'Filter',extensions: formats,},],})let data = nulllet name = 'untitled'if (path) {data = await readTextFile(path)name = path.split('/').pop().split('.')[0]}return {data,name,}
}
打开二进制文件
const nativeImportFile = async (formats) => {const path = await open({filters: [{name: 'Filter',extensions: formats,},],})let uint8Array = nulllet name = 'untitled'if (path) {uint8Array = await readFile(path)name = path.split('/').pop().split('.')[0]}return {uint8Array,name,}
}
打开工程
const openFile_tauri = async (rawdata) => {if (files.value && files.value.length) {tips.value = '目前字玩仅支持同时编辑一个工程,请关闭当前工程再打开新工程。注意,关闭工程前请保存工程以避免数据丢失。'tipsDialogVisible.value = true} else {const { data } = await nativeImportTextFile(['json'])await _openFile_electron(data)}
}
保存工程
const saveFile_tauri = async () => {setSaveDialogVisible(true)
}
另存为工程
const saveAs_tauri = async () => {setSaveDialogVisible(true)
}
导入字体库
const importFont_tauri = async () => {if (files.value && files.value.length) {tips.value = '目前字玩仅支持同时编辑一个工程,请关闭当前工程再导入字体。注意,关闭工程前请保存工程以避免数据丢失。'tipsDialogVisible.value = true} else {const options = await nativeImportFile(['otf', 'ttf'])await _importFont_tauri(options)}
}
导入字形
const importGlyphs_tauri = async () => {const { data: rawdata } = await nativeImportTextFile(['json'])if (!rawdata) returnconst data = JSON.parse(rawdata)const plainGlyphs = data.glyphsif (data.constants) {for (let n = 0; n < data.constants.length; n++) {if (!constantsMap.getByUUID(data.constants[n].uuid)) {constants.value.push(data.constants[n])}}}if (data.constantGlyphMap) {const keys = Object.keys(data.constantGlyphMap)for (let n = 0; n < keys.length; n++) {constantGlyphMap.set(keys[n], data.constantGlyphMap[keys[n]])}}const _glyphs = plainGlyphs.map((plainGlyph) => instanceGlyph(plainGlyph))_glyphs.map((glyph) => {addGlyph(glyph, editStatus.value)addGlyphTemplate(glyph, editStatus.value)})if (editStatus.value === Status.GlyphList) {emitter.emit('renderGlyphPreviewCanvas')} else if (editStatus.value === Status.StrokeGlyphList) {emitter.emit('renderStrokeGlyphPreviewCanvas')} else if (editStatus.value === Status.RadicalGlyphList) {emitter.emit('renderRadicalGlyphPreviewCanvas')} else if (editStatus.value === Status.CompGlyphList) {emitter.emit('renderCompGlyphPreviewCanvas')}
}
导入SVG
const importSVG_tauri = async () => {const { data: rawdata } = await nativeImportTextFile(['svg'])if (!rawdata) returnconst svgEl: HTMLElement = parseStrToSvg(rawdata).childNodes[0] as HTMLElementconst components = parseSvgToComponents(svgEl as HTMLElement)components.forEach((component: IComponent) => {addComponentForCurrentCharacterFile(component)})
}
识别图片
const importPic_tauri = async () => {const options = await nativeImportFile(['jpg', 'png', 'jpeg'])const { name, uint8Array } = optionslet binary = ''uint8Array.forEach((byte) => {binary += String.fromCharCode(byte);})const base64str = btoa(binary)const type = name.split('.')[1] === 'png' ? 'imge/png' : 'image/jpeg'const dataUrl = `data:${type};base64,${base64str}`total.value = 0loaded.value = 0loading.value = trueconst img = document.createElement('img')img.onload = () => {setTimeout(() => {thumbnail(dataUrl, img, 1000)setEditStatus(Status.Pic)loading.value = false}, 100)}img.src = dataUrl
}
导出字体库
const exportFont_tauri = async (options: CreateFontOptions) => {const font = createFont(options)const buffer = toArrayBuffer(font) as ArrayBufferconst filename = `${selectedFile.value.name}.otf`nativeSaveBinary(buffer, filename, ['otf'])
}
导出字形
const exportGlyphs_tauri = async () => {if (editStatus.value === Status.GlyphList) {const _glyphs = glyphs.value.map((glyph: ICustomGlyph) => {return plainGlyph(glyph)})const data = JSON.stringify({glyphs: _glyphs,constants: constants.value,constantGlyphMap: mapToObject(constantGlyphMap),version: 1.0,})await nativeSaveText(data, `glyphs.json`, ['json'])} else if (editStatus.value === Status.StrokeGlyphList) {const _glyphs = stroke_glyphs.value.map((glyph: ICustomGlyph) => {return plainGlyph(glyph)})const data = JSON.stringify({glyphs: _glyphs,constants: constants.value,constantGlyphMap: mapToObject(constantGlyphMap),version: 1.0,})await nativeSaveText(data, `stroke_glyphs.json`, ['json'])} else if (editStatus.value === Status.RadicalGlyphList) {const _glyphs = radical_glyphs.value.map((glyph: ICustomGlyph) => {return plainGlyph(glyph)})const data = JSON.stringify({glyphs: _glyphs,constants: constants.value,constantGlyphMap: mapToObject(constantGlyphMap),version: 1.0,})await nativeSaveText(data, `radical_glyphs.json`, ['json'])} else if (editStatus.value === Status.CompGlyphList) {const _glyphs = comp_glyphs.value.map((glyph: ICustomGlyph) => {return plainGlyph(glyph)})const data = JSON.stringify({glyphs: _glyphs,constants: constants.value,constantGlyphMap: mapToObject(constantGlyphMap),version: 1.0,})nativeSaveText(data, `comp_glyphs.json`, ['json'])} else {const _glyphs = glyphs.value.map((glyph: ICustomGlyph) => {return plainGlyph(glyph)})const data = JSON.stringify({glyphs: _glyphs,constants: constants.value,constantGlyphMap: mapToObject(constantGlyphMap),version: 1.0,})await nativeSaveText(data, `glyphs.json`, ['json'])}
}
导出JPEG图片
const exportJPEG_tauri = async () => {// 导出JPEGconst _canvas = canvas.value as HTMLCanvasElementconst data = _canvas.toDataURL('image/jpeg')const buffer = base64ToArrayBuffer(data)const fileName = `${editCharacterFile.value.character.text}.jpg`nativeSaveBinary(buffer, fileName, ['jpg', 'jpeg'])
}
导出PNG图片
const exportPNG_tauri = async () => {// 导出PNGconst _canvas = canvas.value as HTMLCanvasElementrender(_canvas, false)const data = _canvas.toDataURL('image/png')const buffer = base64ToArrayBuffer(data)const fileName = `${editCharacterFile.value.character.text}.png`nativeSaveBinary(buffer, fileName, ['png'])render(_canvas, true)
}
导出SVG
const exportSVG_tauri = async () => {// 导出SVGif (editStatus.value !== Status.Edit && editStatus.value !== Status.Glyph ) returnconst components = editStatus.value === Status.Edit ? orderedListWithItemsForCurrentCharacterFile.value : orderedListWithItemsForCurrentGlyph.valueconst data = componentsToSvg(components, selectedFile.value.width, selectedFile.value.height)const fileName = `${editCharacterFile.value.character.text}.svg`nativeSaveText(data, fileName, ['svg'])
}
前端与Rust端通信
实现点击菜单按钮事件需要前后端通信,菜单由Rust生成并监听事件,但具体事件逻辑由前端实现。
Rust端代码,声明每个菜单按钮的事件函数,函数中逻辑比较简单,就是发送相应消息给前端,让前端知道目前要做的操作,具体逻辑在前端实现。
#[tauri::command]
fn create_file(app: AppHandle) {app.emit("create-file", ()).unwrap();
}#[tauri::command]
fn open_file(app: AppHandle) {app.emit("open-file", ()).unwrap();
}#[tauri::command]
fn save_file(app: AppHandle) {app.emit("save-file", ()).unwrap();
}#[tauri::command]
fn save_as(app: AppHandle) {app.emit("save-as", ()).unwrap();
}#[tauri::command]
fn undo(app: AppHandle) {app.emit("undo", ()).unwrap();
}#[tauri::command]
fn redo(app: AppHandle) {app.emit("redo", ()).unwrap();
}#[tauri::command]
fn cut(app: AppHandle) {app.emit("cut", ()).unwrap();
}#[tauri::command]
fn copy(app: AppHandle) {app.emit("copy", ()).unwrap();
}#[tauri::command]
fn paste(app: AppHandle) {app.emit("paste", ()).unwrap();
}#[tauri::command]
fn del(app: AppHandle) {app.emit("delete", ()).unwrap();
}#[tauri::command]
fn import_font_file(app: AppHandle) {app.emit("import-font-file", ()).unwrap();
}#[tauri::command]
fn import_templates_file(app: AppHandle) {app.emit("import-templates-file", ()).unwrap();
}#[tauri::command]
fn import_glyphs(app: AppHandle) {app.emit("import-glyphs", ()).unwrap();
}#[tauri::command]
fn import_pic(app: AppHandle) {app.emit("import-pic", ()).unwrap();
}#[tauri::command]
fn import_svg(app: AppHandle) {app.emit("import-svg", ()).unwrap();
}#[tauri::command]
fn export_font_file(app: AppHandle) {app.emit("export-font-file", ()).unwrap();
}#[tauri::command]
fn export_glyphs(app: AppHandle) {app.emit("export-glyphs", ()).unwrap();
}#[tauri::command]
fn export_jpeg(app: AppHandle) {app.emit("export-jpeg", ()).unwrap();
}#[tauri::command]
fn export_png(app: AppHandle) {app.emit("export-png", ()).unwrap();
}#[tauri::command]
fn export_svg(app: AppHandle) {app.emit("export-svg", ()).unwrap();
}#[tauri::command]
fn add_character(app: AppHandle) {app.emit("add-character", ()).unwrap();
}#[tauri::command]
fn add_icon(app: AppHandle) {app.emit("add-icon", ()).unwrap();
}#[tauri::command]
fn font_settings(app: AppHandle) {app.emit("font-settings", ()).unwrap();
}#[tauri::command]
fn preference_settings(app: AppHandle) {app.emit("preference-settings", ()).unwrap();
}#[tauri::command]
fn language_settings(app: AppHandle) {app.emit("language-settings", ()).unwrap();
}#[tauri::command]
fn import_template1(app: AppHandle) {app.emit("template-1", ()).unwrap();
}#[tauri::command]
fn remove_overlap(app: AppHandle) {app.emit("remove_overlap", ()).unwrap();
}
Rust端代码,监听事件:
app.on_menu_event(move |app, event| {if event.id() == "create-file" {create_file(app.app_handle().clone())} else if event.id() == "open-file" {open_file(app.app_handle().clone())} else if event.id() == "save-file" {save_file(app.app_handle().clone())} else if event.id() == "save-as" {save_as(app.app_handle().clone())} else if event.id() == "undo" {undo(app.app_handle().clone())} else if event.id() == "redo" {redo(app.app_handle().clone())} else if event.id() == "cut" {cut(app.app_handle().clone())} else if event.id() == "copy" {copy(app.app_handle().clone())} else if event.id() == "paste" {paste(app.app_handle().clone())} else if event.id() == "delete" {del(app.app_handle().clone())} else if event.id() == "import-font-file" {import_font_file(app.app_handle().clone())} else if event.id() == "import-templates-file" {import_templates_file(app.app_handle().clone())} else if event.id() == "import-glyphs" {import_glyphs(app.app_handle().clone())} else if event.id() == "import-pic" {import_pic(app.app_handle().clone())} else if event.id() == "import-svg" {import_svg(app.app_handle().clone())} else if event.id() == "export-font-file" {export_font_file(app.app_handle().clone())} else if event.id() == "export-glyphs" {export_glyphs(app.app_handle().clone())} else if event.id() == "export-jpeg" {export_jpeg(app.app_handle().clone())} else if event.id() == "export-png" {export_png(app.app_handle().clone())} else if event.id() == "export-svg" {export_svg(app.app_handle().clone())} else if event.id() == "add-character" {add_character(app.app_handle().clone())} else if event.id() == "add-icon" {add_icon(app.app_handle().clone())} else if event.id() == "font-settings" {font_settings(app.app_handle().clone())} else if event.id() == "preference-settings" {preference_settings(app.app_handle().clone())} else if event.id() == "language-settings" {language_settings(app.app_handle().clone())} else if event.id() == "template-1" {import_template1(app.app_handle().clone())} else if event.id() == "remove_overlap" {remove_overlap(app.app_handle().clone())}
});
前端代码,监听消息:
const initTauri = () => {const keys = Object.keys(tauri_handlers)for (let i = 0; i < keys.length; i++) {const key = keys[i]listen(key, (event) => {tauri_handlers[key]()})}
}
相关文章:
字玩FontPlayer开发笔记8 Tauri2文件系统
字玩FontPlayer开发笔记8 Tauri2文件系统 字玩FontPlayer是笔者开源的一款字体设计工具,使用Vue3 ElementUI开发,源代码: github: https://github.com/HiToysMaker/fontplayer gitee: https://gitee.com/toysmaker/fontplayer 笔记 字玩目…...
头歌python实验:网络安全应用实践3-验证码识别
第1关:简单的验证码识别 本关任务:编写一个能简单识别验证码的小程序。 为了完成本关任务,你需要掌握: 使用 pytesseract 库与 PIL 库解析图片;环境配置;读取图片文本信息。使用 pytesseract 库与 PIL 库解析图片 pytesseract 库可以从图像中提取文本。Tesseract 是一…...

客户案例:基于慧集通(DataLinkX)集成平台的金蝶云星空与HIS系统集成案例--凭证模板的配置(一)
当前的原型客户是一家医院,财务系统使用的是金蝶云星空,需要与医院专用的HIS系统进行集成。本文档主要是介绍其中的凭证模板的配置功能。 凭证模板组件旨在生成凭证前,通过内部整理整合原始单据数据,将其转化为可生成一张凭证的数…...
基于 Python 的大学教室资源管理系统的设计与实现
标题:基于 Python 的大学教室资源管理系统的设计与实现 内容:1.摘要 摘要:随着高校教育的不断发展,教室资源的管理变得越来越重要。为了提高教室资源的利用率,本文设计并实现了一个基于 Python 的大学教室资源管理系统。该系统采用了 B/S 架…...

nginx-灰度发布策略(split_clients)
一. 简述: 基于客户端的灰度发布(也称为蓝绿部署或金丝雀发布)是一种逐步将新版本的服务或应用暴露给部分用户,以确保在出现问题时可以快速回滚并最小化影响的技术。对于 Nginx,可以通过配置和使用不同的模块来实现基于…...

nginx正向代理从安装到使用一网打尽系列(二)使用
一、背景 使用场景大总结,可作为参考手册用 nginx正向代理从安装到使用一网打尽系列(一)安装 nginx正向代理从安装到使用一网打尽系列(二)使用 二、使用场景 1、所有内网应用都不能直接访问外网,但需要…...

Bash Shell的操作环境
目录 1、路径与指令搜寻顺序 2、bash的进站(开机)与欢迎信息:/etc/issue,/etc/motd (1)/etc/issue (2)/etc/motd 3、bash的环境配置文件 (1)login与non-…...

Python爬虫基础——认识网页结构(各种标签的使用)
1、添加<div>标签的代码定义了两个区块的宽度和高度均为100px,边框的格式也相同,只是区块中显示的内容不同; 2、添加<ul>和<ol>标签分别用于定义无序列表和有序列表。<il>标签位于<ul>标签或<ol>标签之…...
如何实现一个充满科技感的官网(二)
背景 在上一篇文章 《如何实现一个充满科技感的官网(一)》 中,我们初步了解了该官网的整体设计,并与大家探讨了它的视觉呈现和用户体验。 我们前期的内部设计偏向简洁,所以开始思考如何提升网站的整体设计感。这些尝…...
GNU链接器简介
GNU链接器简介 1 使用简单程序简介链接脚本1.1 测试程序1.2 编译测试程序1.2.1 不使用链接器编译1.2.1.1 不使用链接器编译1.2.1.2 读取objdump_test 的结构 1.2.2 使用链接器去链接1.2.2.1 链接脚本1.2.2.2 使用链接脚本编译1.2.2.3 读取objdump 的结构 2 链接脚本2.1 基本连接…...
欧几里得算法(简单理解版,非严格证明)
欧几里得算法用于求解两个整数的最大公约数,又称为辗转相除 依据的基本定理: GCD(a,b)GCD(a%b,b) 证明: 对于搞理论的人可能需要会严格证明,但是对于我们一般人而言,只要能理解其原理并记住即可,后者实际上…...

Mac软件介绍之录屏软件Filmage Screen
软件介绍 Filmage Screen 是一款专业的视频录制和编辑软件,适用于 Mac 系统 可以选择4k 60fps,可以选择录制电脑屏幕,摄像头录制,可以选择区域录制。同时也支持,简单的视频剪辑。 可以同时录制电脑麦克风声音 标准…...
Ubuntu cuda-cudnn中断安装如何卸载
文章目录 问题描述解决方法使用强制移除 问题描述 Ubuntu22.04系统,在终端中执行apt insatll安装或dpkg .deb安装时如果强制关闭终端会导致安装失败(安装包会变成iu状态或ru状态,安装成功的应该是ii状态) 此时,无论是…...
CSS——7.CSS注释
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>css注释</title><link rel"stylesheet" type"text/css" href"a.css"/></head><body><!--头部开始(h…...

鸿蒙APP之从开发到发布的一点心得
引言: 做鸿蒙开发大概有1年左右时间了,从最开始的看官方文档、看B站视频,到后来成功发布两款个人APP(房贷计算极简版、时简时钟 轻喷,谢谢)。简单描述一下里边遇到的坑以及一些经历吧。 学习鸿蒙开发 个…...

某小程序sign签名参数逆向分析
文章目录 1. 写在前面2. 接口分析3. 分析还原 【🏠作者主页】:吴秋霖 【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python…...

智能风控/数据分析 聚合 分组 连接
data。head()查看前几行 data.head() 是一个在Python的Pandas库中常用的方法,用于查看DataFrame对象的前几行数据。默认情况下,head() 方法会返回DataFrame的前5行数据,但是你也可以通过传递一个整数参数来指定返回的…...
Unity3D PBR光照计算公式推导详解
前言 在Unity3D中,PBR(Physically Based Rendering,基于物理的渲染)光照模型是一种高级光照模型,它模拟了真实世界中光的传播和反射过程,从而提供了更加逼真的渲染效果。PBR光照模型的计算公式涉及多个物理…...
行为树详解(6)——黑板模式
【动作节点数据共享】 行为树中需要的参数可以来自游戏中的各个模块,如果仅需从多个模块获取少量参数,那么可以直接在代码中调用其他模块的单例继而层层调用获取数据。 如果获取的参数量很大,从架构上看,我们需要通过加一个中间…...
Vue.js与其他框架有哪些兼容性?
Vue.js的兼容性主要体现在几个方面,包括浏览器支持、运行环境适应性、与其他库和框架的集成能力等。以下是更详细的解释: 浏览器兼容性 现代浏览器:Vue.js广泛支持所有主流的现代浏览器,如Google Chrome, Firefox, Safari, Edge…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...