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

fix: Relative length units such as em and vw used in CSS calc() not working correctly #972

Merged
merged 1 commit into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
73 changes: 54 additions & 19 deletions packages/core/src/vivliostyle/css-cascade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export class CascadeValue {
if (propName && Css.isCustomPropName(propName)) {
return this.value;
}
return CssParser.evaluateCSSToCSS(context, this.value, propName);
return evaluateCSSToCSS(context, this.value, propName);
}

isEnabled(context: Exprs.Context): boolean {
Expand Down Expand Up @@ -576,7 +576,7 @@ export class InheritanceVisitor extends Css.FilterVisitor {

override visitExpr(expr: Css.Expr): Css.Val {
if (this.propName == "font-size") {
const val = CssParser.evaluateCSSToCSS(this.context, expr, this.propName);
const val = evaluateCSSToCSS(this.context, expr, this.propName);
return val.visit(this);
}
return expr;
Expand Down Expand Up @@ -2868,7 +2868,7 @@ export class CascadeInstance {
this.applyVarFilter([this.currentStyle], styler, element);

// Calculate calc()
this.applyCalcFilter(this.currentStyle, styler);
this.applyCalcFilter(this.currentStyle, this.context);

this.applyAttrFilter(element);
const quotesCasc = baseStyle["quotes"] as CascadeValue;
Expand Down Expand Up @@ -3012,7 +3012,7 @@ export class CascadeInstance {
// all variables substituted
const validatorSet = (styler as any)
.validatorSet as CssValidator.ValidatorSet;
const shorthand = validatorSet.shorthands[name]?.clone();
const shorthand = validatorSet?.shorthands[name]?.clone();
if (shorthand) {
if (Css.isDefaultingValue(value)) {
for (const nameLH of shorthand.propList) {
Expand Down Expand Up @@ -3056,16 +3056,13 @@ export class CascadeInstance {
/**
* Calculate all calc() in property values in elementStyle
*/
applyCalcFilter(
elementStyle: ElementStyle,
styler: CssStyler.AbstractStyler,
): void {
const visitor = new CalcFilterVisitor(styler);
applyCalcFilter(elementStyle: ElementStyle, context: Exprs.Context): void {
const visitor = new CalcFilterVisitor(context);
for (const name in elementStyle) {
if (isMapName(name)) {
const pseudoMap = getStyleMap(elementStyle, name);
for (const pseudoName in pseudoMap) {
this.applyCalcFilter(pseudoMap[pseudoName], styler);
this.applyCalcFilter(pseudoMap[pseudoName], context);
}
} else if (isPropName(name) && !Css.isCustomPropName(name)) {
const cascVal = getProp(elementStyle, name);
Expand Down Expand Up @@ -4168,7 +4165,11 @@ export class VarFilterVisitor extends Css.FilterVisitor {
* Convert calc() to its value
*/
export class CalcFilterVisitor extends Css.FilterVisitor {
constructor(public styler: CssStyler.AbstractStyler) {
constructor(
public context: Exprs.Context,
public resolveViewportUnit?: boolean,
public percentRef?: number,
) {
super();
}

Expand All @@ -4179,22 +4180,56 @@ export class CalcFilterVisitor extends Css.FilterVisitor {
return value;
}
const exprText = value.toString().replace(/^calc\b/, "-epubx-expr");
const unitInCalc = exprText.replace(/^.*?\d([a-zA-Z]+\b|%).*$|^.*$/, "$1");
if (/\d(%|em|ex|cap|ch|ic|lh|p?v[whbi]|p?vmin|p?vmax)\W/i.test(exprText)) {
return value;
}
const exprVal = CssParser.parseValue(
(this.styler as any).scope,
null,
new CssTokenizer.Tokenizer(exprText, null),
"",
);
if (exprVal instanceof Css.Expr) {
const exprResult = exprVal.expr.evaluate((this.styler as any).context);
const exprResult = exprVal.expr.evaluate(this.context);
if (typeof exprResult === "number") {
if (unitInCalc && unitInCalc !== "%") {
value = new Css.Numeric(exprResult, "px");
}
// TODO: percentage value, etc.
// FIXME: font-size-relative or viewport relative unit may be incorrect
value = new Css.Numeric(exprResult, "px");
}
}
return value;
}

override visitNumeric(numeric: Css.Numeric): Css.Val {
if (
this.resolveViewportUnit &&
Exprs.isViewportRelativeLengthUnit(numeric.unit)
) {
return new Css.Numeric(
numeric.num * this.context.queryUnitSize(numeric.unit, false),
"px",
);
}
if (typeof this.percentRef === "number" && numeric.unit === "%") {
return new Css.Numeric((numeric.num * this.percentRef) / 100, "px");
}
return numeric;
}
}

export function evaluateCSSToCSS(
context: Exprs.Context,
val: Css.Val,
propName?: string,
percentRef?: number,
): Css.Val {
try {
if (val instanceof Css.Expr) {
return CssParser.evaluateExprToCSS(context, val.expr, propName);
}
if (val instanceof Css.Numeric || val instanceof Css.Func) {
return val.visit(new CalcFilterVisitor(context, true, percentRef));
}
} catch (err) {
Logging.logger.warn(err);
return Css.empty;
}
return val;
}
8 changes: 3 additions & 5 deletions packages/core/src/vivliostyle/css-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1478,11 +1478,9 @@ class SingleBoxMarginBoxSizingParam implements MarginBoxSizingParam {
scope: Exprs.LexicalScope,
private readonly clientLayout: Vtree.ClientLayout,
) {
this.hasAutoSize_ = !PageMaster.toExprAuto(
scope,
style[isHorizontal ? "width" : "height"],
new Exprs.Numeric(scope, 0, "px"),
);
const val = style[isHorizontal ? "width" : "height"];
this.hasAutoSize_ =
!val || val === Css.ident.auto || Css.isDefaultingValue(val);
}

/** @override */
Expand Down
30 changes: 1 addition & 29 deletions packages/core/src/vivliostyle/css-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1850,16 +1850,7 @@ export class Parser {
tokenizer.consume();
continue;
case Action.VAL_NUMERIC:
if (Exprs.isViewportRelativeLengthUnit(token.text)) {
// Treat numeric value with viewport unit as numeric in expr.
valStack.push(
new Css.Expr(
new Exprs.Numeric(handler.getScope(), token.num, token.text),
),
);
} else {
valStack.push(new Css.Numeric(token.num, token.text));
}
valStack.push(new Css.Numeric(token.num, token.text));
tokenizer.consume();
continue;
case Action.VAL_STR:
Expand Down Expand Up @@ -2809,22 +2800,3 @@ export function evaluateExprToCSS(
}
throw new Error("E_UNEXPECTED");
}

/**
* @return val
*/
export function evaluateCSSToCSS(
context: Exprs.Context,
val: Css.Val,
propName?: string,
): Css.Val {
if (val.isExpr()) {
try {
return evaluateExprToCSS(context, (val as Css.Expr).expr, propName);
} catch (err) {
Logging.logger.warn(err);
}
return Css.empty;
}
return val;
}
6 changes: 4 additions & 2 deletions packages/core/src/vivliostyle/exprs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ export class Context {
}

queryVal(scope: LexicalScope, key: string): Result | undefined {
const s = this.scopes[scope.scopeKey];
const s = scope && this.scopes[scope.scopeKey];
return s ? s[key] : undefined;
}

Expand Down Expand Up @@ -609,7 +609,9 @@ export class Val {
return result;
}
result = this.evaluateCore(context);
context.storeVal(this.scope, this.key, result);
if (this.scope) {
context.storeVal(this.scope, this.key, result);
}
return result;
}

Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/vivliostyle/ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ export class StyleInstance
this.styler.cascade.applyVarFilter([pageStyle], this.styler, null);

// Calculate calc()
this.styler.cascade.applyCalcFilter(pageStyle, this.styler);
this.styler.cascade.applyCalcFilter(pageStyle, this.styler.context);

const pageSizeAndBleed = CssPage.evaluatePageSizeAndBleed(
CssPage.resolvePageSizeAndBleed(pageStyle),
Expand Down Expand Up @@ -1787,7 +1787,7 @@ export class StyleInstance
this.styler.cascade.applyVarFilter([cascadedPageStyle], this.styler, null);

// Calculate calc()
this.styler.cascade.applyCalcFilter(cascadedPageStyle, this.styler);
this.styler.cascade.applyCalcFilter(cascadedPageStyle, this.styler.context);

const pageMaster = this.selectPageMaster(cascadedPageStyle);
if (!pageMaster) {
Expand All @@ -1813,6 +1813,7 @@ export class StyleInstance
this,
);
this.setPageSizeAndBleed(evaluatedPageSizeAndBleed, page);

CssPage.addPrinterMarks(
cascadedPageStyle,
evaluatedPageSizeAndBleed,
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/vivliostyle/page-master.ts
Original file line number Diff line number Diff line change
Expand Up @@ -991,15 +991,18 @@ export class PageBoxInstance<P extends PageBox = PageBox<any>> {
}
}
if (val) {
val = CssParser.evaluateCSSToCSS(context, val, name);
val = CssCascade.evaluateCSSToCSS(context, val, name);
}
return val;
}

getPropAsNumber(context: Exprs.Context, name: string): number {
let val = this.style[name];
if (val) {
val = CssParser.evaluateCSSToCSS(context, val, name);
let percentRef = /\b(height|top|bottom)\b/.test(name)
? context.pageAreaHeight ?? context.pageHeight()
: context.pageAreaWidth ?? context.pageWidth();
val = CssCascade.evaluateCSSToCSS(context, val, name, percentRef);
}
return Css.toNumber(val, context);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/vivliostyle/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1249,7 +1249,7 @@ export namespace Vtree {
readonly parentShadow: ShadowContext;
subShadow: ShadowContext;
readonly type: Vtree.ShadowType;
readonly styler: object;
readonly styler: CssStyler.AbstractStyler;

equals(other: ShadowContext): boolean;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/vivliostyle/vgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ export class ViewFactory
// Figure out element's styles
let element = this.sourceNode as Element;
const styler = this.nodeContext.shadowContext
? (this.nodeContext.shadowContext.styler as CssStyler.AbstractStyler)
? this.nodeContext.shadowContext.styler
: this.styler;
let elementStyle = styler.getStyle(element, false);
if (!this.nodeContext.shadowContext) {
Expand Down