## canvas 绘制(dist\bundle.js) ```plain ┌─────────────────────────────────────────────────────────────┐ │ 1. 用户打开 cool.html │ │ 参数: WOPISrc=..., access_token=... │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 2. global.js 初始化 │ │ - 检测浏览器类型 (BrowserProperties) │ │ - 创建 WebSocket 连接 │ │ - 发送 "load url=..." 命令 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 3. bundle.js 加载并处理 "status:" 消息 │ │ status: { type: "text", parts: 5, width: 21000, ... } │ │ → 创建 L.WriterTileLayer │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 4. TileManager 请求瓦片 │ │ → "tilecombine part=0 tileposx=0,256,512 ..." │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 5. 服务器返回瓦片数据 │ │ textMsg: "tile: nviewid=0 part=0 width=256 height=256" │ │ img: ImageBitmap (二进制图像数据) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 6. TileManager.onTileMsg() 处理 │ │ - 解析 tileMsgObj │ │ - 存储到 _tiles Map │ │ - 标记需要重绘 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 7. CanvasSectionContainer 绘制 │ │ ctx.drawImage(tile.image, sx, sy, sw, sh, dx, dy, dw, dh)│ │ → 用户看到完整的文档页面 │ └─────────────────────────────────────────────────────────────┘ ``` + **文档类型检测**: `_docType` 属性标识文档类型 ("text", "spreadsheet", "presentation", "drawing") + **消息处理**: `_onMessage(textMsg, img)` 是核心消息处理函数 + **WriterTileLayer**: 专门用于处理 Word 文档 (docx) 的图层类 + **瓦片消息**: 通过 WebSocket 接收 "tile:" 和 "tilecombine:" 消息 + **Canvas 渲染**: 使用 `canvas.drawImage()` 将瓦片图像绘制到 Canvas 上 ### 核心架构 ### 1. 文档类型层次结构 ```javascript L.Layer (基类) ↓ L.CanvasTileLayer (瓦片图层基类) ↓ ├── L.WriterTileLayer (Word 文档 - docx) ├── L.CalcTileLayer (电子表格 - xlsx) └── L.ImpressTileLayer (演示文稿 - pptx/odp) ``` ### 2. DOCX 文档数据对象结构 #### **TileMsgObj** (瓦片消息对象) ```javascript { id: number, // 瓦片唯一标识 width: number, // 瓦片宽度 (像素) height: number, // 瓦片高度 (像素) part: number, // 文档部分 (页码) mode: number, // 渲染模式 (默认 0) nviewid: number, // 视图ID wireId: number, // 传输ID (用于增量更新) zoom: number // 缩放级别 } ``` #### **文档层对象** (L.WriterTileLayer) ```javascript { _docType: "text", // 文档类型标识 _selectedPart: number, // 当前选中的页码 _selectedMode: number, // 当前模式 _viewId: number, // 视图ID _debug: { // 调试信息 tileInvalidationsOn: boolean, tileOverlaysOn: boolean }, _onMessage: Function // 核心消息处理函数 } ``` ### 渲染流程 ### 第一阶段: 初始化与连接 ```javascript 1. cool.html 加载 ↓ 2. global.js 初始化浏览器属性和配置 ↓ 3. 创建 WebSocket 连接 websocketURI = "ws://collabora-server/cool/{docURL}" ↓ 4. 发送 load 命令 "load url={encodedDocURL} lang=zh-CN accessibilityState=..." ``` ### 第二阶段: 文档加载 ```javascript 1. 服务器返回 "status:" 消息 包含文档类型、尺寸、页数等元数据 ↓ 2. 根据文档类型创建对应的 TileLayer if (command.type === "text") docLayer = new L.WriterTileLayer(options) ↓ 3. 设置文档属性 docLayer._docType = "text" docLayer.options = { tileWidthTwips: 3840, tileHeightTwips: 3840, tileSize: 256 // 默认瓦片大小 256x256 像素 } ``` ### 第三阶段: 瓦片渲染 #### **瓦片请求** ```javascript // 客户端发送瓦片组合请求 "tilecombine nviewid=0 part=0 width=256 height=256 tileposx=0,256,512 tileposy=0,0,0 tilewidth=3840 tileheight=3840" ``` #### **瓦片接收与处理** ```javascript TileManager.onTileMsg(textMsg, img) { // 1. 解析瓦片消息 var tileMsgObj = parseServerCmd(textMsg); // textMsg 格式: "tile: nviewid=0 part=0 width=256 height=256 ..." // 2. 转换为坐标 var coords = tileMsgToCoords(tileMsgObj); // coords = { x, y, z (zoom), part, mode } // 3. 获取或创建瓦片对象 var tile = this.get(coords); tile.viewId = tileMsgObj.nviewid; tile.wireId = tileMsgObj.wireId; tile.image = img; // ImageBitmap 或 Image 对象 // 4. 标记需要重绘 this.rehydrateTile(tile, true); } ``` ### 第四阶段: Canvas 绘制 #### **CanvasSectionContainer** (Canvas 容器管理) ```javascript { canvas: HTMLCanvasElement, // 主 Canvas 元素 context: CanvasRenderingContext2D, // 2D 渲染上下文 width: number, // Canvas 宽度 height: number, // Canvas 高度 clearColor: string // 背景色 } ``` #### **瓦片绘制到 Canvas** ```javascript // 核心绘制逻辑 function paintTile(tile, canvas) { // 1. 获取 2D 渲染上下文 var ctx = canvas.getContext("2d"); // 2. 计算源矩形 (瓦片图像中的区域) var sx = 0, sy = 0; var sWidth = tile.image.width; var sHeight = tile.image.height; // 3. 计算目标矩形 (Canvas 中的位置) var dx = tile.coords.x * 256; // 目标 X 坐标 var dy = tile.coords.y * 256; // 目标 Y 坐标 var dWidth = 256; // 目标宽度 var dHeight = 256; // 目标高度 // 4. 绘制瓦片 if (tile.image) { canvas.drawImage( tile.image, // 源图像 sx, sy, // 源起点 sWidth, sHeight, // 源尺寸 dx, dy, // 目标起点 dWidth, dHeight // 目标尺寸 ); } } ``` ### 第五阶段: 增量更新 (Delta) ```javascript // 服务器发送增量更新而非完整瓦片 if (textMsg.startsWith("delta:")) { var deltaObj = parseServerCmd(textMsg); // 应用增量到现有瓦片 applyDelta(tile, deltaObj); } ``` ### 关键特性 ### 1. **瓦片缓存机制** ```javascript TileManager._tiles = new Map(); // 缓存所有已加载的瓦片 // Key: "z:x:y:part:mode" // Value: { image, wireId, viewId, coords } ``` ### 2. **视口可见性优化** ```javascript // 仅请求当前视口内的瓦片 clientvisiblearea = "0;0;{width};{height}" // 优先级: 视口中心 > 视口边缘 > 视口外 ``` ### 3. **消息队列** ```javascript global.queueMsg = []; // 在文档层准备好之前缓存消息 socket.onmessage = function(event) { if (typeof socket._onMessage === "function") { socket._emptyQueue(); socket._onMessage(event); } else { queueMsg.push(event.data); // 延迟处理 } } ``` ### 4. **多用户协作** ```javascript // 每个用户视图有独立的 viewId tileMsgObj.nviewid = 0; // 当前用户 // 其他用户的光标、选择通过单独的消息同步 ```