首页/新闻资讯/正文详情

EastDraw矢量绘图软件:完整源码解析与改造指南

发布时间:2026/9/25 7:11:33 来源:云帆数科 栏目:资讯中心
EastDraw矢量绘图软件:完整源码解析与改造指南
简介EastDraw是一款专为图形创作设计的矢量绘图软件基于数学路径而非像素描述图形因此可无损缩放并保持清晰细节。压缩包内含完整工程与源代码共91个文件、约317KB文件类型以VC项目为主29个h头文件与28个cpp源文件构成绘图核心逻辑21个bmp位图、4个cur光标与2个ico图标提供界面素材另有dsw/dsp工程文件、rc资源脚本及exe可执行程序便于读者直接编译运行。该资源已有196人学习下载适合计算机图形学学习者、MFC程序员及有课程设计需求的在校学生。分析源码可深入理解矢量图形的生成与编辑算法、图形对象的数据结构与图层管理、文件格式的导入导出流程以及菜单、工具栏等GUI交互逻辑。代码结构清晰模块划分完整是学习图形处理与软件工程设计的优秀范例也可作为开发自绘控件或绘图工具的直接参考。1. 矢量绘图软件EastDraw一份能编译能改的完整源码项目很多做桌面工具的人都有过这种体验想给项目加一个矢量绘图能力翻开源码不是上百万行、架构复杂到读不进去的巨型项目就是只画了一条直线的Demo。矢量绘图软件EastDraw及其完整源代码.zip这类资源正好卡在中间——它是一份完整绘图软件的工程源码图形对象模型、贝塞尔曲线编辑、文件读写、撤销重做都在里面并且被打包成一个zip本地解压后可以直接编译、断点、改造。下面围绕这份zip讲清楚怎么跑通、怎么读透、怎么把它改成自己能用的绘图内核。2. 拆解EastDraw的源码骨架对象模型、渲染与模块边界2.1 先认文档对象模型绘图软件的“数据结构地基”写绘图软件最容易踩的坑是一上手就写画布控件、写鼠标事件结果画出来的图形存不住、选不中、无法导出。真实绘图软件的骨架是文档对象模型不是界面。EastDraw这类源码里通常会把这一层放在最前面。对象模型一般分三级。最外层是Document管理页面尺寸、图层列表、当前选中集合中间是Layer一个图层相当于一张透明胶片控制可见、锁定、堆叠顺序最底层是GraphicObject也就是实际画出来的矩形、路径、文本。这个分层乍看多绕了一层但图层可以整层隐藏、整层锁定、批量排序这是直接在画布层面做不到的。GraphicObject是整棵继承树的根它把“一个图形”抽象成三个能力画自己、测命中、算包围盒。下面是这类接口最常见的定义方式。// 图形对象的统一接口绘图引擎只认这个抽象不关心具体是什么形状 class GraphicObject { public: virtual void draw(IRenderer* renderer) 0; // 把自身画到渲染器 virtual bool hitTest(double x, double y) const 0; // 命中测试供选择工具用 virtual Rect boundingBox() const 0; // 包围盒重绘和缩放取景都靠它 bool visible() const { return visible_; } void setVisible(bool v) { visible_ v; } protected: bool visible_ true; std::string name_; };接口里三个虚函数不是在凑设计模式而是对应三个真实场景。draw是渲染帧要做的事hitTest解决“鼠标点在空白处怎么知道点到谁”boundingBox是画布局部重绘的依据——拖动一个对象时只需要重画旧位置和新位置的包围盒不需要整屏刷新。参数类型用double而不是float或int因为矢量图的逻辑坐标系与像素无关放大到800%之后float的小数精度就不够用了int更是直接废掉。再往下组合对象是源码里大概率出现的一层。GroupObject内部持有子对象列表对外仍是一个GraphicObject移动它等于移动全部子对象画它等于按顺序画所有子对象。这种组合结构让整个文档变成一棵树遍历、序列化、选中都顺着树做。读代码时先确认一件事Document里有没有显式的选中集合。有的话后面所有交互逻辑都会围绕它转没有的话说明选中状态是临时算出来的通常是“最后命中的对象”这种实现做多选场景很容易翻车。2.2 渲染管线从逻辑坐标到屏幕像素的必修课坐标变换是矢量绘图软件最容易看晕的一段。按下面这条链理解就行世界坐标存的是逻辑单位和屏幕像素无关视图变换负责缩放、平移、旋转设备坐标是最终像素。文档里存的永远是第一层界面看到的是第三层。变换关系用2D仿射矩阵统一表达。这样设计之后渲染循环在任何地方都一样取出图形的世界坐标路径套矩阵得到像素路径再填充和描边。每一步互相独立想加缩略图、导出高分辨率图都只是换矩阵的事。// 2D 仿射变换矩阵按 a,b,c,d,e,f 顺序存储 // x a*x c*y e // y b*x d*y f struct Matrix2D { double m[6]; static Matrix2D scale(double sx, double sy) { return Matrix2D{sx, 0, 0, sy, 0, 0}; } static Matrix2D translate(double tx, double ty) { return Matrix2D{1, 0, 0, 1, tx, ty}; } }; Point transform(const Matrix2D mt, const Point p) { return Point{ mt.m[0]*p.x mt.m[2]*p.y mt.m[4], mt.m[1]*p.x mt.m[3]*p.y mt.m[5] }; }矩阵参数说明e和f是平移量单位跟世界坐标一致a和d是缩放系数大于1放大、小于1缩小b和c是旋转和斜切耦合项。平时平移可以直接在坐标上加但一旦涉及“先旋转再缩放再平移”逐坐标加加减减就乱了矩阵相乘一次可以把所有变换合并成一个再做一次变换。这一节还要理解“矢量图放大不糊”的真实现法放大时路径控制点坐标不变渲染器只是把曲线重新采样成新的像素点。决定清晰度的不是原分辨率而是采样步长和抗锯齿策略这部分在第3章展开。2.3 读完整源码的顺序从Document开始别开main拿到压缩包解压完多数人习惯直接开main函数找窗口。血泪经验是绘图软件恰恰不该从界面读。界面代码和工具调度绑得太深细节多、逻辑碎读进去容易迷失。我一般按下面的顺序扫源码读码顺序关注模块重点确认的问题1文档对象模型对象继承体系、属性存哪2序列化/文件读写save/load格式、有无版本兼容3渲染器路径求值、画笔画刷实现4交互选区、控制点、命令栈5UI工具条与画布事件怎么对接把序列化放到第二步是有原因的。读写文件是文档对象模型的“投影”作者怎么抽象图形、哪些属性被认定为图形固有属性都体现在存储格式里。能读懂序列化代码就通了一半。至于“完整源代码”到底全不全第一件事是查zip里有没有常见工程文件。如果工程文件是.dsw或.vcproj多半是VC6/VC2008时代写的有这些文件说明能还原作者当时的编译环境只有源码没有工程文件就得自己重新搭工程。这个判断会在第5章的避坑清单里再讲。3. 复现核心绘图链路从贝塞尔曲线到像素输出3.1 三次贝塞尔曲线所有矢量轮廓的底层语言位图里“圆”是点阵矢量图里“圆”是一组数学曲线。几乎所有形状最终都会被转成由贝塞尔曲线组成的路径。三次贝塞尔曲线由4个控制点定义P0是起点P3是终点P1和P2是方向控制点公式可以写成B(t) (1-t)³·P0 3(1-t)²·t·P1 3(1-t)·t²·P2 t³·P3t从0到1。落到代码里就是一个通用求值函数Point cubicBezier(const Point p0, const Point p1, const Point p2, const Point p3, double t) { double mt 1.0 - t; double a mt * mt * mt; double b 3.0 * mt * mt * t; double c 3.0 * mt * t * t; double d t * t * t; return Point{ a*p0.x b*p1.x c*p2.x d*p3.x, a*p0.y b*p1.y c*p2.y d*p3.y }; }参数t是0到1的比例。t0时结果就是P0t1时结果就是P3中间值被P1、P2“拽”出弧度这也是“控制点”叫法的来历。注意a、b、c、d四项的系数之和恒等于1所以曲线永远落在控制点围成的凸包内这个性质在做裁剪和包围盒计算时很有用。从数学公式到绘图源码中间还要经过一层路径数据结构。一个路径由若干段组成MoveTo表示起点LineTo是直线段CurveTo是三次贝塞尔段ClosePath收口。老代码里常见的是用数组存子路径每段带类型标记基于Windows GDI的工程可能封装了PolyBezier底层API但它们表达的本质都是这套参数。读这类源码时把代码里的数字代入上面公式把控制点坐标打印出来看比纯看代码块更快建立“控制点拖拽”的手感。3.2 路径转像素采样步长与抗锯齿的取舍矢量渲染最后一步是把数学曲线变成屏幕上的离散像素。做法是采样按一定步长取t的序列算出点坐标连成折线画。步长定多少是个经典工程取舍。步长太大折线感强圆看着像多边形。步长太小计算量大拖动时掉帧。合理策略是按“当前屏幕尺寸下的允许误差”反推步长而不是固定给个0.01。经验值是把曲线上相邻采样点的像素距离控制在0.5到1个像素以内。也就是说同样一条曲线缩放到50%时取200段放大到200%时就得取800段这样不同缩放级别下的视觉误差才能保持一致。很多老绘图软件放大后曲线发毛就是这里图省事写死了步长。抗锯齿是另一层。最简单的办法是超采样把画面按2x或4x放大渲染再缩小显示相当于用计算量换平滑。正式一点的渲染器会计算像素被覆盖的面积比例边缘像素按覆盖率混色。读源码时注意找渲染器里的覆盖率权重那通常是一串浮点系数比超采样高效得多。最终像素颜色 背景色 × (1 - 覆盖率) 前景色 × 覆盖率。覆盖率0.5就是半透明边缘这就是矢量图形边缘平滑的本质。3.3 最小可运行验证把曲线导出成SVG为了在不熟悉整套源码的情况下快速验证“我对贝塞尔的理解和作者一致”我常用一段不依赖图形库的脚本把路径直接导出成SVG用浏览器肉眼对比。下面这段可以整体复制运行。# 把一条三次贝塞尔曲线采样后导出为 SVG用于肉眼验证采样逻辑 def sample_bezier(p0, p1, p2, p3, steps): pts [] for i in range(steps 1): t i / steps mt 1 - t x mt**3 * p0[0] 3 * mt**2 * t * p1[0] 3 * mt * t**2 * p2[0] t**3 * p3[0] y mt**3 * p0[1] 3 * mt**2 * t * p1[1] 3 * mt * t**2 * p2[1] t**3 * p3[1] pts.append((x, y)) return pts def dump_svg(polyline, size400): path M L .join(f{x:.1f},{y:.1f} for x, y in polyline) svg fsvg xmlnshttp://www.w3.org/2000/svg width{size} height{size} viewBox0 0 {size} {size} path d{path} fillnone stroke#202020 stroke-width2 / /svg with open(bezier_check.svg, w, encodingutf-8) as f: f.write(svg) if __name__ __main__: # 起点左下、两个控制点在上方终点右下形成一条弓形 curve sample_bezier((40, 320), (120, 40), (280, 40), (360, 320), steps200) dump_svg(curve) print(bezier_check.svg written)steps参数需要说明200步对400x400画布足够肉眼看不到折线改成10再跑一次能明显看到多边形化这就是步长影响的极简演示。运行完用浏览器打开bezier_check.svg如果曲线平滑说明采样逻辑没问题。之后再读源码里真正的渲染器拿这段做参照能快速定位是步长问题还是变换矩阵问题。4. 交互层的工程账选中、拖拽和撤销重做的真实实现4.1 命中测试鼠标点下去软件怎么知道点中了你交互第一步是判断鼠标按下点落在哪个对象上这一步叫命中测试。矢量图形的命中测试不能靠“查像素颜色”来做因为填充区域和描边都要可点而且不同对象在不同缩放级别下命中难度应该一致。常见做法是把路径扁平化成折线然后计算点到线段的最小距离距离小于阈值就算命中。填充多边形用“点在多边形内部”算法线条和开放路径用点到线段距离。阈值一般是4到6个像素并且要跟着视图缩放换算回世界坐标。这条容易被忽略如果直接用世界坐标距离做阈值放大之后会感觉“点哪都选不中”。// 点 P 到线段 A-B 的最短距离命中测试的核心计算 double pointToSegmentDist(const Point p, const Point a, const Point b) { double dx b.x - a.x, dy b.y - a.y; double len2 dx*dx dy*dy; double t (len2 0.0) ? 0.0 : ((p.x - a.x)*dx (p.y - a.y)*dy) / len2; t std::max(0.0, std::min(1.0, t)); double px a.x t * dx, py a.y t * dy; double ex p.x - px, ey p.y - py; return std::sqrt(ex*ex ey*ey); }代码里的t是投影比例如果垂足落在线段内部t在0到1之间在线段外就夹到端点。返回值就是点到线段的最短欧氏距离。阈值要预先通过视图矩阵逆变换成世界坐标距离这样缩放就不会影响手感。选中之后还有层级问题。叠在一起的多个对象应该选最上层那个。源码里一般从图层顺序反着遍历命中第一个就返回多选则用Shift把命中结果追加到选中集合。4.2 命令模式撤销重做不是快照而是操作栈绘图软件里CtrlZ是刚需。最简单的实现是快照每次操作前把整个文档序列化保存一份撤销时整份恢复。数据量小的时候确实省事但一个几十MB的文档操作十几次内存就爆了而且拖拽过程中的每次微调都算一步的话快照的内存会灾难性增长。成熟做法是命令模式每一次用户操作封装成一个命令对象负责execute和undo。每步只存差异不存全量快照。class Command { public: virtual void execute() 0; // 做一次操作 virtual void undo() 0; // 撤销该操作 virtual ~Command() default; }; class MoveCommand : public Command { GraphicObject* obj_; double dx_, dy_; public: MoveCommand(GraphicObject* obj, double dx, double dy) : obj_(obj), dx_(dx), dy_(dy) {} void execute() override { obj_-move(dx_, dy_); } void undo() override { obj_-move(-dx_, -dy_); } };命令栈维护undo和redo两个栈。执行命令压入undo栈撤销时把命令弹出、调undo()再压入redo栈重做反之。一个容易漏的细节新命令入栈前要清空redo栈否则历史会分叉用户会看到“撤销之后重做那一串突然消失了”。用MoveCommand举例是因为它简单。实际绘图软件里移动、缩放、改属性各是一套命令。命令模式的好处很明显每个命令自带undo逻辑业务代码和撤销机制彻底解耦新增一种操作只需要写好execute和undo。注意命令对象里保存的dx、dy是本次操作完整移动量而不是鼠标每一帧的增量否则一次拖拽会被记成几十条历史。4.3 视图与文档分离为什么不能把数据直接塞进控件新手做画板最常见的翻车是“草图即文档”直接在画布控件上存了一堆形状。问题是多开一个预览窗口就没法同步导出图片时要复刻整个画布状态代码越写越拧巴。EastDraw这层如果做得到位界面和文档之间一定有一条清晰的边界文档层只存数据和业务逻辑不关心自己在屏幕上长什么样。视图层订阅文档变更事件收到通知就重绘。任何交互操作都走命令模式命令改文档文档发通知视图刷新。受益场景很实在一个文档多开视图画布窗口、导出预览、缩略图各是一个视图导出功能直接拿文档层数据去渲染完全不依赖界面控件。如果某个操作改了画布数据但忘了通知视图界面上不会变一旦触发重绘又突然跳回旧状态这就是典型的同步漏电。写交互层代码时我会刻意把文档层编译成独立静态库让UI层依赖它。这样单元测试可以直接测文档和命令不用起窗口跑得又快又稳。5. zip解压到编译跑通的避坑清单五个常见问题与解法5.1 zip伪加密提示要密码但文件其实没加密现象双击压缩包每个文件都要求输密码但资源明明写着无密码。原因zip通用格式里有一个加密标志位。某些打包工具或传播者为了让下载者“多走一步”会把通用位标志的第0位置成1但文件内容根本没加密这就是zip伪加密。严格解压软件看到加密位就直接弹密码框于是看起来像真的加密了。解决先别放弃。用7-Zip打开看能否列出目录能列出目录说明大概率只是标志位问题。写个小脚本把本地文件头和中央目录里的加密位清掉import struct def repair_fake_encryption(zip_path, out_path): with open(zip_path, rb) as f: data bytearray(f.read()) # 本地文件头签名 PK\x03\x04 与中央目录头签名 PK\x01\x02 都要处理 for sig in (bPK\x03\x04, bPK\x01\x02): start 0 while True: idx data.find(sig, start) if idx -1: break # 通用位标志在文件头偏移 6 处占 2 字节清掉最低位(加密位) flags struct.unpack_from(H, data, idx 6)[0] flags ~0x0001 struct.pack_into(H, data, idx 6, flags) start idx 1 with open(out_path, wb) as f: f.write(data) if __name__ __main__: repair_fake_encryption(source.zip, source_fixed.zip)脚本逻辑是按zip文件头签名定位所有文件头逐个清除加密标志位。参数说明zip_path是输入原包out_path是修复后的输出路径原文件不要动。修复完用7-Zip重新打开验证。更严谨的工程化做法是用zipfile遍历出每个entry的偏移再精确修改上面这段对常规zip足够实际使用中也没翻过车。5.2 Linux下unzip解压中文文件名乱码现象在Linux里解压文件名全部变成乱码或问号。原因老zip包用GBK编码存文件名现代unzip默认按UTF-8解码两边对不上就乱码。解决解压时显式指定编码unzip -O GBK source.zip -d EastDraw如果unzip版本不支持-O参数用7-Zip7z x source.zip -oEastDraw -tcw-tcw是告诉7-Zip文件名按Windows编码GBK处理。解压后立即把关键目录改成纯ASCII名字避免后续编译环境里编码问题连环炸。特别是工程文件路径里有中文名时老编译器经常找不到中间文件。5.3 老工程全身报错依赖缺失与工具集不匹配现象打开解决方案编译输出上千行错误报头文件找不到、宏未定义看起来像源码损坏。原因老工程面向VC6/VC2008编写新编译器对标准库和Windows SDK的默认配置不同加上缺少MFC或ATL组件就会引发连锁报错。解决先看stdafx.h和targetver.h里include了哪些头按缺失项顺序排查。常见的是缺MFC头文件或Windows SDK组件在Visual Studio Installer里勾选“适用于最新v143生成工具的C MFC”之类的组件再重试。这类问题不是靠改代码能扛过去的我一般先降级到兼容的构建工具集。一个很实在的提醒不要一上来就把.dsw/.vcproj直接转成新格式的.sln。转格式可能引入几百个和源码无关的差异先原格式编译缺啥装啥等通过后再转否则问题混在一起更难定位。5.4 解压文件数量对不上路径过长和不完整包现象解压过程没报错但编译时说缺某个.cpp回头看zip发现有些文件根本没解出来。原因两种居多。一种是Windows提取路径超过260字符系统截断另一种是zip本身不完整曾被传输中断。解决先做完整性校验7z t source.zip有警告或错误就先重新下载或用7z重压一份。校验无误后解压目标放到磁盘根目录的短路径下比如C:\src\EastDraw能绕开路径过长限制。最后做一道保险对比zip里文件清单和解压出的文件数量数量对不上一定不要开始编译。5.5 高分屏模糊和画布闪烁老代码的现代适配病现象程序编译通过运行起来在高分屏上字小得看不清拖动图形时画布闪烁。原因老代码没声明DPI感知Windows会自动把界面放大导致GDI画布尺寸和窗口实际像素不匹配看什么都发虚闪烁则是没做双缓冲每次重绘直接在窗口上画一擦一画就闪。解决DPI问题在程序入口处调用SetProcessDPIAware()或者给工程加manifest声明dpiAware画布闪烁用双缓冲画布先把内容画到内存位图再一次性BitBlt到窗口。这两处改动都不大但改完体验提升非常明显是低成本高回报的适配。6. 把EastDraw的源码改造成自己的绘图组件库读完源码不要整体搬走。我一般只抽取三块复用文档对象模型、命令栈、路径采样渲染。抽取时坚持让文档层和命令层保持纯逻辑不依赖任何UI框架。这样组件能同时服务桌面端、Web端和离线导出。验证方法要趁早建立。核心路径的单元测试很简单比如贝塞尔端点性质t0和t1必须分别等于起点终点命中测试的边缘距离要精确到浮点误差范围内。下面这段测试代码值得保留在工程里// 核心逻辑的纯函数测试不依赖任何窗口和渲染设备 void test_bezier_endpoints() { Point p0{0, 0}, p1{100, 100}, p2{200, -50}, p3{300, 0}; Point s cubicBezier(p0, p1, p2, p3, 0.0); Point e cubicBezier(p0, p1, p2, p3, 1.0); assert(s.x 0 s.y 0); // t0 必须在起点 assert(e.x 300 e.y 0); // t1 必须在终点 }测试跑通后再把业务对象映射成GraphicObject的Adapter把工程里已有的JSON、SVG导出接上。这样预制给业务团队的不是一套画板而是绘图内核谁要谁自己包界面。做这类组件很多次之后我养成了一个习惯动手改源码前先建好命令行验证路径确保核心算法不依赖任何窗口。这种习惯能省下大量调试时间。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

