From a67a65e4cced650802ca4fd085f519d2e9874478 Mon Sep 17 00:00:00 2001 From: Ryan McCahan Date: Sat, 19 Feb 2022 13:07:38 -0700 Subject: [PATCH] New length calculation typing --- .../background-position.ts | 8 +- src/css/syntax/tokenizer.ts | 11 + src/css/types/length-percentage.ts | 194 +++++++++++------- tests/reftests/background/position.html | 2 +- 4 files changed, 140 insertions(+), 75 deletions(-) diff --git a/src/css/property-descriptors/background-position.ts b/src/css/property-descriptors/background-position.ts index 26a1f3a..ed9265b 100644 --- a/src/css/property-descriptors/background-position.ts +++ b/src/css/property-descriptors/background-position.ts @@ -1,6 +1,6 @@ import {PropertyDescriptorParsingType, IPropertyListDescriptor} from '../IPropertyDescriptor'; -import {CSSValue, parseFunctionArgs} from '../syntax/parser'; -import {isLengthPercentage, LengthPercentageTuple, parseLengthPercentageTuple} from '../types/length-percentage'; +import {parseFunctionArgs} from '../syntax/parser'; +import {LengthPercentageTuple, parseLengthPercentageTuple} from '../types/length-percentage'; import {Context} from '../../core/context'; export type BackgroundPosition = BackgroundImagePosition[]; @@ -11,7 +11,7 @@ export const backgroundPosition: IPropertyListDescriptor = { initialValue: '0% 0%', type: PropertyDescriptorParsingType.LIST, prefix: false, - parse: (_context: Context, tokens: CSSValue[]): BackgroundPosition => { - return parseFunctionArgs(tokens); + parse: (_context: Context, tokens: LengthPercentageTuple): BackgroundPosition => { + return parseFunctionArgs(tokens).map(parseLengthPercentageTuple); } }; diff --git a/src/css/syntax/tokenizer.ts b/src/css/syntax/tokenizer.ts index 11bbb03..8be8045 100644 --- a/src/css/syntax/tokenizer.ts +++ b/src/css/syntax/tokenizer.ts @@ -97,6 +97,17 @@ export interface DimensionToken extends IToken { number: number; } +export interface IdentToken extends IToken { + type: TokenType.IDENT_TOKEN; + value: string; +} + +export interface FunctionToken extends IToken { + type: TokenType.FUNCTION; + name: string; + values: Array; +} + export interface UnicodeRangeToken extends IToken { type: TokenType.UNICODE_RANGE_TOKEN; start: number; diff --git a/src/css/types/length-percentage.ts b/src/css/types/length-percentage.ts index 6837d82..947a0f9 100644 --- a/src/css/types/length-percentage.ts +++ b/src/css/types/length-percentage.ts @@ -1,13 +1,30 @@ -import {DimensionToken, FLAG_INTEGER, NumberValueToken, TokenType} from '../syntax/tokenizer'; +import { + DimensionToken, + FLAG_INTEGER, + FunctionToken, + IdentToken, + NumberValueToken, + TokenType +} from '../syntax/tokenizer'; import {CSSValue, isDimensionToken} from '../syntax/parser'; import {isLength} from './length'; export type LengthPercentage = DimensionToken | NumberValueToken; -export type LengthPercentageTuple = [LengthPercentage] | [LengthPercentage, LengthPercentage]; +export type LengthValue = LengthPercentage | IdentToken | FunctionToken; +export type LengthAnchor = IdentToken | LengthValue; +export type LengthPercentageTuple = + | [LengthValue] + | [LengthValue, LengthValue] + | [LengthAnchor, LengthAnchor, LengthAnchor] + | [LengthAnchor, LengthValue, LengthAnchor, LengthValue]; export const isLengthPercentage = (token: CSSValue): token is LengthPercentage => token.type === TokenType.PERCENTAGE_TOKEN || isLength(token); -export const parseLengthPercentageTuple = (tokens: LengthPercentage[]): LengthPercentageTuple => - tokens.length > 1 ? [tokens[0], tokens[1]] : [tokens[0]]; +export const parseLengthPercentageTuple = (tokens: LengthPercentage[]): LengthPercentageTuple => { + if (tokens.length == 4) + return [tokens[0], tokens[1], tokens[2], tokens[3]]; + if (tokens.length == 3) return [tokens[0], tokens[1], tokens[2]]; + return tokens.length > 1 ? [tokens[0], tokens[1]] : [tokens[0]]; +}; export const ZERO_LENGTH: NumberValueToken = { type: TokenType.NUMBER_TOKEN, number: 0, @@ -31,89 +48,123 @@ export const getAbsoluteValueForTuple = ( width: number, height: number ): [number, number] => { + if (tuple.length == 3) { + return resolveThreeValueSyntax(tuple, width, height); + } + + if (tuple.length == 4) { + return resolveFourValueSyntax(tuple, width, height); + } + let [x, y] = tuple; let absoluteX = getAbsoluteValue(x, width); let absoluteY = getAbsoluteValue(typeof y !== 'undefined' ? y : x, height); + return [absoluteX, absoluteY]; +}; + +export const resolveThreeValueSyntax = ( + tuple: [LengthAnchor, LengthAnchor, LengthAnchor], + width: number, + height: number +): [number, number] => { + let absoluteX = 0; + let absoluteY = 0; + // https://developer.mozilla.org/en-US/docs/Web/CSS/background-position#values // With 3-value syntax, there are two keywords, and an offset that modifies the // preceding keyword - if (tuple.length == 3) { - // If the first keyword is "left" or "right", we start with the X position - if (tuple[0].value == 'left' || tuple[0].value == 'right') { - // If the second tuple is an offset, apply it to the X position - if (tuple[1].type == TokenType.PERCENTAGE_TOKEN || tuple[1].type == TokenType.DIMENSION_TOKEN) { - if (tuple[0].value == 'left') absoluteX = getAbsoluteValue(tuple[1], width); - if (tuple[0].value == 'right') absoluteX = width - getAbsoluteValue(tuple[1], width); - } - // If the second tuple is a keyword, X position is left or right - if (tuple[1].type == TokenType.IDENT_TOKEN) { - if (tuple[0].value == 'left') absoluteX = getAbsoluteValue(tuple[0], width); - if (tuple[0].value == 'right') absoluteX = width - getAbsoluteValue(tuple[0], width); - } - - // If the first keyword is left/right and the last is 50%, we know that's - // vertical centering - if (tuple[2].type == TokenType.PERCENTAGE_TOKEN) { - absoluteY = getAbsoluteValue(tuple[2], height); - } + // If the first keyword is "left" or "right", we start with the X position + if (tuple[0].type == TokenType.IDENT_TOKEN && (tuple[0].value == 'left' || tuple[0].value == 'right')) { + // If the second tuple is an offset, apply it to the X position + if (tuple[1].type == TokenType.PERCENTAGE_TOKEN || tuple[1].type == TokenType.DIMENSION_TOKEN) { + if (tuple[0].value == 'left') absoluteX = getAbsoluteValue(tuple[1], width); + if (tuple[0].value == 'right') absoluteX = width - getAbsoluteValue(tuple[1], width); } - // If the third tuple is an offset (meaning the first two are keywords) it modifies - // the second tuple keyword - if (tuple[2].type == TokenType.PERCENTAGE_TOKEN || tuple[2].type == TokenType.DIMENSION_TOKEN) { - if (tuple[1].value == 'top') absoluteY = getAbsoluteValue(tuple[2], height); - if (tuple[1].value == 'bottom') absoluteY = height - getAbsoluteValue(tuple[2], height); - if (tuple[1].value == 'left') absoluteX = getAbsoluteValue(tuple[2], width); - if (tuple[1].value == 'right') absoluteX = width - getAbsoluteValue(tuple[2], width); + // If the second tuple is a keyword, X position is left or right + if (tuple[1].type == TokenType.IDENT_TOKEN) { + if (tuple[0].value == 'left') absoluteX = getAbsoluteValue(tuple[0], width); + if (tuple[0].value == 'right') absoluteX = width - getAbsoluteValue(tuple[0], width); } - // If the first keyword is "center" we check to see if the other keyword is left/right - // to see if this applies to Y, otherwise assume X - if (tuple[0].value == 'center') { - if ( - tuple[1].value !== 'left' && - tuple[1].value !== 'right' && - tuple[2].value !== 'left' && - tuple[2].value !== 'right' - ) { - absoluteX = getAbsoluteValue(tuple[0], width); - } - - if (tuple[1].value == 'left' || tuple[1].value == 'right') { - absoluteY = getAbsoluteValue(tuple[0], height); - } - } - - if (tuple[1].value == 'center' || tuple[2].value == 'center') { - if (tuple[0].value == 'right' || tuple[0].value == 'right') { - absoluteY = getAbsoluteValue(tuple[2], height); - } + // If the first keyword is left/right and the last is 50%, we know that's + // vertical centering + if (tuple[2].type == TokenType.PERCENTAGE_TOKEN) { + absoluteY = getAbsoluteValue(tuple[2], height); } } - // With 4-value syntax, the 1st and 3rd values are keywords, and the 2nd and 4th - // are offsets for each of them respectively - if (tuple.length == 4) { - if (tuple[0].type == TokenType.IDENT_TOKEN) { - if (tuple[0].value == 'left') absoluteX = getAbsoluteValue(tuple[1], width); - if (tuple[0].value == 'right') absoluteX = width - getAbsoluteValue(tuple[1], width); - if (tuple[0].value == 'top') absoluteY = getAbsoluteValue(tuple[1], height); - if (tuple[0].value == 'bottom') absoluteY = height - getAbsoluteValue(tuple[1], height); + // If the third tuple is an offset (meaning the first two are keywords) it modifies + // the second tuple keyword + if ( + tuple[1].type == TokenType.IDENT_TOKEN && + (tuple[2].type == TokenType.PERCENTAGE_TOKEN || tuple[2].type == TokenType.DIMENSION_TOKEN) + ) { + if (tuple[1].value == 'top') absoluteY = getAbsoluteValue(tuple[2], height); + if (tuple[1].value == 'bottom') absoluteY = height - getAbsoluteValue(tuple[2], height); + if (tuple[1].value == 'left') absoluteX = getAbsoluteValue(tuple[2], width); + if (tuple[1].value == 'right') absoluteX = width - getAbsoluteValue(tuple[2], width); + } + + // If the first keyword is "center" we check to see if the other keyword is left/right + // to see if this applies to Y, otherwise assume X + if (tuple[0].type == TokenType.IDENT_TOKEN && tuple[0].value == 'center') { + if ( + (tuple[1].type == TokenType.IDENT_TOKEN && tuple[1].value !== 'left' && tuple[1].value !== 'right') || + (tuple[2].type == TokenType.IDENT_TOKEN && tuple[2].value !== 'left' && tuple[2].value !== 'right') + ) { + absoluteX = getAbsoluteValue(tuple[0], width); } - if (tuple[2].type == TokenType.IDENT_TOKEN) { - if (tuple[2].value == 'top') absoluteY = getAbsoluteValue(tuple[3], height); - if (tuple[2].value == 'bottom') absoluteY = height - getAbsoluteValue(tuple[3], height); - if (tuple[2].value == 'left') absoluteX = getAbsoluteValue(tuple[3], width); - if (tuple[2].value == 'right') absoluteX = width - getAbsoluteValue(tuple[3], width); + if (tuple[1].type == TokenType.IDENT_TOKEN && (tuple[1].value == 'left' || tuple[1].value == 'right')) { + absoluteY = getAbsoluteValue(tuple[0], height); + } + } + + if ( + (tuple[1].type == TokenType.IDENT_TOKEN && tuple[1].value == 'center') || + (tuple[2].type == TokenType.IDENT_TOKEN && tuple[2].value == 'center') + ) { + if (tuple[0].type == TokenType.IDENT_TOKEN && (tuple[0].value == 'right' || tuple[0].value == 'right')) { + absoluteY = getAbsoluteValue(tuple[2], height); } } return [absoluteX, absoluteY]; }; -export const getAbsoluteValue = (token: LengthPercentage, parent: number): number => { + +export const resolveFourValueSyntax = ( + tuple: [LengthAnchor, LengthValue, LengthAnchor, LengthValue], + width: number, + height: number +): [number, number] => { + let absoluteX = 0; + let absoluteY = 0; + + // https://developer.mozilla.org/en-US/docs/Web/CSS/background-position#values + // With 4-value syntax, the 1st and 3rd values are keywords, and the 2nd and 4th + // are offsets for each of them respectively + + if (tuple[0].type == TokenType.IDENT_TOKEN) { + if (tuple[0].value == 'left') absoluteX = getAbsoluteValue(tuple[1], width); + if (tuple[0].value == 'right') absoluteX = width - getAbsoluteValue(tuple[1], width); + if (tuple[0].value == 'top') absoluteY = getAbsoluteValue(tuple[1], height); + if (tuple[0].value == 'bottom') absoluteY = height - getAbsoluteValue(tuple[1], height); + } + + if (tuple[2].type == TokenType.IDENT_TOKEN) { + if (tuple[2].value == 'top') absoluteY = getAbsoluteValue(tuple[3], height); + if (tuple[2].value == 'bottom') absoluteY = height - getAbsoluteValue(tuple[3], height); + if (tuple[2].value == 'left') absoluteX = getAbsoluteValue(tuple[3], width); + if (tuple[2].value == 'right') absoluteX = width - getAbsoluteValue(tuple[3], width); + } + + return [absoluteX, absoluteY]; +}; + +export const getAbsoluteValue = (token: LengthValue, parent: number): number => { if (token.type === TokenType.PERCENTAGE_TOKEN) { return (token.number / 100) * parent; } @@ -130,15 +181,18 @@ export const getAbsoluteValue = (token: LengthPercentage, parent: number): numbe } } - // Firefox translates positions like "right 20px" as calc(100% + 20px) + // Handle (simple) length calculations, e.g. Firefox translates background positions like "right 20px" as calc(100% + 20px) if (token.type === TokenType.FUNCTION) { if (token.name === 'calc' && token.values.length == 5) { - let firstValue = getAbsoluteValue(token.values[0], parent); - let secondValue = getAbsoluteValue(token.values[4], parent); + let firstValue = getAbsoluteValue(token.values[0], parent); + let secondValue = getAbsoluteValue(token.values[4], parent); - if (token.values[2].value == '-') return firstValue - secondValue; - if (token.values[2].value == '+') return firstValue + secondValue; + if (token.values[2].type == TokenType.DELIM_TOKEN && token.values[2].value == '-') + return firstValue - secondValue; + if (token.values[2].type == TokenType.DELIM_TOKEN && token.values[2].value == '+') + return firstValue + secondValue; } + return 0; } if (isDimensionToken(token)) { diff --git a/tests/reftests/background/position.html b/tests/reftests/background/position.html index f8edb9d..7bafdc7 100644 --- a/tests/reftests/background/position.html +++ b/tests/reftests/background/position.html @@ -67,7 +67,7 @@
-
+