Files
leaudit-platform-backend/docs/Collabora/参考资料/Collabora canvas 绘制.md
T

9.8 KiB
Raw Blame History

canvas 绘制(dist\bundle.js

┌─────────────────────────────────────────────────────────────┐
│ 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. 文档类型层次结构

L.Layer (基类)
  
L.CanvasTileLayer (瓦片图层基类)
  
  ├── L.WriterTileLayer    (Word 文档 - docx)
  ├── L.CalcTileLayer      (电子表格 - xlsx)
  └── L.ImpressTileLayer   (演示文稿 - pptx/odp)

2. DOCX 文档数据对象结构

TileMsgObj (瓦片消息对象)

{
  id: number,           // 瓦片唯一标识
  width: number,        // 瓦片宽度 (像素)
  height: number,       // 瓦片高度 (像素)
  part: number,         // 文档部分 (页码)
  mode: number,         // 渲染模式 (默认 0)
  nviewid: number,      // 视图ID
  wireId: number,       // 传输ID (用于增量更新)
  zoom: number          // 缩放级别
}

文档层对象 (L.WriterTileLayer)

{
  _docType: "text",              // 文档类型标识
  _selectedPart: number,         // 当前选中的页码
  _selectedMode: number,         // 当前模式
  _viewId: number,               // 视图ID
  _debug: {                      // 调试信息
    tileInvalidationsOn: boolean,
    tileOverlaysOn: boolean
  },
  _onMessage: Function           // 核心消息处理函数
}

渲染流程

第一阶段: 初始化与连接

1. cool.html 加载
   
2. global.js 初始化浏览器属性和配置
   
3. 创建 WebSocket 连接
   websocketURI = "ws://collabora-server/cool/{docURL}"
   
4. 发送 load 命令
   "load url={encodedDocURL} lang=zh-CN accessibilityState=..."

第二阶段: 文档加载

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 像素
   }

第三阶段: 瓦片渲染

瓦片请求

// 客户端发送瓦片组合请求
"tilecombine nviewid=0 part=0 width=256 height=256 
 tileposx=0,256,512 tileposy=0,0,0 tilewidth=3840 tileheight=3840"

瓦片接收与处理

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 容器管理)

{
  canvas: HTMLCanvasElement,        // 主 Canvas 元素
  context: CanvasRenderingContext2D, // 2D 渲染上下文
  width: number,                    // Canvas 宽度
  height: number,                   // Canvas 高度
  clearColor: string                // 背景色
}

瓦片绘制到 Canvas

// 核心绘制逻辑
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)

// 服务器发送增量更新而非完整瓦片
if (textMsg.startsWith("delta:")) {
  var deltaObj = parseServerCmd(textMsg);
  // 应用增量到现有瓦片
  applyDelta(tile, deltaObj);
}

关键特性

1. 瓦片缓存机制

TileManager._tiles = new Map();  // 缓存所有已加载的瓦片
// Key: "z:x:y:part:mode"
// Value: { image, wireId, viewId, coords }

2. 视口可见性优化

// 仅请求当前视口内的瓦片
clientvisiblearea = "0;0;{width};{height}"
// 优先级: 视口中心 > 视口边缘 > 视口外

3. 消息队列

global.queueMsg = [];  // 在文档层准备好之前缓存消息
socket.onmessage = function(event) {
  if (typeof socket._onMessage === "function") {
    socket._emptyQueue();
    socket._onMessage(event);
  } else {
    queueMsg.push(event.data);  // 延迟处理
  }
}

4. 多用户协作

// 每个用户视图有独立的 viewId
tileMsgObj.nviewid = 0;  // 当前用户
// 其他用户的光标、选择通过单独的消息同步