仓库货位识别系统实战:OpenCV图像处理与Tesseract识别落地指南
仓库货位识别系统实战:OpenCV图像处理与Tesseract识别落地指南

简介:一套基于Python和计算机视觉的仓库货位识别系统项目实例,面向具备Python基础、熟悉OpenCV或深度学习框架的开发者、高校学生及仓储自动化从业者。系统以摄像头采集货架与标签图像,结合YOLO目标检测、PaddleOCR文字识别及透视变换&#x… · 2026/9/25 7:11:26

高效文本润色指南:优化措辞与提升表达流畅度
高效文本润色指南:优化措辞与提升表达流畅度

好的,请把需要整理语言的内容发给我。我会在保留原意的前提下,优化措辞、调整结构、提升表达流畅度,并按要求重新输出。 · 2026/9/25 7:11:20

xberg C 绑定实战:在同一个抽取配置中组合结果缓存与质量后处理
xberg C 绑定实战:在同一个抽取配置中组合结果缓存与质量后处理

后端AI 应用NLP 【免费下载链接】xberg Polyglot document intelligence with a Rust core: extract text, metadata, images, tables, and structured data from 106 formats across 140 file extensions, plus code intelligence for 371 languages. Fifteen bindings, with … · 2026/9/25 7:11:14

奈氏图完全解析:从传递函数到闭环稳定性判据
奈氏图完全解析:从传递函数到闭环稳定性判据

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:37:26

全名处理:从字段设计到国际化,避开用户系统中的命名陷阱
全名处理:从字段设计到国际化,避开用户系统中的命名陷阱

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:37:20

Android逆向利器JEB:解密加固APK的完整工作流
Android逆向利器JEB:解密加固APK的完整工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:37:20

物联网无线收发芯片选型实战指南:穿透参数表的物理层与协议栈真相
物联网无线收发芯片选型实战指南:穿透参数表的物理层与协议栈真相

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:37:20

三极管工作状态与失真诊断:从放大区到饱和截止的边界分析
三极管工作状态与失真诊断:从放大区到饱和截止的边界分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:37:20

ESP32开发板更换后为何需重新适配?小智源码板级适配全解析
ESP32开发板更换后为何需重新适配?小智源码板级适配全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:37:20

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码