mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00

* initial typescript conversion * test: update overflow+transform ref test * fix: correctly render pseudo element content * fix: testrunner build * fix: karma test urls * test: update underline tests with <u> elements * test: update to es6-promise polyfill * test: remove watch from server * test: remove flow * format: update prettier for typescript * test: update eslint to use typescript parser * test: update linear gradient reftest * test: update test runner * test: update testrunner promise polyfill * fix: handle display: -webkit-flex correctly (fix #1817) * fix: correctly render gradients with clip & repeat (fix #1773) * fix: webkit-gradient function support * fix: implement radial gradients * fix: text-decoration rendering * fix: missing scroll positions for elements * ci: fix ios 11 tests * fix: ie logging * ci: improve device availability logging * fix: lint errors * ci: update to ios 12 * fix: check for console availability * ci: fix build dependency * test: update text reftests * fix: window reference for unit tests * feat: add hsl/hsla color support * fix: render options * fix: CSSKeyframesRule cssText Permission Denied on Internet Explorer 11 (#1830) * fix: option lint * fix: list type rendering * test: fix platform import * fix: ie css parsing for numbers * ci: add minified build * fix: form element rendering * fix: iframe rendering * fix: re-introduce experimental foreignobject renderer * fix: text-shadow rendering * feat: improve logging * fix: unit test logging * fix: cleanup resources * test: update overflow scrolling to work with ie * build: update build to include typings * fix: do not parse select element children * test: fix onclone test to work with older IEs * test: reduce reftest canvas sizes * test: remove dynamic setUp from list tests * test: update linear-gradient tests * build: remove old source files * build: update docs dependencies * build: fix typescript definition path * ci: include test.js on docs website
189 lines
5.7 KiB
TypeScript
189 lines
5.7 KiB
TypeScript
import {
|
|
CSSToken,
|
|
DimensionToken,
|
|
EOF_TOKEN,
|
|
NumberValueToken,
|
|
StringValueToken,
|
|
Tokenizer,
|
|
TokenType
|
|
} from './tokenizer';
|
|
|
|
export type CSSBlockType =
|
|
| TokenType.LEFT_PARENTHESIS_TOKEN
|
|
| TokenType.LEFT_SQUARE_BRACKET_TOKEN
|
|
| TokenType.LEFT_CURLY_BRACKET_TOKEN;
|
|
|
|
export interface CSSBlock {
|
|
type: CSSBlockType;
|
|
values: CSSValue[];
|
|
}
|
|
|
|
export interface CSSFunction {
|
|
type: TokenType.FUNCTION;
|
|
name: string;
|
|
values: CSSValue[];
|
|
}
|
|
|
|
export type CSSValue = CSSFunction | CSSToken | CSSBlock;
|
|
|
|
export class Parser {
|
|
private _tokens: CSSToken[];
|
|
|
|
constructor(tokens: CSSToken[]) {
|
|
this._tokens = tokens;
|
|
}
|
|
|
|
static create(value: string): Parser {
|
|
const tokenizer = new Tokenizer();
|
|
tokenizer.write(value);
|
|
return new Parser(tokenizer.read());
|
|
}
|
|
|
|
static parseValue(value: string): CSSValue {
|
|
return Parser.create(value).parseComponentValue();
|
|
}
|
|
|
|
static parseValues(value: string): CSSValue[] {
|
|
return Parser.create(value).parseComponentValues();
|
|
}
|
|
|
|
parseComponentValue(): CSSValue {
|
|
let token = this.consumeToken();
|
|
while (token.type === TokenType.WHITESPACE_TOKEN) {
|
|
token = this.consumeToken();
|
|
}
|
|
|
|
if (token.type === TokenType.EOF_TOKEN) {
|
|
throw new SyntaxError(`Error parsing CSS component value, unexpected EOF`);
|
|
}
|
|
|
|
this.reconsumeToken(token);
|
|
const value = this.consumeComponentValue();
|
|
|
|
do {
|
|
token = this.consumeToken();
|
|
} while (token.type === TokenType.WHITESPACE_TOKEN);
|
|
|
|
if (token.type === TokenType.EOF_TOKEN) {
|
|
return value;
|
|
}
|
|
|
|
throw new SyntaxError(`Error parsing CSS component value, multiple values found when expecting only one`);
|
|
}
|
|
|
|
parseComponentValues(): CSSValue[] {
|
|
const values = [];
|
|
while (true) {
|
|
let value = this.consumeComponentValue();
|
|
if (value.type === TokenType.EOF_TOKEN) {
|
|
return values;
|
|
}
|
|
values.push(value);
|
|
values.push();
|
|
}
|
|
}
|
|
|
|
private consumeComponentValue(): CSSValue {
|
|
const token = this.consumeToken();
|
|
|
|
switch (token.type) {
|
|
case TokenType.LEFT_CURLY_BRACKET_TOKEN:
|
|
case TokenType.LEFT_SQUARE_BRACKET_TOKEN:
|
|
case TokenType.LEFT_PARENTHESIS_TOKEN:
|
|
return this.consumeSimpleBlock(token.type);
|
|
case TokenType.FUNCTION_TOKEN:
|
|
return this.consumeFunction(token);
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
private consumeSimpleBlock(type: CSSBlockType): CSSBlock {
|
|
const block: CSSBlock = {type, values: []};
|
|
|
|
let token = this.consumeToken();
|
|
while (true) {
|
|
if (token.type === TokenType.EOF_TOKEN || isEndingTokenFor(token, type)) {
|
|
return block;
|
|
}
|
|
|
|
this.reconsumeToken(token);
|
|
block.values.push(this.consumeComponentValue());
|
|
token = this.consumeToken();
|
|
}
|
|
}
|
|
|
|
private consumeFunction(functionToken: StringValueToken): CSSFunction {
|
|
const cssFunction: CSSFunction = {
|
|
name: functionToken.value,
|
|
values: [],
|
|
type: TokenType.FUNCTION
|
|
};
|
|
|
|
while (true) {
|
|
const token = this.consumeToken();
|
|
if (token.type === TokenType.EOF_TOKEN || token.type === TokenType.RIGHT_PARENTHESIS_TOKEN) {
|
|
return cssFunction;
|
|
}
|
|
|
|
this.reconsumeToken(token);
|
|
cssFunction.values.push(this.consumeComponentValue());
|
|
}
|
|
}
|
|
|
|
private consumeToken(): CSSToken {
|
|
const token = this._tokens.shift();
|
|
return typeof token === 'undefined' ? EOF_TOKEN : token;
|
|
}
|
|
|
|
private reconsumeToken(token: CSSToken): void {
|
|
this._tokens.unshift(token);
|
|
}
|
|
}
|
|
|
|
export const isDimensionToken = (token: CSSValue): token is DimensionToken => token.type === TokenType.DIMENSION_TOKEN;
|
|
export const isNumberToken = (token: CSSValue): token is NumberValueToken => token.type === TokenType.NUMBER_TOKEN;
|
|
export const isIdentToken = (token: CSSValue): token is StringValueToken => token.type === TokenType.IDENT_TOKEN;
|
|
export const isStringToken = (token: CSSValue): token is StringValueToken => token.type === TokenType.STRING_TOKEN;
|
|
export const isIdentWithValue = (token: CSSValue, value: string): boolean =>
|
|
isIdentToken(token) && token.value === value;
|
|
|
|
export const nonWhiteSpace = (token: CSSValue) => token.type !== TokenType.WHITESPACE_TOKEN;
|
|
export const nonFunctionArgSeperator = (token: CSSValue) =>
|
|
token.type !== TokenType.WHITESPACE_TOKEN && token.type !== TokenType.COMMA_TOKEN;
|
|
|
|
export const parseFunctionArgs = (tokens: CSSValue[]): CSSValue[][] => {
|
|
const args: CSSValue[][] = [];
|
|
let arg: CSSValue[] = [];
|
|
tokens.forEach(token => {
|
|
if (token.type === TokenType.COMMA_TOKEN) {
|
|
if (arg.length === 0) {
|
|
throw new Error(`Error parsing function args, zero tokens for arg`);
|
|
}
|
|
args.push(arg);
|
|
arg = [];
|
|
return;
|
|
}
|
|
|
|
if (token.type !== TokenType.WHITESPACE_TOKEN) {
|
|
arg.push(token);
|
|
}
|
|
});
|
|
if (arg.length) {
|
|
args.push(arg);
|
|
}
|
|
|
|
return args;
|
|
};
|
|
|
|
const isEndingTokenFor = (token: CSSToken, type: CSSBlockType): boolean => {
|
|
if (type === TokenType.LEFT_CURLY_BRACKET_TOKEN && token.type === TokenType.RIGHT_CURLY_BRACKET_TOKEN) {
|
|
return true;
|
|
}
|
|
if (type === TokenType.LEFT_SQUARE_BRACKET_TOKEN && token.type === TokenType.RIGHT_SQUARE_BRACKET_TOKEN) {
|
|
return true;
|
|
}
|
|
|
|
return type === TokenType.LEFT_PARENTHESIS_TOKEN && token.type === TokenType.RIGHT_PARENTHESIS_TOKEN;
|
|
};
|