New length calculation typing

This commit is contained in:
Ryan McCahan 2022-02-19 13:07:38 -07:00
parent 8003a47925
commit a67a65e4cc
4 changed files with 140 additions and 75 deletions

View File

@ -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<BackgroundPosition> = {
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);
}
};

View File

@ -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<CSSToken>;
}
export interface UnicodeRangeToken extends IToken {
type: TokenType.UNICODE_RANGE_TOKEN;
start: number;

View File

@ -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 [<LengthAnchor>tokens[0], <LengthValue>tokens[1], <LengthAnchor>tokens[2], <LengthValue>tokens[3]];
if (tokens.length == 3) return [<LengthAnchor>tokens[0], <LengthAnchor>tokens[1], <LengthAnchor>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(<LengthPercentage>token.values[0], parent);
let secondValue = getAbsoluteValue(<LengthPercentage>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)) {

View File

@ -67,7 +67,7 @@
<div style="background:url(../../assets/image.jpg) no-repeat right 30px bottom 40px;"></div>
<div style="background:url(../../assets/image.jpg) no-repeat left bottom -40px;"></div>
<div style="background:url(../../assets/image.jpg) no-repeat right 30% top 20%;"></div>
<div style="background:url(../../assets/image.jpg) no-repeat center right 20px;"></div>
<div style="background:url(../../assets/image.jpg) no-repeat center right calc(20px + 1em);"></div>
</div>
<div class="medium">