├── .all-contributorsrc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE-MIT ├── README.md ├── package.json └── src ├── __tests__ ├── characters.js └── index.js ├── characters.js └── index.js /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "postcss-unicode-characters", 3 | "projectOwner": "ben-eb", 4 | "files": [ 5 | "README.md" 6 | ], 7 | "imageSize": 100, 8 | "commit": false, 9 | "contributors": [ 10 | { 11 | "login": "ben-eb", 12 | "name": "Ben Briggs", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/1282980?v=3", 14 | "profile": "http://beneb.info", 15 | "contributions": [ 16 | "code", 17 | "doc", 18 | "review", 19 | "test" 20 | ] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{json,yml}] 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | dist 4 | node_modules 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | matrix: 5 | include: 6 | - node_js: '6' 7 | - node_js: '4' 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.1 2 | 3 | * Upgrades unicode to v9 to avoid a security issue. 4 | https://snyk.io/vuln/npm:unicode:20161219 5 | 6 | # 1.0.0 7 | 8 | * Initial release. 9 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Ben Briggs (http://beneb.info) 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [postcss][postcss]-unicode-characters [![Build Status](https://travis-ci.org/ben-eb/postcss-unicode-characters.svg?branch=master)][ci] [![NPM version](https://badge.fury.io/js/postcss-unicode-characters.svg)][npm] [![Dependency Status](https://gemnasium.com/ben-eb/postcss-unicode-characters.svg)][deps] 2 | 3 | > An easier way to write unicode-range descriptors. 4 | 5 | 6 | ## Install 7 | 8 | With [npm](https://npmjs.org/package/postcss-unicode-characters) do: 9 | 10 | ``` 11 | npm install postcss-unicode-characters --save 12 | ``` 13 | 14 | 15 | ## Example 16 | 17 | This module provides syntactic sugar for the [`unicode-range`][1] descriptor, 18 | inspired by [@svgeesus][2]' dotCSS talk. It provides three different ways to 19 | construct a standard [`unicode-range`][1] descriptor by using the non-standard 20 | `unicode-characters` descriptor. 21 | 22 | [1]: https://developer.mozilla.org/en/docs/Web/CSS/@font-face/unicode-range 23 | [2]: https://github.com/svgeesus 24 | 25 | The formal syntax for `unicode-characters` looks like this: 26 | 27 | ``` 28 | [ | name( ) | script( ) ]# 29 | ``` 30 | 31 | ### Using literal characters 32 | 33 | Write in the characters that you want to subset directly, using a string: 34 | 35 | #### Input 36 | 37 | ```css 38 | @font-face { 39 | font-family: test; 40 | src: local("Baskerville"); 41 | unicode-characters: "hello"; 42 | } 43 | ``` 44 | 45 | #### Output 46 | 47 | ```css 48 | @font-face { 49 | font-family: test; 50 | src: local("Baskerville"); 51 | unicode-range: U+68, U+65, U+6C, U+6F; 52 | } 53 | ``` 54 | 55 | Using this method, duplicate characters are automatically removed for you. 56 | 57 | ### Using the `name` function 58 | 59 | If you can remember a Unicode description for a character, using this function 60 | will save some time obtaining the literal: 61 | 62 | #### Input 63 | 64 | ```css 65 | @font-face { 66 | font-family: test; 67 | src: local("Baskerville"); 68 | unicode-characters: name("black star"); 69 | } 70 | ``` 71 | 72 | #### Output 73 | 74 | ```css 75 | @font-face { 76 | font-family: test; 77 | src: local("Baskerville"); 78 | unicode-range: U+2605; 79 | } 80 | ``` 81 | 82 | ### Using the `script` function 83 | 84 | You can also use the `script` function for a range of characters. 85 | 86 | #### Input 87 | 88 | ```css 89 | @font-face { 90 | font-family: test; 91 | src: local("Baskerville"); 92 | unicode-characters: script("arrows"); 93 | } 94 | ``` 95 | 96 | #### Output 97 | 98 | ```css 99 | @font-face { 100 | font-family: test; 101 | src: local("Baskerville"); 102 | unicode-range: U+2190-21FF; 103 | } 104 | ``` 105 | 106 | ### Mix & match 107 | 108 | Note that you can mix and match as many combinations as you like, even using 109 | the classic `unicode-range` syntax. Just remember that these should be separated 110 | by a comma: 111 | 112 | #### Input 113 | 114 | ```css 115 | @font-face { 116 | font-family: test; 117 | src: local("Baskerville"); 118 | unicode-characters: script("arrows"), "hello", U+26; 119 | } 120 | ``` 121 | 122 | #### Output 123 | 124 | ```css 125 | @font-face { 126 | font-family: test; 127 | src: local("Baskerville"); 128 | unicode-range: U+2190-21FF, U+68, U+65, U+6C, U+6F, U+26; 129 | } 130 | ``` 131 | 132 | 133 | ## Usage 134 | 135 | See the [PostCSS documentation](https://github.com/postcss/postcss#usage) for 136 | examples for your environment. 137 | 138 | 139 | ## Contributors 140 | 141 | Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)): 142 | 143 | 144 | | [
Ben Briggs](http://beneb.info)
[💻](https://github.com/ben-eb/postcss-unicode-characters/commits?author=ben-eb) [📖](https://github.com/ben-eb/postcss-unicode-characters/commits?author=ben-eb) 👀 [⚠️](https://github.com/ben-eb/postcss-unicode-characters/commits?author=ben-eb) | 145 | | :---: | 146 | 147 | 148 | This project follows the [all-contributors] specification. Contributions of 149 | any kind welcome! 150 | 151 | 152 | ## License 153 | 154 | MIT © [Ben Briggs](http://beneb.info) 155 | 156 | 157 | [all-contributors]: https://github.com/kentcdodds/all-contributors 158 | [ci]: https://travis-ci.org/ben-eb/postcss-unicode-characters 159 | [deps]: https://gemnasium.com/ben-eb/postcss-unicode-characters 160 | [npm]: http://badge.fury.io/js/postcss-unicode-characters 161 | [postcss]: https://github.com/postcss/postcss 162 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-unicode-characters", 3 | "version": "1.0.1", 4 | "description": "An easier way to write unicode-range descriptors.", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "contributorAdd": "all-contributors add", 8 | "contributorGenerate": "all-contributors generate", 9 | "pretest": "eslint src", 10 | "prepublish": "del-cli dist && cross-env BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/", 11 | "report": "nyc report --reporter=html", 12 | "test": "cross-env BABEL_ENV=test BABEL_DISABLE_CACHE=1 nyc --check-coverage --lines 100 ava" 13 | }, 14 | "engines": { 15 | "node": ">=4" 16 | }, 17 | "files": [ 18 | "dist", 19 | "LICENSE-MIT" 20 | ], 21 | "keywords": [ 22 | "css", 23 | "postcss", 24 | "postcss-plugin", 25 | "unicode", 26 | "unicode range", 27 | "syntax sugar", 28 | "sugar" 29 | ], 30 | "homepage": "https://github.com/ben-eb/postcss-unicode-characters", 31 | "author": { 32 | "name": "Ben Briggs", 33 | "email": "beneb.info@gmail.com", 34 | "url": "http://beneb.info" 35 | }, 36 | "repository": "ben-eb/postcss-unicode-characters", 37 | "license": "MIT", 38 | "dependencies": { 39 | "postcss": "^5.2.5", 40 | "postcss-value-parser": "^3.3.0", 41 | "unicode": "^9.0.0", 42 | "unicode-range-json": "^1.0.0", 43 | "uniqs": "^2.0.0" 44 | }, 45 | "devDependencies": { 46 | "all-contributors-cli": "^4.0.0", 47 | "ava": "^0.17.0", 48 | "babel-cli": "^6.5.1", 49 | "babel-core": "^6.5.1", 50 | "babel-plugin-istanbul": "^1.0.3", 51 | "babel-preset-es2015": "^6.9.0", 52 | "babel-preset-stage-0": "^6.5.0", 53 | "babel-register": "^6.7.2", 54 | "cross-env": "^4.0.0", 55 | "del-cli": "^0.2.0", 56 | "eslint": "^3.0.0", 57 | "eslint-config-cssnano": "^3.0.0", 58 | "eslint-plugin-babel": "^3.3.0", 59 | "eslint-plugin-import": "^1.12.0", 60 | "nyc": "^10.0.0" 61 | }, 62 | "nyc": { 63 | "include": [ 64 | "src/**/*.js" 65 | ], 66 | "exclude": [ 67 | "**/__tests__" 68 | ], 69 | "sourceMap": false, 70 | "instrument": false 71 | }, 72 | "ava": { 73 | "babel": "inherit", 74 | "require": "babel-register" 75 | }, 76 | "eslintConfig": { 77 | "extends": "cssnano" 78 | }, 79 | "babel": { 80 | "presets": [ 81 | "es2015", 82 | "stage-0" 83 | ], 84 | "env": { 85 | "development": { 86 | "sourceMaps": "inline" 87 | }, 88 | "test": { 89 | "plugins": [ 90 | "istanbul" 91 | ] 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/__tests__/characters.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import unicodeRange from 'unicode-range-json'; 3 | import characters from '../characters'; 4 | 5 | test('should have no collisions between unicode groups & character names', t => { 6 | const categories = unicodeRange.map(r => r.category.toLowerCase()); 7 | Object.keys(characters).forEach(key => { 8 | const {name} = characters[key]; 9 | t.falsy(~categories.indexOf(name.toLowerCase())); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/__tests__/index.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import postcss from 'postcss'; 3 | import plugin from '../'; 4 | 5 | function testConversion (t, input, output) { 6 | const fixture = ` 7 | @font-face { 8 | font-family: test; 9 | src: local("Baskerville"); 10 | unicode-characters: ${input}; 11 | }`; 12 | const expected = ` 13 | @font-face { 14 | font-family: test; 15 | src: local("Baskerville"); 16 | unicode-range: ${output}; 17 | }`; 18 | return postcss(plugin).process(fixture).then(result => { 19 | t.is(result.css, expected); 20 | }); 21 | } 22 | 23 | function testError (t, input) { 24 | t.throws(testConversion(t, input, '')); 25 | } 26 | 27 | test( 28 | 'should work with literal characters', 29 | testConversion, 30 | `"&"`, 31 | `U+26` 32 | ); 33 | 34 | test( 35 | 'should work with multiple literal characters', 36 | testConversion, 37 | `"&!"`, 38 | `U+26, U+21` 39 | ); 40 | 41 | test( 42 | 'should work with unicode names', 43 | testConversion, 44 | `name("black star")`, 45 | `U+2605` 46 | ); 47 | 48 | test( 49 | 'should work with categories', 50 | testConversion, 51 | `script("arrows")`, 52 | `U+2190-21FF` 53 | ); 54 | 55 | test( 56 | 'should handle multiple types', 57 | testConversion, 58 | `U+26, "!", script("arrows")`, 59 | `U+26, U+21, U+2190-21FF` 60 | ); 61 | 62 | test( 63 | 'should handle duplicates', 64 | testConversion, 65 | `"hello"`, 66 | `U+68, U+65, U+6C, U+6F` 67 | ); 68 | 69 | test( 70 | 'should handle var', 71 | testConversion, 72 | `var(--foo)`, 73 | `var(--foo)` 74 | ); 75 | 76 | test( 77 | 'should error if a category is not found', 78 | testError, 79 | `script("Unicorn!")` 80 | ); 81 | 82 | test( 83 | 'should error if a character is not found', 84 | testError, 85 | `name("Unicorn!")` 86 | ); 87 | -------------------------------------------------------------------------------- /src/characters.js: -------------------------------------------------------------------------------- 1 | import Cc from 'unicode/category/Cc'; 2 | import Cf from 'unicode/category/Cf'; 3 | import Co from 'unicode/category/Co'; 4 | import Cs from 'unicode/category/Cs'; 5 | import Ll from 'unicode/category/Ll'; 6 | import Lm from 'unicode/category/Lm'; 7 | import Lo from 'unicode/category/Lo'; 8 | import Lt from 'unicode/category/Lt'; 9 | import Lu from 'unicode/category/Lu'; 10 | import Mc from 'unicode/category/Mc'; 11 | import Me from 'unicode/category/Me'; 12 | import Mn from 'unicode/category/Mn'; 13 | import Nd from 'unicode/category/Nd'; 14 | import Nl from 'unicode/category/Nl'; 15 | import No from 'unicode/category/No'; 16 | import Pc from 'unicode/category/Pc'; 17 | import Pd from 'unicode/category/Pd'; 18 | import Pe from 'unicode/category/Pe'; 19 | import Pf from 'unicode/category/Pf'; 20 | import Pi from 'unicode/category/Pi'; 21 | import Po from 'unicode/category/Po'; 22 | import Ps from 'unicode/category/Ps'; 23 | import Sc from 'unicode/category/Sc'; 24 | import Sk from 'unicode/category/Sk'; 25 | import Sm from 'unicode/category/Sm'; 26 | import So from 'unicode/category/So'; 27 | import Zl from 'unicode/category/Zl'; 28 | import Zp from 'unicode/category/Zp'; 29 | import Zs from 'unicode/category/Zs'; 30 | 31 | export default { 32 | ...Cc, 33 | ...Cf, 34 | ...Co, 35 | ...Cs, 36 | ...Ll, 37 | ...Lm, 38 | ...Lo, 39 | ...Lt, 40 | ...Lu, 41 | ...Mc, 42 | ...Me, 43 | ...Mn, 44 | ...Nd, 45 | ...Nl, 46 | ...No, 47 | ...Pc, 48 | ...Pd, 49 | ...Pe, 50 | ...Pf, 51 | ...Pi, 52 | ...Po, 53 | ...Ps, 54 | ...Sc, 55 | ...Sk, 56 | ...Sm, 57 | ...So, 58 | ...Zl, 59 | ...Zp, 60 | ...Zs, 61 | }; 62 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | import valueParser, {stringify} from 'postcss-value-parser'; 3 | import unicodeRange from 'unicode-range-json'; 4 | import uniqs from 'uniqs'; 5 | import characters from './characters'; 6 | 7 | function trimLeadingZero (str) { 8 | if (str[0] === '0') { 9 | return str.slice(str.lastIndexOf('0') + 1); 10 | } 11 | return str; 12 | } 13 | 14 | function formatCodePoint (...values) { 15 | return `U+${values.map(trimLeadingZero).join('-')}`; 16 | } 17 | 18 | function formatError (node) { 19 | let desc = 'identifier'; 20 | if (node.value === 'script') { 21 | desc = `Unicode script range`; 22 | } else { 23 | desc = `Unicode character description`; 24 | } 25 | return `Could not find a ${desc} matching \`${stringify(node)}\`.`; 26 | } 27 | 28 | function transform (node) { 29 | const {type} = node; 30 | if (type === 'function') { 31 | if (node.value !== 'script' && node.value !== 'name') { 32 | return false; 33 | } 34 | if (node.value === 'script') { 35 | // Expecting a category name 36 | const range = unicodeRange.find(r => r.category.match(new RegExp(node.nodes[0].value, 'i'))); 37 | if (range) { 38 | node.type = 'word'; 39 | node.value = formatCodePoint(...range.hexrange); 40 | return false; 41 | } 42 | } else { 43 | // Expecting a character description 44 | const character = Object.keys(characters).find(key => { 45 | return characters[key].name.match(new RegExp(node.nodes[0].value, 'i')); 46 | }); 47 | if (character) { 48 | node.type = 'word'; 49 | node.value = formatCodePoint(characters[character].value); 50 | return false; 51 | } 52 | } 53 | throw new Error(formatError(node)); 54 | } else if (type === 'string') { 55 | // Expecting a unicode literal 56 | node.type = 'word'; 57 | node.value = uniqs(node.value.split('').map(char => { 58 | return formatCodePoint(characters[char.codePointAt(0)].value); 59 | })).join(', '); 60 | return false; 61 | } 62 | } 63 | 64 | export default postcss.plugin('postcss-unicode-characters', () => { 65 | return css => { 66 | css.walkDecls(/^unicode-characters$/i, decl => { 67 | decl.value = valueParser(decl.value).walk(transform).toString(); 68 | decl.prop = 'unicode-range'; 69 | }); 70 | }; 71 | }); 72 | --------------------------------------------------------------------------------