-
Notifications
You must be signed in to change notification settings - Fork 159
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ThemeMagic: Add cssjanus to transform RTL language
- Loading branch information
Showing
4 changed files
with
296 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,277 @@ | ||
/** | ||
* Creates a CSSJanus object. | ||
* | ||
* CSSJanus transforms CSS rules with horizontal relevance so that a left-to-right stylesheet can | ||
* become a right-to-left stylesheet automatically. Processing can be bypassed for an entire rule | ||
* or a single property by adding a / * @noflip * / comment above the rule or property. | ||
* | ||
* @author "Trevor Parscal" <[email protected]> | ||
* @author "Roan Kattouw" <[email protected]> | ||
* @author "Lindsey Simon" <[email protected]> | ||
* @author "Roozbeh Pournader" <[email protected]> | ||
* @author "Bryon Engelhardt" <[email protected]> | ||
* | ||
* @class | ||
* @constructor | ||
* @param {RegExp} regex Regular expression whose matches to replace by a token | ||
* @param {String} token Placeholder text | ||
*/ | ||
function CSSJanus() { | ||
|
||
/* Private Members */ | ||
|
||
var prepared = false, | ||
// Tokens | ||
temporaryToken = '`TMP`', | ||
noFlipSingleToken = '`NOFLIP_SINGLE`', | ||
noFlipClassToken = '`NOFLIP_CLASS`', | ||
commentToken = '`COMMENT`', | ||
// Patterns | ||
nonAsciiPattern = '[^\\u0020-\\u007e]', | ||
unicodePattern = '(?:(?:\\[0-9a-f]{1,6})(?:\\r\\n|\\s)?)', | ||
numPattern = '(?:[0-9]*\\.[0-9]+|[0-9]+)', | ||
unitPattern = '(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)', | ||
directionPattern = 'direction\\s*:\\s*', | ||
urlSpecialCharsPattern = '[!#$%&*-~]', | ||
validAfterUriCharsPattern = '[\'"]?\\s*', | ||
nonLetterPattern = '(^|[^a-zA-Z])', | ||
charsWithinSelectorPattern = '[^\\}]*?', | ||
noFlipPattern = '\\/\\*\\s*@noflip\\s*\\*\\/', | ||
commentPattern = '\\/\\*[^*]*\\*+([^\\/*][^*]*\\*+)*\\/', | ||
escapePattern = '(?:' + unicodePattern + '|\\\\[^\\r\\n\\f0-9a-f])', | ||
nmstartPattern = '(?:[_a-z]|' + nonAsciiPattern + '|' + escapePattern + ')', | ||
nmcharPattern = '(?:[_a-z0-9-]|' + nonAsciiPattern + '|' + escapePattern + ')', | ||
identPattern = '-?' + nmstartPattern + nmcharPattern + '*', | ||
quantPattern = numPattern + '(?:\\s*' + unitPattern + '|' + identPattern + ')?', | ||
signedQuantPattern = '((?:-?' + quantPattern + ')|(?:inherit|auto))', | ||
fourNotationQuantPropsPattern = '((?:margin|padding|border-width)\\s*:\\s*)', | ||
fourNotationColorPropsPattern = '(-color\\s*:\\s*)', | ||
colorPattern = '(#?' + nmcharPattern + '+)', | ||
urlCharsPattern = '(?:' + urlSpecialCharsPattern + '|' + nonAsciiPattern + '|' + escapePattern + ')*', | ||
lookAheadNotOpenBracePattern = '(?!(' + nmcharPattern + '|\\r?\\n|\\s|#|\\:|\\.|\\,|\\+|>)*?{)', | ||
lookAheadNotClosingParenPattern = '(?!' + urlCharsPattern + '?' + validAfterUriCharsPattern + '\\))', | ||
lookAheadForClosingParenPattern = '(?=' + urlCharsPattern + '?' + validAfterUriCharsPattern + '\\))', | ||
// Regular expressions | ||
temporaryTokenRegExp = new RegExp( '`TMP`', 'g' ), | ||
commentRegExp = new RegExp( commentPattern, 'gi' ), | ||
noFlipSingleRegExp = new RegExp( '(' + noFlipPattern + lookAheadNotOpenBracePattern + '[^;}]+;?)', 'gi' ), | ||
noFlipClassRegExp = new RegExp( '(' + noFlipPattern + charsWithinSelectorPattern + '})', 'gi' ), | ||
directionLtrRegExp = new RegExp( '(' + directionPattern + ')ltr', 'gi' ), | ||
directionRtlRegExp = new RegExp( '(' + directionPattern + ')rtl', 'gi' ), | ||
leftRegExp = new RegExp( nonLetterPattern + '(left)' + lookAheadNotClosingParenPattern + lookAheadNotOpenBracePattern, 'gi' ), | ||
rightRegExp = new RegExp( nonLetterPattern + '(right)' + lookAheadNotClosingParenPattern + lookAheadNotOpenBracePattern, 'gi' ), | ||
leftInUrlRegExp = new RegExp( nonLetterPattern + '(left)' + lookAheadForClosingParenPattern, 'gi' ), | ||
rightInUrlRegExp = new RegExp( nonLetterPattern + '(right)' + lookAheadForClosingParenPattern, 'gi' ), | ||
ltrInUrlRegExp = new RegExp( nonLetterPattern + '(ltr)' + lookAheadForClosingParenPattern, 'gi' ), | ||
rtlInUrlRegExp = new RegExp( nonLetterPattern + '(rtl)' + lookAheadForClosingParenPattern, 'gi' ), | ||
cursorEastRegExp = new RegExp( nonLetterPattern + '([ns]?)e-resize', 'gi' ), | ||
cursorWestRegExp = new RegExp( nonLetterPattern + '([ns]?)w-resize', 'gi' ), | ||
fourNotationQuantRegExp = new RegExp( fourNotationQuantPropsPattern + signedQuantPattern + '(\\s+)' + signedQuantPattern + '(\\s+)' + signedQuantPattern + '(\\s+)' + signedQuantPattern, 'gi' ), | ||
fourNotationColorRegExp = new RegExp( fourNotationColorPropsPattern + colorPattern + '(\\s+)' + colorPattern + '(\\s+)' + colorPattern + '(\\s+)' + colorPattern, 'gi' ), | ||
bgHorizontalPercentageRegExp = new RegExp( '(background(?:-position)?\\s*:\\s*[^%]*?)(-?' + numPattern + ')(%\\s*(?:' + quantPattern + '|' + identPattern + '))', 'gi' ), | ||
bgHorizontalPercentageXRegExp = new RegExp( '(background-position-x\\s*:\\s*)(-?' + numPattern + ')(%)', 'gi' ), | ||
borderRadiusRegExp = new RegExp( '(border-radius\\s*:\\s*)([^;]*)', 'gi' ); | ||
|
||
/* Private Methods */ | ||
|
||
/** | ||
* Inverts the horizontal value of a background position property. | ||
* | ||
* @private | ||
* @function | ||
* @param {String} match Matched property | ||
* @param {String} pre Text before value | ||
* @param {String} value Horizontal value | ||
* @param {String} post Text after value | ||
* @return {String} Inverted property | ||
*/ | ||
function calculateNewBackgroundPosition( match, pre, value, post ) { | ||
return pre + ( 100 - Number( value ) ) + post; | ||
} | ||
|
||
/** | ||
* Inverts the horizontal value of a background position property. | ||
* | ||
* @private | ||
* @function | ||
* @param {String} match Matched property | ||
* @param {String} pre Text before value | ||
* @param {String} value Horizontal value | ||
* @param {String} post Text after value | ||
* @return {String} Inverted property | ||
*/ | ||
function calculateNewBorderRadius( match, pre, values ) { | ||
values = values.split( /\s+/g ); | ||
switch ( values.length ) { | ||
case 4: | ||
values = [values[1], values[0], values[3], values[2]]; | ||
break; | ||
case 3: | ||
values = [values[1], values[0], values[2]]; | ||
break; | ||
case 2: | ||
values = [values[1], values[0]]; | ||
break; | ||
} | ||
return pre + values.join( ' ' ); | ||
} | ||
|
||
/* Methods */ | ||
|
||
return { | ||
/** | ||
* Transform a left-to-right stylesheet to right-to-left. | ||
* | ||
* @method | ||
* @param {String} css Stylesheet to transform | ||
* @param {Boolean} swapLtrRtlInUrl Swap 'ltr' and 'rtl' in URLs | ||
* @param {Boolean} swapLeftRightInUrl Swap 'left' and 'right' in URLs | ||
* @return {String} Transformed stylesheet | ||
*/ | ||
'transform': function( css, swapLtrRtlInUrl, swapLeftRightInUrl ) { | ||
// Tokenizers | ||
var noFlipSingleTokenizer = new Tokenizer( noFlipSingleRegExp, noFlipSingleToken ), | ||
noFlipClassTokenizer = new Tokenizer( noFlipClassRegExp, noFlipClassToken ), | ||
commentTokenizer = new Tokenizer( commentRegExp, commentToken ); | ||
|
||
// Tokenize | ||
css = commentTokenizer.tokenize( | ||
noFlipClassTokenizer.tokenize( | ||
noFlipSingleTokenizer.tokenize( | ||
// We wrap tokens in ` , not ~ like the original implementation does. | ||
// This was done because ` is not a legal character in CSS and can only | ||
// occur in URLs, where we escape it to %60 before inserting our tokens. | ||
css.replace( '`', '%60' ) | ||
) | ||
) | ||
); | ||
|
||
// Transform URLs | ||
if ( swapLtrRtlInUrl ) { | ||
// Replace 'ltr' with 'rtl' and vice versa in background URLs | ||
css = css | ||
.replace( ltrInUrlRegExp, '$1' + temporaryToken ) | ||
.replace( rtlInUrlRegExp, '$1ltr' ) | ||
.replace( temporaryTokenRegExp, 'rtl' ); | ||
} | ||
if ( swapLeftRightInUrl ) { | ||
// Replace 'left' with 'right' and vice versa in background URLs | ||
css = css | ||
.replace( leftInUrlRegExp, '$1' + temporaryToken ) | ||
.replace( rightInUrlRegExp, '$1left' ) | ||
.replace( temporaryTokenRegExp, 'right' ); | ||
} | ||
|
||
// Transform rules | ||
css = css | ||
// Replace direction: ltr; with direction: rtl; and vice versa. | ||
.replace( directionLtrRegExp, '$1' + temporaryToken ) | ||
.replace( directionRtlRegExp, '$1ltr' ) | ||
.replace( temporaryTokenRegExp, 'rtl' ) | ||
// Flip rules like left: , padding-right: , etc. | ||
.replace( leftRegExp, '$1' + temporaryToken ) | ||
.replace( rightRegExp, '$1left' ) | ||
.replace( temporaryTokenRegExp, 'right' ) | ||
// Flip East and West in rules like cursor: nw-resize; | ||
.replace( cursorEastRegExp, '$1$2' + temporaryToken ) | ||
.replace( cursorWestRegExp, '$1$2e-resize' ) | ||
.replace( temporaryTokenRegExp, 'w-resize' ) | ||
// Border radius | ||
.replace( borderRadiusRegExp, calculateNewBorderRadius ) | ||
// Swap the second and fourth parts in four-part notation rules | ||
// like padding: 1px 2px 3px 4px; | ||
.replace( fourNotationQuantRegExp, '$1$2$3$8$5$6$7$4' ) | ||
.replace( fourNotationColorRegExp, '$1$2$3$8$5$6$7$4' ) | ||
// Flip horizontal background percentages | ||
.replace( bgHorizontalPercentageRegExp, calculateNewBackgroundPosition ) | ||
.replace( bgHorizontalPercentageXRegExp, calculateNewBackgroundPosition ); | ||
|
||
// Detokenize | ||
css = noFlipSingleTokenizer.detokenize( | ||
noFlipClassTokenizer.detokenize( | ||
commentTokenizer.detokenize( css ) | ||
) | ||
); | ||
|
||
return css; | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* Creates a tokenizer object. | ||
* | ||
* This utility class is used by CSSJanus to protect strings by replacing them temporarily with | ||
* tokens and later transforming them back. | ||
* | ||
* @author Trevor Parscal | ||
* @author Roan Kattouw | ||
* | ||
* @class | ||
* @constructor | ||
* @param {RegExp} regex Regular expression whose matches to replace by a token | ||
* @param {String} token Placeholder text | ||
*/ | ||
Tokenizer = function( regex, token ) { | ||
|
||
/* Private Members */ | ||
|
||
var matches = [], | ||
index = 0; | ||
|
||
/* Private Methods */ | ||
|
||
/** | ||
* Adds a match. | ||
* | ||
* @private | ||
* @function | ||
* @param {String} match Matched string | ||
* @returns {String} Token to leave in the matched string's place | ||
*/ | ||
function tokenizeCallback( match ) { | ||
matches.push( match ); | ||
return token; | ||
} | ||
|
||
/** | ||
* Gets a match. | ||
* | ||
* @private | ||
* @function | ||
* @param {String} token Matched token | ||
* @returns {String} Original matched string to restore | ||
*/ | ||
function detokenizeCallback( token ) { | ||
return matches[index++]; | ||
} | ||
|
||
/* Methods */ | ||
|
||
return { | ||
/** | ||
* Replace matching strings with tokens. | ||
* | ||
* @method | ||
* @param {String} str String to tokenize | ||
* @return {String} Tokenized string | ||
*/ | ||
'tokenize': function( str ) { | ||
return str.replace( regex, tokenizeCallback ); | ||
}, | ||
/** | ||
* Restores tokens to their original values. | ||
* | ||
* @method | ||
* @param {String} str String previously run through tokenize() | ||
* @return {String} Original string | ||
*/ | ||
'detokenize': function( str ) { | ||
return str.replace( new RegExp( '(' + token + ')', 'g' ), detokenizeCallback ); | ||
} | ||
}; | ||
}; | ||
|
||
/* Initialization */ | ||
|
||
var cssjanus = new CSSJanus(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters