简单来说,TreeSaver有个函数:treesaver.layout.Grid.best = function(content, grids, breakRecord) { 这个函数根据内容,选择最合适的模板。
这个函数的逻辑就是TreeSaver的模板匹配算法。
下面是对这个函数以及调用他的分析:
对于一篇文章来说,分页运算的逻辑在下面文件的 treesaver.ui.Article.prototype.paginateAsync 函数中。
https://github.com/Treesaver/treesaver/blob/master/src/ui/article.js
treesaver.ui.Article.prototype.paginateAsync 函数延迟2秒后使用调度程序对文章解析分页。
在TreeSaver中,Grid的定义就是模板的定义。(其他部分每个分页,每篇文章都是一样的。)
在TreeSaver的资源定义中,我们可以看到有很多个Grid定义,那个Grid适合我们的模板,是根据下面逻辑进行判断的。
https://github.com/Treesaver/treesaver/blob/master/src/layout/grid.js
一、知道浏览器可以显示区域的大小。
这里的区域不是浏览器的内容区域,而是资源定义中viewer的区域,viewer区域显示的内容会同一篇文章每个页面都不一样。
二、根据浏览器特征,对候选模板Grid进行过滤,排序
这部分的代码如下:
/**
* Stretch the grids into appropriate heights, and filter out any grids* which do not fit. Return the stretched subset of grids in an array* @param {{ w: number, h: number }} size* @return {Array.<treesaver.layout.Grid>}*/treesaver.ui.Article.prototype.stretchGrids = function(size) {this.eligible_grids = this.grids.filter(function(grid) {return grid.capabilityFilter() && grid.sizeFilter(size);
}).map(function(grid) {
// Now stretch to the space
return grid.stretch(size.h);
});// Are there any grids?
if (!this.eligible_grids.length) {treesaver.debug.error('No eligible grids at ' + size.w + 'x' + size.h);}// Sort by highest text height (helps with shortcutting in scoring)
this.eligible_grids.sort(treesaver.layout.Grid.sort);
};
过滤策略:
- 如果内容对 data-requires 有限制,对应的Grid是否适合这种数据约束,比如内容是Flash的,模板Grid中也应该是能用来显示这种data-requires的模板。data-requires有哪些枚举,是自己配置的,只需要你保证模板和内容上都有你新增的数据约束即可。
treesaver.layout.Grid.prototype.capabilityFilter = function() { 函数就是做这个判断逻辑的。 - View区域大小限制了只能用那些模板。
这个逻辑判断在下面函数中:treesaver.layout.Grid.prototype.sizeFilter = function(size) {, 输入参数为View区域的大小。 - 对通过上面两个过滤的模板Grid数组进行排序,根据Grid的column和container格式降序排列。
三、遍历文章内容,产生分页,下面就是这个逻辑的主要框架部分代码。
遍历逻辑的核心代码如下,即依次建立每一个Page对象。
/**
* Paginate the article asynchronously* @param {boolean} bg Paginate remainder of article in background.* @param {number} index Paginate synchronously until this index.* @param {?treesaver.layout.ContentPosition|number} pos Paginate synchronously until this position.* @private*/treesaver.ui.Article.prototype.paginate = function(bg, index, pos) {//.......
var page;
index = index || 0;while (!this.br.finished) {page = new treesaver.layout.Page(
/** @type {!treesaver.layout.Content } */ (this.content),this.eligible_grids,
/** @type {!treesaver.layout.BreakRecord} */ (this.br));//.......
}//.......
}
Page对象的构造函数中,第一句就是挑选合适的模板Grid,然后根据挑选的模板组织页面,代码如下:
https://github.com/Treesaver/treesaver/blob/master/src/layout/page.js
/**
* Page class* @constructor* @param {!treesaver.layout.Content} content* @param {!Array.<treesaver.layout.Grid>} grids* @param {!treesaver.layout.BreakRecord} br The current breakRecord.*/treesaver.layout.Page = function(content, grids, br) {
var best = treesaver.layout.Grid.best(content, grids, br),
host = document.createElement('div'),
originalBr = br.clone(),containerFilled = false;
// ........
}
计算最佳模板,就涉及到了模板权重评分计算。
对于一个模板,它的权重计算逻辑如下:
- Column 文本显示的分栏,一栏一个Column。计算模板权重时,一个Column,权重多加50分。模板中有几个Column,权重分数就增加几倍的50。
- 如果模板Grid的lineHeight和内容的lineHeight不一样,则权重分数扣除2000分。
- 如果模板Grid的colWidth和内容的colWidth不一致,则权重分数扣除无限大(Infinity)。
- 如果模板中标识了onlypage(用于文章的封面,只能用于唯一且第一页),如果当前页面是第一页,权重加4000,否则权重减无穷大。
- 如果模板强制指定了适合那一页,发现当前是该页时,权重增加3000,如果定义了,但是不是该页则权重减无穷大。
模板定义页面的匹配正则是:treesaver.layout.Grid.pageFlagRegex = /^(no-)?page-(\d+)$/; - 如果模板定义了不适合于那一页,发现不适合时,权重减无穷大;不适合的模板class 是 no-page- 开头的。
- 如果模板定义了适合偶数页(even),则在偶数页时权重增加2000,奇数页时权重减无穷大。
- 如果模板定义了适合奇数页(odd),则在奇数页时权重增加2000,偶数时权重减无穷大。
以上模板权重评分分数计算在 treesaver.layout.Grid.prototype.score = function(content, breakRecord) { 函数中实现。注意以上只算了 模板的 Column 信息,没有计算模板的 Container 信息。
下一步我们遍历模板中每一个container,看内容中定义的的figure是否适合它。
这个算法在下面几个函数中实现:
treesaver.layout.Grid.prototype.mapContainers = function(content, br) {
- 如果模板中定义了固定部分(container),则模板权重增加 2000+最小高度×5 .优先使用有图片的模板。
- 如果内容页,figure是可选择的图片的,则权重增加 4000. 图片具有自适应,优先出现。
- 如果模板中定义没有使用了fixed,( !container.flexible) 权重增加 5000.
Grid模板描述语言中的子元素:
- Container, 图片及非自动填充的内容。For positioning images and other non-flowing content,
在内容中,figure控件对应的就是Container。 - Column 需要自动填充的内容。For flowing content
- runningtitle ?
内容集合
https://github.com/Treesaver/treesaver/blob/master/src/layout/content.js
- figures 集合(不能自动填充的内容的集合,图片,引用等)
- blocks 集合(分段的文本)