9.8 KiB
9.8 KiB
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; // 当前用户
// 其他用户的光标、选择通过单独的消息同步