Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support cross references by target-counter()/target-counters() #248

Merged
merged 24 commits into from
Jun 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
433284f
Support `target-counter()`/`target-counters()` for counters on elements
kwkbtr May 31, 2016
eb48049
Support `attr()` function in the first argument of `target-counter()`…
kwkbtr May 31, 2016
6b43120
Merge functionalities of `vivliostyle.page.PageCounterStore` into `vi…
kwkbtr May 31, 2016
6936785
[WIP] Support `target-counter()`/`target-counters()` for page-based c…
kwkbtr May 31, 2016
348f350
Refactoring: move duplicate procedures to a separate function
kwkbtr Jun 1, 2016
5c0148f
Refactoring: move a duplicate procedure rendering a single page into …
kwkbtr Jun 1, 2016
9d95b63
`target-counter()`: re-layout pages with page number references when …
kwkbtr Jun 1, 2016
1d9d334
Fix incorrect page reference in some cases
kwkbtr Jun 6, 2016
d5d7125
Refresh page display when a currently displayed page is re-laid out b…
kwkbtr Jun 6, 2016
ede7e10
Merge branch 'master' into target-counter-r#131
kwkbtr Jun 7, 2016
f6211a0
Properly resolves references (by 'target-counter()') to another sourc…
kwkbtr Jun 7, 2016
95e5ab9
Make 'target-counter()` work even if the target URL points to another…
kwkbtr Jun 7, 2016
cf1a2d9
Support 'target-counters()' for an element on a later page
kwkbtr Jun 7, 2016
2767d8f
Optimize 'target-counter()' for a case where the target element comes…
kwkbtr Jun 7, 2016
ff40b52
Optimize 'target-counter()' for a case where the target element comes…
kwkbtr Jun 7, 2016
aed6cf9
Re-layout next page too if page break position has been moved by cros…
kwkbtr Jun 7, 2016
ff87651
Do not save an unresolved reference if the same reference is already …
kwkbtr Jun 7, 2016
f738271
Save page break position right after layout of a single page is done
kwkbtr Jun 7, 2016
5bec940
Re-layout next page too if page break position has been moved by cros…
kwkbtr Jun 8, 2016
ab3d9c9
Mark the unresolved reference as resolved even if no counter with the…
kwkbtr Jun 8, 2016
b0288a3
Prevent reference target from going back to the previous page by refe…
kwkbtr Jun 8, 2016
0d9973b
Refresh cross reference that was already resolved from the beginning …
kwkbtr Jun 8, 2016
6aa423e
Update Change Log
kwkbtr Jun 8, 2016
0d151f1
Update Supported Features
kwkbtr Jun 8, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
- Support `left`/`right`/`recto`/`verso` values for `(page-)break-before`/`(page-)break-after`
- <https://github.com/vivliostyle/vivliostyle.js/issues/25>
- Spec: [CSS Fragmentation - Page Break Values](http://dev.w3.org/csswg/css-break/#page-break-values)
- Support cross references by `target-counter()`/`target-counters()`
- <https://github.com/vivliostyle/vivliostyle.js/pull/248>
- Spec: [CSS Generated Content Module Level 3 - Cross references and the target-* functions](https://drafts.csswg.org/css-content/#cross-references)

### Fixed
- Fix a bug that `clear` is ignored when `white-space` property is used before the element
Expand Down
7 changes: 7 additions & 0 deletions doc/supported-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ Properties where <quote>Allowed prefixes</quote> is indicated may be used with a
- [HSL color values](https://www.w3.org/TR/css3-color/#hsl-color), [HSLA color values](https://www.w3.org/TR/css3-color/#hsla-color)
- [Extended color keywords](https://www.w3.org/TR/css3-color/#svg-color)
- [‘currentColor’ color keyword](https://www.w3.org/TR/css3-color/#currentcolor)
- [Attribute references: `attr()`](https://www.w3.org/TR/css-values/#attr-notation)
- Supported in all browsers
- Only supported in values of `content` property.
- Only 'string' and 'url' types are supported.
- [Cross references: `target-counter()` and `target-counters()`](https://drafts.csswg.org/css-content/#cross-references)
- Supported in all browsers
- Only supported in values of `content` property.

## Selectors

Expand Down
4 changes: 4 additions & 0 deletions resources/validation.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ ATTR = attr(SPACE(IDENT TYPE_OR_UNIT_IN_ATTR?) [ STRING | IDENT | COLOR | INT |
CONTENT = normal | none |
[ STRING | URI | counter(IDENT LIST_STYLE_TYPE?) |
counters(IDENT STRING LIST_STYLE_TYPE?) | ATTR |
target-counter([ STRING | URI ] IDENT LIST_STYLE_TYPE?) |
target-counter(ATTR IDENT LIST_STYLE_TYPE?) |
target-counters([ STRING | URI ] IDENT STRING LIST_STYLE_TYPE?) |
target-counters(ATTR IDENT STRING LIST_STYLE_TYPE?) |
open-quote | close-quote | no-open-quote | no-close-quote ]+;
content = CONTENT;
COUNTER = [ IDENT INT? ]+ | none;
Expand Down
139 changes: 124 additions & 15 deletions src/adapt/csscasc.js
Original file line number Diff line number Diff line change
Expand Up @@ -1733,26 +1733,59 @@ adapt.csscasc.QuotesScopeItem.prototype.pop = function(cascade, depth) {
};

/**
* Interface representing an object which can resolve a page-based counter by its name.
* Represent a container for values of counters.
* Key=name of a counter, Value=values of a corresponding counter ordered from outermost to innermost
* @typedef {Object<string, !Array<number>>}
*/
adapt.csscasc.CounterValues;

/**
* @interface
*/
adapt.csscasc.PageCounterResolver = function() {};
adapt.csscasc.CounterListener = function() {};

/**
* @param {string} id
* @param {!adapt.csscasc.CounterValues} counters
*/
adapt.csscasc.CounterListener.prototype.countersOfId = function(id, counters) {};

/**
* @interface
*/
adapt.csscasc.CounterResolver = function() {};

/**
* Returns an adapt.expr.Val, whose value is calculated at the layout time by retrieving the innermost page-based counter (null if it does not exist) by its name and formatting the value into a string.
* @param {string} name Name of the page-based counter to be retrieved
* @param {function(?number):string} format A function that formats the counter value into a string
* @returns {adapt.expr.Val}
*/
adapt.csscasc.PageCounterResolver.prototype.getCounterVal = function(name, format) {};
adapt.csscasc.CounterResolver.prototype.getPageCounterVal = function(name, format) {};

/**
* Returns an adapt.expr.Val, whose value is calculated at the layout time by retrieving the page-based counters by its name and formatting the values into a string.
* @param {string} name Name of the page-based counters to be retrieved
* @param {function(!Array.<number>):string} format A function that formats the counter values (passed as an array ordered by the nesting depth with the outermost counter first and the innermost last) into a string
* @returns {adapt.expr.Val}
*/
adapt.csscasc.PageCounterResolver.prototype.getCountersVal = function(name, format) {};
adapt.csscasc.CounterResolver.prototype.getPageCountersVal = function(name, format) {};

/**
* @param {string} url
* @param {string} name
* @param {function(?number):string} format
* @returns {!adapt.expr.Val}
*/
adapt.csscasc.CounterResolver.prototype.getTargetCounterVal = function(url, name, format) {};

/**
* @param {string} url
* @param {string} name
* @param {function(!Array<number>):string} format
* @returns {!adapt.expr.Val}
*/
adapt.csscasc.CounterResolver.prototype.getTargetCountersVal = function(url, name, format) {};

/**
* @constructor
Expand Down Expand Up @@ -1819,12 +1852,14 @@ adapt.csscasc.AttrValueFilterVisitor.prototype.visitFunc = function(func) {
* @constructor
* @param {adapt.csscasc.CascadeInstance} cascade
* @param {Element} element
* @param {!adapt.csscasc.CounterResolver} counterResolver
* @extends {adapt.css.FilterVisitor}
*/
adapt.csscasc.ContentPropVisitor = function(cascade, element) {
adapt.csscasc.ContentPropVisitor = function(cascade, element, counterResolver) {
adapt.css.FilterVisitor.call(this);
this.cascade = cascade;
this.element = element;
/** @const */ this.counterResolver = counterResolver;
};
goog.inherits(adapt.csscasc.ContentPropVisitor, adapt.css.FilterVisitor);

Expand Down Expand Up @@ -2107,7 +2142,7 @@ adapt.csscasc.ContentPropVisitor.prototype.visitFuncCounter = function(values) {
return new adapt.css.Str(this.format(numval, type));
} else {
var self = this;
var c = new adapt.css.Expr(this.cascade.pageCounterResolver.getCounterVal(counterName, function(numval) {
var c = new adapt.css.Expr(this.counterResolver.getPageCounterVal(counterName, function(numval) {
return self.format(numval || 0, type);
}));
return new adapt.css.SpaceList([c]);
Expand All @@ -2132,7 +2167,7 @@ adapt.csscasc.ContentPropVisitor.prototype.visitFuncCounters = function(values)
}
}
var self = this;
var c = new adapt.css.Expr(this.cascade.pageCounterResolver.getCountersVal(counterName, function(numvals) {
var c = new adapt.css.Expr(this.counterResolver.getPageCountersVal(counterName, function(numvals) {
var parts = /** @type {Array.<string>} */ ([]);
if (numvals.length) {
for (var i = 0; i < numvals.length; i++) {
Expand All @@ -2152,6 +2187,58 @@ adapt.csscasc.ContentPropVisitor.prototype.visitFuncCounters = function(values)
return new adapt.css.SpaceList([c]);
};

/**
* @param {Array.<adapt.css.Val>} values
* @return {adapt.css.Val}
*/
adapt.csscasc.ContentPropVisitor.prototype.visitFuncTargetCounter = function(values) {
var targetUrl = values[0];
var targetUrlStr;
if (targetUrl instanceof adapt.css.URL) {
targetUrlStr = targetUrl.url;
} else {
targetUrlStr = targetUrl.stringValue();
}
var counterName = values[1].toString();
var type = values.length > 2 ? values[2].stringValue() : "decimal";

var self = this;
var c = new adapt.css.Expr(this.counterResolver.getTargetCounterVal(targetUrlStr, counterName, function(numval) {
return self.format(numval || 0, type);
}));
return new adapt.css.SpaceList([c]);
};

/**
* @param {Array<adapt.css.Val>} values
* @returns {adapt.css.Val}
*/
adapt.csscasc.ContentPropVisitor.prototype.visitFuncTargetCounters = function(values) {
var targetUrl = values[0];
var targetUrlStr;
if (targetUrl instanceof adapt.css.URL) {
targetUrlStr = targetUrl.url;
} else {
targetUrlStr = targetUrl.stringValue();
}
var counterName = values[1].toString();
var separator = values[2].stringValue();
var type = values.length > 3 ? values[3].stringValue() : "decimal";

var self = this;
var c = new adapt.css.Expr(this.counterResolver.getTargetCountersVal(targetUrlStr, counterName, function(numvals) {
var parts = numvals.map(function(numval) {
return self.format(numval, type);
});
if (parts.length) {
return parts.join(separator);
} else {
return self.format(0, type);
}
}));
return new adapt.css.SpaceList([c]);
};

/**
* @override
*/
Expand All @@ -2167,6 +2254,16 @@ adapt.csscasc.ContentPropVisitor.prototype.visitFunc = function(func) {
return this.visitFuncCounters(func.values);
}
break;
case "target-counter":
if (func.values.length <= 3) {
return this.visitFuncTargetCounter(func.values);
}
break;
case "target-counters":
if (func.values.length <= 4) {
return this.visitFuncTargetCounters(func.values);
}
break;
}
vivliostyle.logging.logger.warn("E_CSS_CONTENT_PROP:", func.toString());
return new adapt.css.Str("");
Expand Down Expand Up @@ -2245,11 +2342,12 @@ adapt.csscasc.Cascade.prototype.insertInTable = function(table, key, action) {

/**
* @param {adapt.expr.Context} context
* @param {adapt.csscasc.PageCounterResolver} pageCounterResolver
* @param {!adapt.csscasc.CounterListener} counterListener
* @param {!adapt.csscasc.CounterResolver} counterResolver
* @return {adapt.csscasc.CascadeInstance}
*/
adapt.csscasc.Cascade.prototype.createInstance = function(context, pageCounterResolver, lang) {
return new adapt.csscasc.CascadeInstance(this, context, pageCounterResolver, lang);
adapt.csscasc.Cascade.prototype.createInstance = function(context, counterListener, counterResolver, lang) {
return new adapt.csscasc.CascadeInstance(this, context, counterListener, counterResolver, lang);
};

/**
Expand All @@ -2263,14 +2361,16 @@ adapt.csscasc.Cascade.prototype.nextOrder = function() {
/**
* @param {adapt.csscasc.Cascade} cascade
* @param {adapt.expr.Context} context
* @param {adapt.csscasc.PageCounterResolver} pageCounterResolver
* @param {!adapt.csscasc.CounterListener} counterListener
* @param {!adapt.csscasc.CounterResolver} counterResolver
* @param {string} lang
* @constructor
*/
adapt.csscasc.CascadeInstance = function(cascade, context, pageCounterResolver, lang) {
adapt.csscasc.CascadeInstance = function(cascade, context, counterListener, counterResolver, lang) {
/** @const */ this.code = cascade;
/** @const */ this.context = context;
/** @const */ this.pageCounterResolver = pageCounterResolver;
/** @const */ this.counterListener = counterListener;
/** @const */ this.counterResolver = counterResolver;
/** @const */ this.stack = /** @type {Array.<Array.<adapt.csscasc.ConditionItem>>} */ ([[],[]]);
/** @const */ this.conditions = /** @type {Object.<string,number>} */ ({});
/** @type {Element} */ this.currentElement = null;
Expand All @@ -2285,7 +2385,7 @@ adapt.csscasc.CascadeInstance = function(cascade, context, pageCounterResolver,
/** @type {?string} */ this.currentPageType = null;
/** @type {boolean} */ this.isFirst = true;
/** @type {boolean} */ this.isRoot = true;
/** @type {Object.<string,Array.<number>>} */ this.counters = {};
/** @type {!Object.<string,!Array.<number>>} */ this.counters = {};
/** @type {Array.<Object.<string,boolean>>} */ this.counterScoping = [{}];
/** @type {Array.<adapt.css.Str>} */ this.quotes =
[new adapt.css.Str("\u201C"), new adapt.css.Str("\u201D"),
Expand Down Expand Up @@ -2469,7 +2569,7 @@ adapt.csscasc.CascadeInstance.prototype.processPseudoelementProps = function(pse
this.pushCounters(pseudoprops);
if (pseudoprops["content"]) {
pseudoprops["content"] = pseudoprops["content"].filterValue(
new adapt.csscasc.ContentPropVisitor(this, element));
new adapt.csscasc.ContentPropVisitor(this, element, this.counterResolver));
}
this.popCounters();
};
Expand Down Expand Up @@ -2532,6 +2632,7 @@ adapt.csscasc.CascadeInstance.prototype.pushElement = function(element, baseStyl
new adapt.csscasc.RestoreLangItem(this.lang));
this.lang = lang.toLowerCase();
}
var isRoot = this.isRoot;

var siblingOrderStack = this.siblingOrderStack;
this.currentSiblingOrder = ++siblingOrderStack[siblingOrderStack.length - 1];
Expand Down Expand Up @@ -2577,6 +2678,14 @@ adapt.csscasc.CascadeInstance.prototype.pushElement = function(element, baseStyl
}
}
this.pushCounters(this.currentStyle);
var id = this.currentId || this.currentXmlId || "";
if (isRoot || id) {
/** @type {!Object<string, Array<number>>} */ var counters = {};
Object.keys(this.counters).forEach(function(name) {
counters[name] = Array.from(this.counters[name]);
}, this);
this.counterListener.countersOfId(id, counters);
}
var pseudos = adapt.csscasc.getStyleMap(this.currentStyle, "_pseudos");
if (pseudos) {
var before = true;
Expand Down
29 changes: 26 additions & 3 deletions src/adapt/cssstyler.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,11 +423,12 @@ adapt.cssstyler.BoxStack.prototype.nearestBlockStartOffset = function(box) {
* @param {adapt.expr.Context} context
* @param {Object.<string,boolean>} primaryFlows
* @param {adapt.cssvalid.ValidatorSet} validatorSet
* @param {adapt.csscasc.PageCounterResolver} pageCounterResolver
* @param {!adapt.csscasc.CounterListener} counterListener
* @param {!adapt.csscasc.CounterResolver} counterResolver
* @constructor
* @implements {adapt.cssstyler.AbstractStyler}
*/
adapt.cssstyler.Styler = function(xmldoc, cascade, scope, context, primaryFlows, validatorSet, pageCounterResolver) {
adapt.cssstyler.Styler = function(xmldoc, cascade, scope, context, primaryFlows, validatorSet, counterListener, counterResolver) {
/** @const */ this.xmldoc = xmldoc;
/** @const */ this.root = xmldoc.root;
/** @const */ this.cascadeHolder = cascade;
Expand All @@ -441,7 +442,8 @@ adapt.cssstyler.Styler = function(xmldoc, cascade, scope, context, primaryFlows,
/** @const */ this.flowChunks = /** @type {Array.<adapt.vtree.FlowChunk>} */ ([]);
/** @type {adapt.cssstyler.FlowListener} */ this.flowListener = null;
/** @type {?string} */ this.flowToReach = null;
/** @const */ this.cascade = cascade.createInstance(context, pageCounterResolver, xmldoc.lang);
/** @type {?string} */ this.idToReach = null;
/** @const */ this.cascade = cascade.createInstance(context, counterListener, counterResolver, xmldoc.lang);
/** @const */ this.offsetMap = new adapt.cssstyler.SlipMap();
/** @type {boolean} */ this.primary = true;
/** @const */ this.primaryStack = /** @type {Array.<boolean>} */ ([]);
Expand Down Expand Up @@ -695,6 +697,23 @@ adapt.cssstyler.Styler.prototype.styleUntilFlowIsReached = function(flowName) {
}
};

/**
* @param {string} id
*/
adapt.cssstyler.Styler.prototype.styleUntilIdIsReached = function(id) {
if (!id) return;
this.idToReach = id;
var offset = 0;
while (true) {
if (!this.idToReach)
break;
offset += 5000;
if (this.styleUntil(offset, 0) === Number.POSITIVE_INFINITY)
break;
}
this.idToReach = null;
};

/**
* @private
* @param {string} flowName
Expand Down Expand Up @@ -829,6 +848,10 @@ adapt.cssstyler.Styler.prototype.styleUntil = function(startOffset, lookup) {
var style = this.getAttrStyle(elem);
this.primaryStack.push(this.primary);
this.cascade.pushElement(elem, style);
var id = elem.getAttribute("id") || elem.getAttributeNS(adapt.base.NS.XML, "id");
if (id && id === this.idToReach) {
this.idToReach = null;
}
if (!this.bodyReached && elem.localName == "body" && elem.parentNode == this.root) {
this.postprocessTopStyle(style, true);
this.bodyReached = true;
Expand Down
Loading