8
0
Просмотр исходного кода

Merge pull request #5 from ckeditor/t/4

Feature: The initial font feature implementation. Closes #2. Closes #3. Closes #4.
Aleksander Nowodzinski 8 лет назад
Родитель
Сommit
d2a6ba52b6
53 измененных файлов с 3110 добавлено и 16 удалено
  1. 1 1
      packages/ckeditor5-font/.eslintrc.js
  2. 11 11
      packages/ckeditor5-font/.travis.yml
  3. 1 1
      packages/ckeditor5-font/LICENSE.md
  4. 17 0
      packages/ckeditor5-font/docs/_snippets/features/build-font-source.html
  5. 14 0
      packages/ckeditor5-font/docs/_snippets/features/build-font-source.js
  6. 13 0
      packages/ckeditor5-font/docs/_snippets/features/custom-font-family-options.html
  7. 28 0
      packages/ckeditor5-font/docs/_snippets/features/custom-font-family-options.js
  8. 9 0
      packages/ckeditor5-font/docs/_snippets/features/custom-font-size-named-options.html
  9. 28 0
      packages/ckeditor5-font/docs/_snippets/features/custom-font-size-named-options.js
  10. 11 0
      packages/ckeditor5-font/docs/_snippets/features/custom-font-size-numeric-options.html
  11. 32 0
      packages/ckeditor5-font/docs/_snippets/features/custom-font-size-numeric-options.js
  12. 20 0
      packages/ckeditor5-font/docs/_snippets/features/font.html
  13. 22 0
      packages/ckeditor5-font/docs/_snippets/features/font.js
  14. 31 0
      packages/ckeditor5-font/docs/api/font.md
  15. 258 0
      packages/ckeditor5-font/docs/features/font.md
  16. 10 0
      packages/ckeditor5-font/lang/contexts.json
  17. 4 3
      packages/ckeditor5-font/package.json
  18. 38 0
      packages/ckeditor5-font/src/font.js
  19. 91 0
      packages/ckeditor5-font/src/fontcommand.js
  20. 111 0
      packages/ckeditor5-font/src/fontfamily.js
  21. 29 0
      packages/ckeditor5-font/src/fontfamily/fontfamilycommand.js
  22. 68 0
      packages/ckeditor5-font/src/fontfamily/fontfamilyediting.js
  23. 117 0
      packages/ckeditor5-font/src/fontfamily/fontfamilyui.js
  24. 92 0
      packages/ckeditor5-font/src/fontfamily/utils.js
  25. 113 0
      packages/ckeditor5-font/src/fontsize.js
  26. 29 0
      packages/ckeditor5-font/src/fontsize/fontsizecommand.js
  27. 68 0
      packages/ckeditor5-font/src/fontsize/fontsizeediting.js
  28. 135 0
      packages/ckeditor5-font/src/fontsize/fontsizeui.js
  29. 114 0
      packages/ckeditor5-font/src/fontsize/utils.js
  30. 18 0
      packages/ckeditor5-font/tests/font.js
  31. 279 0
      packages/ckeditor5-font/tests/fontcommand.js
  32. 18 0
      packages/ckeditor5-font/tests/fontfamily.js
  33. 35 0
      packages/ckeditor5-font/tests/fontfamily/fontfamilycommand.js
  34. 201 0
      packages/ckeditor5-font/tests/fontfamily/fontfamilyediting.js
  35. 163 0
      packages/ckeditor5-font/tests/fontfamily/fontfamilyui.js
  36. 86 0
      packages/ckeditor5-font/tests/fontfamily/utils.js
  37. 18 0
      packages/ckeditor5-font/tests/fontsize.js
  38. 35 0
      packages/ckeditor5-font/tests/fontsize/fontsizecommand.js
  39. 220 0
      packages/ckeditor5-font/tests/fontsize/fontsizeediting.js
  40. 257 0
      packages/ckeditor5-font/tests/fontsize/fontsizeui.js
  41. 52 0
      packages/ckeditor5-font/tests/fontsize/utils.js
  42. 45 0
      packages/ckeditor5-font/tests/manual/font-family.html
  43. 24 0
      packages/ckeditor5-font/tests/manual/font-family.js
  44. 12 0
      packages/ckeditor5-font/tests/manual/font-family.md
  45. 11 0
      packages/ckeditor5-font/tests/manual/font-size-numeric.html
  46. 25 0
      packages/ckeditor5-font/tests/manual/font-size-numeric.js
  47. 12 0
      packages/ckeditor5-font/tests/manual/font-size-numeric.md
  48. 43 0
      packages/ckeditor5-font/tests/manual/font-size-presets.html
  49. 24 0
      packages/ckeditor5-font/tests/manual/font-size-presets.js
  50. 15 0
      packages/ckeditor5-font/tests/manual/font-size-presets.md
  51. BIN
      packages/ckeditor5-font/tests/manual/sample.jpg
  52. 1 0
      packages/ckeditor5-font/theme/icons/font-family.svg
  53. 1 0
      packages/ckeditor5-font/theme/icons/font-size.svg

+ 1 - 1
packages/ckeditor5-font/.eslintrc.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 

+ 11 - 11
packages/ckeditor5-font/.travis.yml

@@ -1,24 +1,24 @@
 sudo: required
 dist: trusty
 addons:
+  firefox: "latest"
   apt:
     sources:
-      - google-chrome
+    - google-chrome
     packages:
-      - google-chrome-stable
+    - google-chrome-stable
 language: node_js
 node_js:
-  - "6"
+- '6'
 cache:
-  - node_modules
+- node_modules
 before_install:
-  - export DISPLAY=:99.0
-  - sh -e /etc/init.d/xvfb start
+- export DISPLAY=:99.0
+- sh -e /etc/init.d/xvfb start
 install:
-  - npm install @ckeditor/ckeditor5-dev-tests
-  - ckeditor5-dev-tests-install-dependencies
+- npm install @ckeditor/ckeditor5-dev-tests
+- ckeditor5-dev-tests-install-dependencies
 script:
-  - ckeditor5-dev-tests-travis
+- ckeditor5-dev-tests-travis
 after_success:
-  - codeclimate-test-reporter < coverage/lcov.info
-  - ckeditor5-dev-tests-save-revision
+- ckeditor5-dev-tests-save-revision

+ 1 - 1
packages/ckeditor5-font/LICENSE.md

@@ -2,7 +2,7 @@ Software License Agreement
 ==========================
 
 **CKEditor 5 Font Feature** – https://github.com/ckeditor/ckeditor5-font <br>
-Copyright (c) 2003-2017, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
+Copyright (c) 2003-2018, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
 
 Licensed under the terms of any of the following licenses at your choice:
 

+ 17 - 0
packages/ckeditor5-font/docs/_snippets/features/build-font-source.html

@@ -0,0 +1,17 @@
+<style>
+	.text-tiny {
+		font-size: 0.7em;
+	}
+
+	.text-small {
+		font-size: 0.85em;
+	}
+
+	.text-big {
+		font-size: 1.4em;
+	}
+
+	.text-huge {
+		font-size: 1.8em;
+	}
+</style>

+ 14 - 0
packages/ckeditor5-font/docs/_snippets/features/build-font-source.js

@@ -0,0 +1,14 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals window */
+
+import ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor';
+
+import Font from '@ckeditor/ckeditor5-font/src/font';
+
+ClassicEditor.build.plugins.push( Font );
+
+window.ClassicEditor = ClassicEditor;

+ 13 - 0
packages/ckeditor5-font/docs/_snippets/features/custom-font-family-options.html

@@ -0,0 +1,13 @@
+<link href="https://fonts.googleapis.com/css?family=Ubuntu|Ubuntu+Mono" rel="stylesheet">
+
+<div id="snippet-custom-font-family-options">
+	<h2>Font Family feature sample.</h2>
+
+	<p>
+		<span style="font-family: Ubuntu, Arial, sans-serif;">This text has "Ubuntu, Arial, Helvetica, sans-serif" font family set.</span>
+	</p>
+
+	<p>
+		<span style="font-family: 'Ubuntu Mono', 'Courier New', Courier, monospace;">This text has "Ubuntu Mono, Courier New, Courier, monospace" font family set.</span>
+	</p>
+</div>

+ 28 - 0
packages/ckeditor5-font/docs/_snippets/features/custom-font-family-options.js

@@ -0,0 +1,28 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals ClassicEditor, console, window, document */
+ClassicEditor
+	.create( document.querySelector( '#snippet-custom-font-family-options' ), {
+		toolbar: {
+			items: [
+				'headings', '|', 'fontFamily', 'bulletedList', 'numberedList', 'undo', 'redo'
+			],
+			viewportTopOffset: 60
+		},
+		fontFamily: {
+			options: [
+				'default',
+				'Ubuntu, Arial, sans-serif',
+				'Ubuntu Mono, Courier New, Courier, monospace'
+			]
+		}
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 9 - 0
packages/ckeditor5-font/docs/_snippets/features/custom-font-size-named-options.html

@@ -0,0 +1,9 @@
+<div id="snippet-custom-font-size-named-options">
+	<h2>Font Size named options sample.</h2>
+
+	<p>
+		This is a mixed text with different sizes of text:
+		<span class="text-small">small</span> and
+		<span class="text-big">big</span>
+	</p>
+</div>

+ 28 - 0
packages/ckeditor5-font/docs/_snippets/features/custom-font-size-named-options.js

@@ -0,0 +1,28 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals ClassicEditor, console, window, document */
+ClassicEditor
+	.create( document.querySelector( '#snippet-custom-font-size-named-options' ), {
+		toolbar: {
+			items: [
+				'headings', '|', 'fontSize', 'bulletedList', 'numberedList', 'undo', 'redo'
+			],
+			viewportTopOffset: 60
+		},
+		fontSize: {
+			options: [
+				'small',
+				'normal',
+				'big'
+			]
+		}
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 11 - 0
packages/ckeditor5-font/docs/_snippets/features/custom-font-size-numeric-options.html

@@ -0,0 +1,11 @@
+<div id="snippet-custom-font-size-numeric-options">
+	<h2>Font Size feature numerical options sample.</h2>
+
+	<p><span style="font-size: 9px">9px</span></p>
+	<p><span style="font-size: 11px">11px</span></p>
+	<p><span style="font-size: 13px">13px</span></p>
+	<p>Normal</p>
+	<p><span style="font-size: 17px">17px</span></p>
+	<p><span style="font-size: 19px">19px</span></p>
+	<p><span style="font-size: 21px">21px</span></p>
+</div>

+ 32 - 0
packages/ckeditor5-font/docs/_snippets/features/custom-font-size-numeric-options.js

@@ -0,0 +1,32 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals ClassicEditor, console, window, document */
+ClassicEditor
+	.create( document.querySelector( '#snippet-custom-font-size-numeric-options' ), {
+		toolbar: {
+			items: [
+				'headings', '|', 'fontSize', 'bulletedList', 'numberedList', 'undo', 'redo'
+			],
+			viewportTopOffset: 60
+		},
+		fontSize: {
+			options: [
+				9,
+				11,
+				13,
+				'normal',
+				17,
+				19,
+				21
+			]
+		}
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 20 - 0
packages/ckeditor5-font/docs/_snippets/features/font.html

@@ -0,0 +1,20 @@
+<div id="snippet-font">
+	<h2>Font feature sample.</h2>
+
+	<p>
+		This is a mixed text with different sizes of text:
+		<span class="text-tiny">tiny</span>,
+		<span class="text-small">small</span>,
+		<span class="text-big">big</span> and
+		<span class="text-huge">huge</span>.
+	</p>
+
+	<p>
+		<span style="font-family: Arial, Helvetica, sans-serif;">This text has "Arial, Helvetica, sans-serif" family set.</span>
+	</p>
+
+	<p>
+		<span style="font-family: 'Courier New', Courier, monospace;">This text has "Courier New, Courier, monospace" family set.</span>
+	</p>
+
+</div>

+ 22 - 0
packages/ckeditor5-font/docs/_snippets/features/font.js

@@ -0,0 +1,22 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals ClassicEditor, console, window, document */
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-font' ), {
+		toolbar: {
+			items: [
+				'headings', '|', 'fontSize', 'fontFamily', '|', 'bulletedList', 'numberedList', 'undo', 'redo'
+			],
+			viewportTopOffset: 60
+		}
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 31 - 0
packages/ckeditor5-font/docs/api/font.md

@@ -0,0 +1,31 @@
+---
+category: api-reference
+---
+
+# CKEditor 5 font feature
+
+[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-font.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-font)
+
+This package implements the font family and font size features for CKEditor 5.
+
+## Documentation
+
+See the {@link features/font Font feature} guide 
+with corresponding {@link module:font/fontfamily~FontFamily} and {@link module:font/fontsize~FontSize} plugin documentation.
+
+## Installation
+
+```bash
+npm install --save @ckeditor/ckeditor5-font
+```
+
+## Contribute
+
+The source code of this package is available on GitHub in https://github.com/ckeditor/ckeditor5-font.
+
+## External links
+
+* [`@ckeditor/ckeditor5-font` on npm](https://www.npmjs.com/package/@ckeditor/ckeditor5-font)
+* [`ckeditor/ckeditor5-font` on GitHub](https://github.com/ckeditor/ckeditor5-font)
+* [Issue tracker](https://github.com/ckeditor/ckeditor5-font/issues)
+* [Changelog](https://github.com/ckeditor/ckeditor5-font/blob/master/CHANGELOG.md)

+ 258 - 0
packages/ckeditor5-font/docs/features/font.md

@@ -0,0 +1,258 @@
+---
+title: Font
+category: features
+---
+
+{@snippet features/build-font-source}
+
+The {@link module:font/font~Font} plugin enables the following features in the editor:
+* {@link module:font/fontfamily~FontFamily} – allows to change the font family by applying inline `<span>` elements with a `font-family` in the `style` attribute,
+* {@link module:font/fontsize~FontSize} – allows to control size by applying inline `<span>` elements that either have a CSS class or a `font-size` in the `style` attribute.
+
+## Demo
+
+{@snippet features/font}
+
+## Configuring the font family
+
+It is possible to configure which font family options are supported by the editor. Use the {@link module:font/fontfamily~FontFamilyConfig#options `fontFamily.options`} configuration option to do so.
+
+Use the special `'default'` keyword to use the default `font-family` defined in the web page styles (removes any custom font family).
+
+For example, the following editor supports only two font families besides the "default" one:
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		fontFamily: {
+			options: [
+				'default',
+				'Ubuntu, Arial, sans-serif',
+				'Ubuntu Mono, Courier New, Courier, monospace'
+			]
+		},
+		toolbar: [
+			'headings', 'bulletedList', 'numberedList', 'fontFamily', 'undo', 'redo'
+		]
+	} )
+	.then( ... )
+	.catch( ... );
+```
+
+{@snippet features/custom-font-family-options}
+
+## Configuring the font size
+
+It is possible to configure which font size options are supported by the editor. Use the {@link module:font/fontsize~FontSizeConfig#options `fontSize.options`} configuration option to do so.
+
+Use the special `'normal'` keyword to use the default font size defined in the web page styles (removes any custom size).
+
+The font size feature supports two ways of defining configuration: using  predefined (named) presets or simple numeric values.
+
+### Configuration using the predefined named presets
+
+The font size feature defines 4 named presets:
+- `'tiny'`
+- `'small'`
+- `'big'`
+- `'huge'`
+
+Each size is represented in the view as a `<span>` element with the `text-*` class. For example, the `'tiny'` preset looks as follows in the editor data:
+
+```html
+<span class="text-tiny">...</span>
+```
+
+The CSS definition for the classes (presets) must be included:
+- in the web page's styles where the editor runs (backend),
+- in the web page's styles where the edited content is rendered (frontend)
+
+Here's an example of the font size CSS classes:
+
+```css
+.text-tiny {
+	font-size: 0.7em;
+}
+
+.text-small {
+	font-size: 0.85em;
+}
+
+.text-big {
+	font-size: 1.4em;
+}
+
+.text-huge {
+	font-size: 1.8em;
+}
+```
+
+An example of the editor that supports only two font sizes:
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		fontSize: {
+			options: [
+				'tiny',
+				'normal',
+				'big'
+			]
+		},
+		toolbar: [
+			'headings', 'bulletedList', 'numberedList', 'fontSize', 'undo', 'redo'
+		]
+	} )
+	.then( ... )
+	.catch( ... );
+```
+
+{@snippet features/custom-font-size-named-options}
+
+### Configuration using numerical values
+
+The font feature also supports numerical values.
+
+In this case, each size is represented in the view as a `<span>` element with the `font-size` style set in `px`.
+For example, `14` will be represented in the editor data as:
+
+```html
+<span style="font-size: 14px">...</span>
+```
+
+Here's an example of the editor that supports numerical font sizes. Note that `'normal'` is controlled by the default styles of the web page:
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		fontSize: {
+			options: [
+				9,
+				11,
+				13,
+				'normal',
+				17,
+				19,
+				21
+			]
+		},
+		toolbar: [
+			'headings', 'bulletedList', 'numberedList', 'fontSize', 'undo', 'redo'
+		]
+	} )
+	.then( ... )
+	.catch( ... );
+```
+
+{@snippet features/custom-font-size-numeric-options}
+
+## Installation
+
+To add this feature to your editor install the [`@ckeditor/ckeditor5-font`](https://www.npmjs.com/package/@ckeditor/ckeditor5-font) package:
+
+```
+npm install --save @ckeditor/ckeditor5-font
+```
+
+And add it to your plugin list and the toolbar configuration:
+
+```js
+import Font from '@ckeditor/ckeditor5-font/src/font';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Font, ... ],
+		toolbar: [ 'fontSize', 'fontFamily', ... ]
+	} )
+	.then( ... )
+	.catch( ... );
+```
+
+or add one of the font features to your plugin list and the toolbar configuration:
+
+```js
+import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ FontFamily, ... ],
+		toolbar: [ 'fontFamily', ... ]
+	} )
+	.then( ... )
+	.catch( ... );
+```
+
+<info-box info>
+	Read more about {@link builds/guides/development/installing-plugins installing plugins}.
+</info-box>
+
+## Common API
+
+The {@link module:font/fontfamily~FontFamily} plugin registers:
+
+* The `'fontFamily'` dropdown,
+* The {@link module:font/fontfamily/fontfamilycommand~FontFamilyCommand `'fontFamily'`} command.
+
+	The number of options and their names correspond to the {@link module:font/fontfamily~FontFamilyConfig#options `fontFamily.options`} configuration option.
+
+	You can change the font family of the current selection by executing the command with a desired value:
+
+	```js
+	editor.execute( 'fontFamily', { value: 'Arial' } );
+	```
+
+	The `value` must correspond to the first font name in the configuration string. For the following default configuration:
+	```js
+	fontFamily.options = [
+		'default',
+		'Arial, Helvetica, sans-serif',
+		'Courier New, Courier, monospace',
+		'Georgia, serif',
+		'Lucida Sans Unicode, Lucida Grande, sans-serif',
+		'Tahoma, Geneva, sans-serif',
+		'Times New Roman, Times, serif',
+		'Trebuchet MS, Helvetica, sans-serif',
+		'Verdana, Geneva, sans-serif'
+	]
+	```
+
+	the `fontFamily` command will accept the corresponding strings as values:
+	* `'Arial'`
+	* `'Courier New'`
+	* `'Georgia'`
+	* `'Lucida Sans Unicode'`
+	* `'Tahoma'`
+	* `'Times New Roman'`
+	* `'Trebuchet MS'`
+	* `'Verdana'`
+
+	Note that passing an empty value will remove the `fontFamily` from the selection (`default`):
+
+	```js
+	editor.execute( 'fontFamily' );
+	```
+
+The {@link module:font/fontsize~FontSize} plugin registers the following components:
+
+* The `'fontSize'` dropdown,
+* The {@link module:font/fontsize/fontsizecommand~FontSizeCommand `'fontSize'`} command.
+
+	The number of options and their names correspond to the {@link module:font/fontsize~FontSizeConfig#options `fontSize.options`} configuration option.
+
+	You can change the font size of the current selection by executing the command with a desired value:
+
+	```js
+	// For numerical values:
+	editor.execute( 'fontSize', { value: 10 } );
+
+	// For named presets:
+	editor.execute( 'fontSize', { value: 'small' } );
+	```
+	passing an empty value will remove any `fontSize` set:
+
+	```js
+	editor.execute( 'fontSize' );
+	```
+## Contribute
+
+The source code of the feature is available on GitHub in https://github.com/ckeditor/ckeditor5-font.

+ 10 - 0
packages/ckeditor5-font/lang/contexts.json

@@ -0,0 +1,10 @@
+{
+	"Font Size": "Tooltip for the font size drop-down.",
+	"Normal": "Drop-down option label for the normal font size.",
+	"Tiny": "Drop-down option label for the 'tiny' font size preset.",
+	"Small": "Drop-down option label for the 'small' font size preset.",
+	"Big": "Drop-down option label for the 'big' font size preset.",
+	"Huge": "Drop-down option label for the 'huge' font size preset.",
+	"Font Family": "Tooltip for the font family drop-down.",
+	"Default": "Drop-down option label for the default font family."
+}

+ 4 - 3
packages/ckeditor5-font/package.json

@@ -12,10 +12,11 @@
     "@ckeditor/ckeditor5-ui": "^1.0.0-alpha.2"
   },
   "devDependencies": {
-    "eslint": "^4.8.0",
-    "eslint-config-ckeditor5": "^1.0.6",
+    "@ckeditor/ckeditor5-paragraph": "^1.0.0-alpha.2",
+    "eslint": "^4.15.0",
+    "eslint-config-ckeditor5": "^1.0.7",
     "husky": "^0.14.3",
-    "lint-staged": "^4.2.3"
+    "lint-staged": "^6.0.0"
   },
   "engines": {
     "node": ">=6.0.0",

+ 38 - 0
packages/ckeditor5-font/src/font.js

@@ -0,0 +1,38 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/font
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+import FontFamily from './fontfamily';
+import FontSize from './fontsize';
+
+/**
+ * A plugin that enables (aggregates) a set of text styling features:
+ * * {@link module:font/fontsize~FontSize},
+ * * {@link module:font/fontfamily~FontFamily}.
+ *
+ * Read more about the feature in the {@glink api/font font package} page.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class Font extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ FontFamily, FontSize ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'Font';
+	}
+}

+ 91 - 0
packages/ckeditor5-font/src/fontcommand.js

@@ -0,0 +1,91 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontcommand
+ */
+
+import Command from '@ckeditor/ckeditor5-core/src/command';
+
+/**
+ * The base font command.
+ *
+ * @extends module:core/command~Command
+ */
+export default class FontCommand extends Command {
+	/**
+	 * Creates an instance of the command.
+	 *
+	 * @param {module:core/editor/editor~Editor} editor Editor instance.
+	 * @param {String} attributeKey Name of an model attribute on which this command operates.
+	 */
+	constructor( editor, attributeKey ) {
+		super( editor );
+
+		/**
+		 * When set, it reflects the {@link #attributeKey} value of the selection.
+		 *
+		 * @observable
+		 * @readonly
+		 * @member {Boolean} module:font/fontcommand~FontCommand#value
+		 */
+
+		/**
+		 * A model attribute on which this command operates.
+		 *
+		 * @readonly
+		 * @member {Boolean} module:font/fontcommand~FontCommand#attributeKey
+		 */
+		this.attributeKey = attributeKey;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	refresh() {
+		const model = this.editor.model;
+		const doc = model.document;
+
+		this.value = doc.selection.getAttribute( this.attributeKey );
+		this.isEnabled = model.schema.checkAttributeInSelection( doc.selection, this.attributeKey );
+	}
+
+	/**
+	 * Executes the command. Applies the `value` of the {@link #attributeKey} to the selection.
+	 * If no `value` is passed, it removes the attribute from the selection.
+	 *
+	 * @protected
+	 * @param {Object} [options] Options for the executed command.
+	 * @param {String} [options.value] a value to apply.
+	 * @fires execute
+	 */
+	execute( options = {} ) {
+		const model = this.editor.model;
+		const document = model.document;
+		const selection = document.selection;
+
+		const value = options.value;
+
+		model.change( writer => {
+			if ( selection.isCollapsed ) {
+				if ( value ) {
+					writer.setSelectionAttribute( this.attributeKey, value );
+				} else {
+					writer.removeSelectionAttribute( this.attributeKey );
+				}
+			} else {
+				const ranges = model.schema.getValidRanges( selection.getRanges(), this.attributeKey );
+
+				for ( const range of ranges ) {
+					if ( value ) {
+						writer.setAttribute( this.attributeKey, value, range );
+					} else {
+						writer.removeAttribute( this.attributeKey, range );
+					}
+				}
+			}
+		} );
+	}
+}

+ 111 - 0
packages/ckeditor5-font/src/fontfamily.js

@@ -0,0 +1,111 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontfamily
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import FontFamilyEditing from './fontfamily/fontfamilyediting';
+import FontFamilyUI from './fontfamily/fontfamilyui';
+
+/**
+ * The font family plugin.
+ *
+ * It enables {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing} and
+ * {@link module:font/fontfamily/fontfamilyui~FontFamilyUI} features in the editor.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontFamily extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ FontFamilyEditing, FontFamilyUI ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'FontFamily';
+	}
+}
+
+/**
+ * Font family option descriptor.
+ *
+ * @typedef {Object} module:font/fontfamily~FontFamilyOption
+ *
+ * @property {String} title The user-readable title of the option.
+ * @property {String} model Attribute's unique value in the model.
+ * @property {module:engine/view/elementdefinition~ElementDefinition} view View element configuration.
+ * @property {Array.<module:engine/view/elementdefinition~ElementDefinition>} [upcastAlso] An array with all matched elements that
+ * view to model conversion should also accept.
+ */
+
+/**
+ * The configuration of the font family feature.
+ * Introduced by the {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing} feature.
+ *
+ * Read more in {@link module:font/fontfamily~FontFamilyConfig}.
+ *
+ * @member {module:font/fontfamily~FontFamilyConfig} module:core/editor/editorconfig~EditorConfig#fontFamily
+ */
+
+/**
+ * The configuration of the font family feature.
+ * The option is used by the {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing} feature.
+ *
+ *		ClassicEditor
+ *			.create( {
+ * 				fontFamily: ... // Font family feature config.
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
+ *
+ * See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
+ *
+ * @interface module:font/fontfamily~FontFamilyConfig
+ */
+
+/**
+ * Available font family options defined as an array of strings. The default value is:
+ *
+ *		const fontFamilyConfig = {
+ *			options: [
+ *				'default',
+ *				'Arial, Helvetica, sans-serif',
+ *				'Courier New, Courier, monospace',
+ *				'Georgia, serif',
+ *				'Lucida Sans Unicode, Lucida Grande, sans-serif',
+ *				'Tahoma, Geneva, sans-serif',
+ *				'Times New Roman, Times, serif',
+ *				'Trebuchet MS, Helvetica, sans-serif',
+ *				'Verdana, Geneva, sans-serif'
+ *			]
+ *		};
+ *
+ * which configures 8 font family options. Each option consist of one or more comma–separated font-family names. The first font name is
+ * used as a dropdown item description in the UI.
+ *
+ * **Note:** The family names that consist of spaces should not have quotes (as opposed to the CSS standard). The necessary quotes
+ * will be added automatically in the view. For example, the `"Lucida Sans Unicode"` will render as follows:
+ *
+ * 		<span style="font-family:'Lucida Sans Unicode', 'Lucida Grande', sans-serif">...</span>
+ *
+ * The "default" option removes the `fontFamily` attribute from the selection. In such case, the text will
+ * be rendered in the view using the default font family defined in the styles of the web page.
+ *
+ * Font family can be applied using the command API. To do that, use the `fontFamily` command and pass the desired family as a `value`.
+ * For example, the following code will apply the `fontFamily` attribute with the `'Arial'` `value` to the current selection:
+ *
+ *		editor.execute( 'fontFamily', { value: 'Arial' } );
+ *
+ * Executing `fontFamily` command without any value will remove `fontFamily` attribute from the current selection.
+ *
+ * @member {Array.<String|module:font/fontfamily~FontFamilyOption>} module:font/fontfamily~FontFamilyConfig#options
+ */

+ 29 - 0
packages/ckeditor5-font/src/fontfamily/fontfamilycommand.js

@@ -0,0 +1,29 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontfamily/fontfamilycommand
+ */
+
+import FontCommand from '../fontcommand';
+
+/**
+ * The font family command. It is used by the {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing}
+ * to apply the font family.
+ *
+ *		editor.execute( 'fontFamily', { value: 'Arial' } );
+ *
+ * **Note**: Executing the command without the value removes the attribute from the model.
+ *
+ * @extends module:font/fontcommand~FontCommand
+ */
+export default class FontFamilyCommand extends FontCommand {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor, 'fontFamily' );
+	}
+}

+ 68 - 0
packages/ckeditor5-font/src/fontfamily/fontfamilyediting.js

@@ -0,0 +1,68 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontfamily/fontfamilyediting
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import { attributeToElement } from '@ckeditor/ckeditor5-engine/src/conversion/two-way-converters';
+
+import FontFamilyCommand from './fontfamilycommand';
+import { normalizeOptions } from './utils';
+
+const FONT_FAMILY = 'fontFamily';
+
+/**
+ * The font family editing feature.
+ *
+ * It introduces the {@link module:font/fontfamily/fontfamilycommand~FontFamilyCommand command} and
+ * the `fontFamily` attribute in the {@link module:engine/model/model~Model model} which renders
+ * in the {@link module:engine/view/view view} as an inline span (`<span style="font-family: Arial">`),
+ * depending on the {@link module:font/fontfamily~FontFamilyConfig configuration}.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontFamilyEditing extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		// Define default configuration using font families shortcuts.
+		editor.config.define( FONT_FAMILY, {
+			options: [
+				'default',
+				'Arial, Helvetica, sans-serif',
+				'Courier New, Courier, monospace',
+				'Georgia, serif',
+				'Lucida Sans Unicode, Lucida Grande, sans-serif',
+				'Tahoma, Geneva, sans-serif',
+				'Times New Roman, Times, serif',
+				'Trebuchet MS, Helvetica, sans-serif',
+				'Verdana, Geneva, sans-serif'
+			]
+		} );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+
+		// Allow fontFamily attribute on text nodes.
+		editor.model.schema.extend( '$text', { allowAttributes: FONT_FAMILY } );
+
+		// Get configured font family options without "default" option.
+		const options = normalizeOptions( editor.config.get( 'fontFamily.options' ) ).filter( item => item.model );
+
+		// Set-up the two-way conversion.
+		attributeToElement( editor.conversion, FONT_FAMILY, options );
+
+		editor.commands.add( FONT_FAMILY, new FontFamilyCommand( editor ) );
+	}
+}

+ 117 - 0
packages/ckeditor5-font/src/fontfamily/fontfamilyui.js

@@ -0,0 +1,117 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontfamily/fontfamilyui
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import Model from '@ckeditor/ckeditor5-ui/src/model';
+import Collection from '@ckeditor/ckeditor5-utils/src/collection';
+
+import { createDropdown, addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
+import { normalizeOptions } from './utils';
+import fontFamilyIcon from '../../theme/icons/font-family.svg';
+
+/**
+ * The font family UI plugin. It introduces the `'fontFamily'` drop-down.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontFamilyUI extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+		const t = editor.t;
+
+		const options = this._getLocalizedOptions();
+
+		const command = editor.commands.get( 'fontFamily' );
+
+		// Register UI component.
+		editor.ui.componentFactory.add( 'fontFamily', locale => {
+			const dropdownView = createDropdown( locale );
+			addListToDropdown( dropdownView, _prepareListOptions( options, command ) );
+
+			dropdownView.buttonView.set( {
+				label: t( 'Font Family' ),
+				icon: fontFamilyIcon,
+				tooltip: true
+			} );
+
+			dropdownView.extendTemplate( {
+				attributes: {
+					class: 'ck-font-family-dropdown'
+				}
+			} );
+
+			dropdownView.bind( 'isEnabled' ).to( command );
+
+			// Execute command when an item from the dropdown is selected.
+			this.listenTo( dropdownView, 'execute', evt => {
+				editor.execute( evt.source.commandName, { value: evt.source.commandParam } );
+				editor.editing.view.focus();
+			} );
+
+			return dropdownView;
+		} );
+	}
+
+	/**
+	 * Returns options as defined in `config.fontFamily.options` but processed to consider
+	 * editor localization, i.e. to display {@link module:font/fontfamily~FontFamilyOption}
+	 * in the correct language.
+	 *
+	 * Note: The reason behind this method is that there's no way to use {@link module:utils/locale~Locale#t}
+	 * when the user config is defined because the editor does not exist yet.
+	 *
+	 * @private
+	 * @returns {Array.<module:font/fontfamily~FontFamilyOption>}.
+	 */
+	_getLocalizedOptions() {
+		const editor = this.editor;
+		const t = editor.t;
+
+		const options = normalizeOptions( editor.config.get( 'fontFamily.options' ) );
+
+		return options.map( option => {
+			// The only title to localize is "Default" others are font names.
+			if ( option.title === 'Default' ) {
+				option.title = t( 'Default' );
+			}
+
+			return option;
+		} );
+	}
+}
+
+// Prepares FontFamily dropdown items.
+// @private
+// @param {Array.<module:font/fontsize~FontSizeOption>} options
+// @param {module:font/fontsize/fontsizecommand~FontSizeCommand} command
+function _prepareListOptions( options, command ) {
+	const dropdownItems = new Collection();
+
+	// Create dropdown items.
+	for ( const option of options ) {
+		const itemModel = new Model( {
+			commandName: 'fontFamily',
+			commandParam: option.model,
+			label: option.title
+		} );
+
+		itemModel.bind( 'isActive' ).to( command, 'value', value => value === option.model );
+
+		// Try to set a dropdown list item style.
+		if ( option.view && option.view.style ) {
+			itemModel.set( 'style', `font-family: ${ option.view.style[ 'font-family' ] }` );
+		}
+
+		dropdownItems.add( itemModel );
+	}
+	return dropdownItems;
+}

+ 92 - 0
packages/ckeditor5-font/src/fontfamily/utils.js

@@ -0,0 +1,92 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontfamily/utils
+ */
+
+/**
+ * Normalizes the {@link module:font/fontfamily~FontFamilyConfig#options config options}
+ * to the {@link module:font/fontfamily~FontFamilyOption} format.
+ *
+ * @param {Array.<String|Object>} configuredOptions An array of options taken from configuration.
+ * @returns {Array.<module:font/fontfamily~FontFamilyOption>}
+ */
+export function normalizeOptions( configuredOptions ) {
+	// Convert options to objects.
+	return configuredOptions
+		.map( getOptionDefinition )
+		// Filter out undefined values that `getOptionDefinition` might return.
+		.filter( option => !!option );
+}
+
+// Returns an option definition either created from string shortcut.
+// If object is passed then this method will return it without alternating it. Returns undefined for item than cannot be parsed.
+//
+// @param {String|Object} option
+// @returns {undefined|module:font/fontfamily~FontFamilyOption}
+function getOptionDefinition( option ) {
+	// Treat any object as full item definition provided by user in configuration.
+	if ( typeof option === 'object' ) {
+		return option;
+	}
+
+	// Handle 'default' string as a special case. It will be used to remove the fontFamily attribute.
+	if ( option === 'default' ) {
+		return {
+			title: 'Default',
+			model: undefined
+		};
+	}
+
+	// Ignore values that we cannot parse to a definition.
+	if ( typeof option !== 'string' ) {
+		return;
+	}
+
+	// Return font family definition from font string.
+	return generateFontPreset( option );
+}
+
+// Creates a predefined preset for pixel size. It deconstructs font-family like string into full configuration option.
+// A font definition is passed as coma delimited set of font family names. Font names might be quoted.
+//
+// @param {String} A font definition form configuration.
+function generateFontPreset( fontDefinition ) {
+	// Remove quotes from font names. They will be normalized later.
+	const fontNames = fontDefinition.replace( /"|'/g, '' ).split( ',' );
+
+	// The first matched font name will be used as dropdown list item title and as model value.
+	const firstFontName = fontNames[ 0 ];
+
+	// CSS-compatible font names.
+	const cssFontNames = fontNames.map( normalizeFontNameForCSS ).join( ', ' );
+
+	return {
+		title: firstFontName,
+		model: firstFontName,
+		view: {
+			name: 'span',
+			style: {
+				'font-family': cssFontNames
+			}
+		}
+	};
+}
+
+// Normalizes font name for the style attribute. It adds braces (') if font name contains spaces.
+//
+// @param {String} fontName
+// @returns {String}
+function normalizeFontNameForCSS( fontName ) {
+	fontName = fontName.trim();
+
+	// Compound font names should be quoted.
+	if ( fontName.indexOf( ' ' ) > 0 ) {
+		fontName = `'${ fontName }'`;
+	}
+
+	return fontName;
+}

+ 113 - 0
packages/ckeditor5-font/src/fontsize.js

@@ -0,0 +1,113 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontsize
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import FontSizeEditing from './fontsize/fontsizeediting';
+import FontSizeUI from './fontsize/fontsizeui';
+
+/**
+ * The font size plugin.
+ *
+ * It enables {@link module:font/fontsize/fontsizeediting~FontSizeEditing} and
+ * {@link module:font/fontsize/fontsizeui~FontSizeUI} features in the editor.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontSize extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ FontSizeEditing, FontSizeUI ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'FontSize';
+	}
+}
+
+/**
+ * Font size option descriptor.
+ *
+ * @typedef {Object} module:font/fontsize~FontSizeOption
+ *
+ * @property {String} title The user-readable title of the option.
+ * @property {String} model Attribute's unique value in the model.
+ * @property {module:engine/view/elementdefinition~ElementDefinition} view View element configuration.
+ * @property {Array.<module:engine/view/elementdefinition~ElementDefinition>} [upcastAlso] An array with all matched elements that
+ * view to model conversion should also accept.
+ */
+
+/**
+ * The configuration of the font size feature.
+ * Introduced by the {@link module:font/fontsize/fontsizeediting~FontSizeEditing} feature.
+ *
+ * Read more in {@link module:font/fontsize~FontSizeConfig}.
+ *
+ * @member {module:font/fontsize~FontSizeConfig} module:core/editor/editorconfig~EditorConfig#fontSize
+ */
+
+/**
+ * The configuration of the font size feature.
+ * The option is used by the {@link module:font/fontsize/fontsizeediting~FontSizeEditing} feature.
+ *
+ * 		ClassicEditor
+ * 			.create( {
+ * 				fontSize: ... // Font size feature config.
+ *			} )
+ * 			.then( ... )
+ * 			.catch( ... );
+ *
+ * See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
+ *
+ * @interface module:font/fontsize~FontSizeConfig
+ */
+
+/**
+ * Available font size options. Expressed as predefined presets, numerical "pixel" values
+ * or the {@link module:font/fontsize~FontSizeOption}.
+ *
+ * The default value is:
+ *
+ *		const fontSizeConfig = {
+ *			options: [
+ *				'tiny',
+ * 				'small',
+ * 				'default',
+ * 				'big',
+ * 				'huge'
+ *			]
+ *		};
+ *
+ * It defines 4 sizes: **tiny**, **small**, **big**, and **huge**. These values will be rendered as `span` elements in view.
+ * The **default** defines a text without the `fontSize` attribute.
+ *
+ * Each `span` has the `class` attribute set to the corresponding size name. For instance, this is what the **small** size looks
+ * like in the view:
+ *
+ * 		<span class="text-small">...</span>
+ *
+ * As an alternative, the font size might be defined using the numerical values (either as a `Number` or as a `String`):
+ *
+ * 		const fontSizeConfig = {
+ * 			options: [ 9, 10, 11, 12, 13, 14, 15 ]
+ * 		};
+ *
+ * Font size can be applied using the command API. To do that, use the `fontSize` command and pass the desired font size as a `value`.
+ * For example, the below code will apply the `fontSize` attribute with the **tiny** value to the current selection:
+ *
+ *		editor.execute( 'fontSize', { value: 'tiny' } );
+ *
+ * Executing `fontSize` command without value will remove `fontSize` attribute from the current selection.
+ *
+ * @member {Array.<String|Number|module:font/fontsize~FontSizeOption>} module:font/fontsize~FontSizeConfig#options
+ */

+ 29 - 0
packages/ckeditor5-font/src/fontsize/fontsizecommand.js

@@ -0,0 +1,29 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontsize/fontsizecommand
+ */
+
+import FontCommand from '../fontcommand';
+
+/**
+ * The font size command. It is used by the {@link module:font/fontsize/fontsizeediting~FontSizeEditing}
+ * to apply the font size.
+ *
+ *		editor.execute( 'fontSize', { value: 'small' } );
+ *
+ * **Note**: Executing the command without the value removes the attribute from the model.
+ *
+ * @extends module:font/fontcommand~FontCommand
+ */
+export default class FontSizeCommand extends FontCommand {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor, 'fontSize' );
+	}
+}

+ 68 - 0
packages/ckeditor5-font/src/fontsize/fontsizeediting.js

@@ -0,0 +1,68 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontsize/fontsizeediting
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import { attributeToElement } from '@ckeditor/ckeditor5-engine/src/conversion/two-way-converters';
+
+import FontSizeCommand from './fontsizecommand';
+import { normalizeOptions } from './utils';
+
+const FONT_SIZE = 'fontSize';
+
+/**
+ * The font size editing feature.
+ *
+ * It introduces the {@link module:font/fontsize/fontsizecommand~FontSizeCommand command} and the `fontSize`
+ * attribute in the {@link module:engine/model/model~Model model} which renders in the {@link module:engine/view/view view}
+ * as a `<span>` element with either:
+ * * a style attribute (`<span style="font-size:12px">...</span>`),
+ * * or a class attribute (`<span class="text-small">...</span>`)
+ *
+ * depending on the {@link module:font/fontsize~FontSizeConfig configuration}.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontSizeEditing extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		// Define default configuration using named presets.
+		editor.config.define( FONT_SIZE, {
+			options: [
+				'tiny',
+				'small',
+				'default',
+				'big',
+				'huge'
+			]
+		} );
+
+		// Define view to model conversion.
+		const options = normalizeOptions( this.editor.config.get( 'fontSize.options' ) ).filter( item => item.model );
+
+		// Set-up the two-way conversion.
+		attributeToElement( editor.conversion, FONT_SIZE, options );
+
+		// Add FontSize command.
+		editor.commands.add( FONT_SIZE, new FontSizeCommand( editor ) );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+
+		// Allow fontSize attribute on text nodes.
+		editor.model.schema.extend( '$text', { allowAttributes: FONT_SIZE } );
+	}
+}

+ 135 - 0
packages/ckeditor5-font/src/fontsize/fontsizeui.js

@@ -0,0 +1,135 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontsize/fontsizeui
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import Model from '@ckeditor/ckeditor5-ui/src/model';
+import Collection from '@ckeditor/ckeditor5-utils/src/collection';
+
+import { createDropdown, addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
+import { normalizeOptions } from '../fontsize/utils';
+import fontSizeIcon from '../../theme/icons/font-size.svg';
+
+/**
+ * The font family UI plugin. It introduces the `'fontSize'` drop-down.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontSizeUI extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+		const t = editor.t;
+
+		const options = this._getLocalizedOptions();
+
+		const command = editor.commands.get( 'fontSize' );
+
+		// Register UI component.
+		editor.ui.componentFactory.add( 'fontSize', locale => {
+			const dropdownView = createDropdown( locale );
+			addListToDropdown( dropdownView, _prepareListOptions( options, command ) );
+
+			// Create dropdown model.
+			dropdownView.buttonView.set( {
+				label: t( 'Font Size' ),
+				icon: fontSizeIcon,
+				tooltip: true
+			} );
+
+			dropdownView.extendTemplate( {
+				attributes: {
+					class: [
+						'ck-font-size-dropdown'
+					]
+				}
+			} );
+
+			dropdownView.bind( 'isEnabled' ).to( command );
+
+			// Execute command when an item from the dropdown is selected.
+			this.listenTo( dropdownView, 'execute', evt => {
+				editor.execute( evt.source.commandName, { value: evt.source.commandParam } );
+				editor.editing.view.focus();
+			} );
+
+			return dropdownView;
+		} );
+	}
+
+	/**
+	 * Returns options as defined in `config.fontSize.options` but processed to consider
+	 * editor localization, i.e. to display {@link module:font/fontsize~FontSizeOption}
+	 * in the correct language.
+	 *
+	 * Note: The reason behind this method is that there's no way to use {@link module:utils/locale~Locale#t}
+	 * when the user config is defined because the editor does not exist yet.
+	 *
+	 * @private
+	 * @returns {Array.<module:font/fontsize~FontSizeOption>}.
+	 */
+	_getLocalizedOptions() {
+		const editor = this.editor;
+		const t = editor.t;
+
+		const localizedTitles = {
+			Default: t( 'Default' ),
+			Tiny: t( 'Tiny' ),
+			Small: t( 'Small' ),
+			Big: t( 'Big' ),
+			Huge: t( 'Huge' )
+		};
+
+		const options = normalizeOptions( editor.config.get( 'fontSize.options' ) );
+
+		return options.map( option => {
+			const title = localizedTitles[ option.title ];
+
+			if ( title && title != option.title ) {
+				// Clone the option to avoid altering the original `namedPresets` from `./utils.js`.
+				option = Object.assign( {}, option, { title } );
+			}
+
+			return option;
+		} );
+	}
+}
+
+// Prepares FontSize dropdown items.
+// @private
+// @param {Array.<module:font/fontsize~FontSizeOption>} options
+// @param {module:font/fontsize/fontsizecommand~FontSizeCommand} command
+function _prepareListOptions( options, command ) {
+	const dropdownItems = new Collection();
+
+	for ( const option of options ) {
+		const itemModel = new Model( {
+			commandName: 'fontSize',
+			commandParam: option.model,
+			label: option.title,
+			class: 'ck-fontsize-option'
+		} );
+
+		if ( option.view && option.view.style ) {
+			itemModel.set( 'style', `font-size:${ option.view.style[ 'font-size' ] }` );
+		}
+
+		if ( option.view && option.view.class ) {
+			itemModel.set( 'class', `${ itemModel.class } ${ option.view.class }` );
+		}
+
+		itemModel.bind( 'isActive' ).to( command, 'value', value => value === option.model );
+
+		// Add the option to the collection.
+		dropdownItems.add( itemModel );
+	}
+
+	return dropdownItems;
+}

+ 114 - 0
packages/ckeditor5-font/src/fontsize/utils.js

@@ -0,0 +1,114 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontsize/utils
+ */
+
+/**
+ * Normalizes and translates the {@link module:font/fontsize~FontSizeConfig#options config options}
+ * to the {@link module:font/fontsize~FontSizeOption} format.
+ *
+ * @param {Array.<String|Number|Object>} configuredOptions An array of options taken from configuration.
+ * @returns {Array.<module:font/fontsize~FontSizeOption>}
+ */
+export function normalizeOptions( configuredOptions ) {
+	// Convert options to objects.
+	return configuredOptions
+		.map( getOptionDefinition )
+		// Filter out undefined values that `getOptionDefinition` might return.
+		.filter( option => !!option );
+}
+
+// Default named presets map.
+const namedPresets = {
+	tiny: {
+		title: 'Tiny',
+		model: 'tiny',
+		view: {
+			name: 'span',
+			class: 'text-tiny'
+		}
+	},
+	small: {
+		title: 'Small',
+		model: 'small',
+		view: {
+			name: 'span',
+			class: 'text-small'
+		}
+	},
+	big: {
+		title: 'Big',
+		model: 'big',
+		view: {
+			name: 'span',
+			class: 'text-big'
+		}
+	},
+	huge: {
+		title: 'Huge',
+		model: 'huge',
+		view: {
+			name: 'span',
+			class: 'text-huge'
+		}
+	}
+};
+
+// Returns an option definition either from preset or creates one from number shortcut.
+// If object is passed then this method will return it without alternating it. Returns undefined for item than cannot be parsed.
+//
+// @param {String|Number|Object} item
+// @returns {undefined|module:font/fontsize~FontSizeOption}
+function getOptionDefinition( option ) {
+	// Treat any object as full item definition provided by user in configuration.
+	if ( typeof option === 'object' ) {
+		return option;
+	}
+
+	// Item is a named preset.
+	if ( namedPresets[ option ] ) {
+		return namedPresets[ option ];
+	}
+
+	// 'Default' font size. It will be used to remove the fontSize attribute.
+	if ( option === 'default' ) {
+		return {
+			model: undefined,
+			title: 'Default'
+		};
+	}
+
+	// At this stage we probably have numerical value to generate a preset so parse it's value.
+	const sizePreset = parseFloat( option );
+
+	// Discard any faulty values.
+	if ( isNaN( sizePreset ) ) {
+		return;
+	}
+
+	// Return font size definition from size value.
+	return generatePixelPreset( sizePreset );
+}
+
+// Creates a predefined preset for pixel size.
+//
+// @param {Number} size Font size in pixels.
+// @returns {module:font/fontsize~FontSizeOption}
+function generatePixelPreset( size ) {
+	const sizeName = String( size );
+
+	return {
+		title: sizeName,
+		model: size,
+		view: {
+			name: 'span',
+			style: {
+				'font-size': `${ size }px`
+			}
+		}
+	};
+}

+ 18 - 0
packages/ckeditor5-font/tests/font.js

@@ -0,0 +1,18 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import Font from './../src/font';
+import FontFamily from './../src/fontfamily';
+import FontSize from './../src/fontsize';
+
+describe( 'Font', () => {
+	it( 'requires FontSize', () => {
+		expect( Font.requires ).to.deep.equal( [ FontFamily, FontSize ] );
+	} );
+
+	it( 'defines plugin name', () => {
+		expect( Font.pluginName ).to.equal( 'Font' );
+	} );
+} );

+ 279 - 0
packages/ckeditor5-font/tests/fontcommand.js

@@ -0,0 +1,279 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import FontCommand from '../src/fontcommand';
+
+import Command from '@ckeditor/ckeditor5-core/src/command';
+import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
+import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import Range from '@ckeditor/ckeditor5-engine/src/model/range';
+import Position from '@ckeditor/ckeditor5-engine/src/model/position';
+
+describe( 'FontCommand', () => {
+	let editor, model, doc, root, command;
+
+	beforeEach( () => {
+		return ModelTestEditor.create()
+			.then( newEditor => {
+				editor = newEditor;
+				model = editor.model;
+				doc = model.document;
+				root = doc.getRoot();
+
+				command = new FontCommand( editor, 'font' );
+				editor.commands.add( 'font', command );
+
+				model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+				model.schema.register( 'img', {
+					allowWhere: [ '$block', '$text' ],
+					isObject: true
+				} );
+
+				model.schema.extend( '$text', { allowAttributes: 'font' } );
+			} );
+	} );
+
+	afterEach( () => {
+		editor.destroy();
+	} );
+
+	it( 'is a command', () => {
+		expect( FontCommand.prototype ).to.be.instanceOf( Command );
+		expect( command ).to.be.instanceOf( Command );
+	} );
+
+	describe( 'value', () => {
+		it( 'is set to font value when selection is in text with font attribute', () => {
+			setData( model, '<paragraph><$text font="foo">fo[]o</$text></paragraph>' );
+
+			expect( command ).to.have.property( 'value', 'foo' );
+		} );
+
+		it( 'is undefined when selection is not in text with font attribute', () => {
+			setData( model, '<paragraph>fo[]o</paragraph>' );
+
+			expect( command ).to.have.property( 'value', undefined );
+		} );
+	} );
+
+	describe( 'isEnabled', () => {
+		it( 'is true when selection is on text which can have font added', () => {
+			setData( model, '<paragraph>fo[]o</paragraph>' );
+
+			expect( command ).to.have.property( 'isEnabled', true );
+		} );
+	} );
+
+	describe( 'execute()', () => {
+		it( 'should do nothing if the command is disabled', () => {
+			setData( model, '<paragraph>fo[ob]ar</paragraph>' );
+
+			command.isEnabled = false;
+
+			command.execute( { value: 'foo' } );
+
+			expect( getData( model ) ).to.equal( '<paragraph>fo[ob]ar</paragraph>' );
+		} );
+
+		it( 'should add font attribute on selected text', () => {
+			setData( model, '<paragraph>a[bc<$text font="foo">fo]obar</$text>xyz</paragraph>' );
+
+			expect( command.value ).to.be.undefined;
+
+			command.execute( { value: 'foo' } );
+
+			expect( command.value ).to.equal( 'foo' );
+
+			expect( getData( model ) ).to.equal( '<paragraph>a[<$text font="foo">bcfo]obar</$text>xyz</paragraph>' );
+		} );
+
+		it( 'should add font attribute on selected nodes (multiple nodes)', () => {
+			setData(
+				model,
+				'<paragraph>abcabc[abc</paragraph>' +
+				'<paragraph>foofoofoo</paragraph>' +
+				'<paragraph>barbar]bar</paragraph>'
+			);
+
+			command.execute( { value: 'foo' } );
+
+			expect( command.value ).to.equal( 'foo' );
+
+			expect( getData( model ) ).to.equal(
+				'<paragraph>abcabc[<$text font="foo">abc</$text></paragraph>' +
+				'<paragraph><$text font="foo">foofoofoo</$text></paragraph>' +
+				'<paragraph><$text font="foo">barbar</$text>]bar</paragraph>'
+			);
+		} );
+
+		it( 'should change font attribute on selected nodes', () => {
+			setData(
+				model,
+				'<paragraph>abc[abc<$text font="text-small">abc</$text></paragraph>' +
+				'<paragraph><$text font="text-small">foofoofoo</$text></paragraph>' +
+				'<paragraph><$text font="text-small">bar]bar</$text>bar</paragraph>'
+			);
+
+			command.execute( { value: 'foo' } );
+
+			expect( command.value ).to.equal( 'foo' );
+
+			expect( getData( model ) ).to.equal(
+				'<paragraph>abc[<$text font="foo">abcabc</$text></paragraph>' +
+				'<paragraph><$text font="foo">foofoofoo</$text></paragraph>' +
+				'<paragraph><$text font="foo">bar</$text>]<$text font="text-small">bar</$text>bar</paragraph>'
+			);
+		} );
+
+		it( 'should remove font attribute on selected nodes when passing undefined value', () => {
+			setData(
+				model,
+				'<paragraph>abcabc[<$text font="foo">abc</$text></paragraph>' +
+				'<paragraph><$text font="foo">foofoofoo</$text></paragraph>' +
+				'<paragraph><$text font="foo">barbar</$text>]bar</paragraph>'
+			);
+			expect( command.value ).to.equal( 'foo' );
+
+			command.execute();
+
+			expect( command.value ).to.be.undefined;
+
+			expect( getData( model ) ).to.equal(
+				'<paragraph>abcabc[abc</paragraph>' +
+				'<paragraph>foofoofoo</paragraph>' +
+				'<paragraph>barbar]bar</paragraph>'
+			);
+		} );
+
+		it( 'should change selection attribute if selection is collapsed in non-empty parent', () => {
+			setData( model, '<paragraph>a[]bc<$text font="foo">foobar</$text>xyz</paragraph><paragraph></paragraph>' );
+
+			expect( command.value ).to.be.undefined;
+
+			command.execute( { value: 'foo' } );
+
+			expect( command.value ).to.equal( 'foo' );
+			expect( doc.selection.hasAttribute( 'font' ) ).to.be.true;
+
+			command.execute();
+
+			expect( command.value ).to.be.undefined;
+			expect( doc.selection.hasAttribute( 'font' ) ).to.be.false;
+		} );
+
+		it( 'should not store attribute change on selection if selection is collapsed in non-empty parent', () => {
+			setData( model, '<paragraph>a[]bc<$text font="foo">foobar</$text>xyz</paragraph>' );
+
+			command.execute( { value: 'foo' } );
+
+			// It should not save that bold was executed at position ( root, [ 0, 1 ] ).
+
+			model.change( writer => {
+				// Simulate clicking right arrow key by changing selection ranges.
+				writer.setSelection( [ new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 0, 2 ] ) ) ] );
+
+				// Get back to previous selection.
+				writer.setSelection( [ new Range( new Position( root, [ 0, 1 ] ), new Position( root, [ 0, 1 ] ) ) ] );
+			} );
+
+			expect( command.value ).to.be.undefined;
+		} );
+
+		it( 'should change selection attribute and store it if selection is collapsed in empty parent', () => {
+			setData( model, '<paragraph>abc<$text font="foo">foobar</$text>xyz</paragraph><paragraph>[]</paragraph>' );
+
+			expect( command.value ).to.be.undefined;
+
+			command.execute( { value: 'foo' } );
+
+			expect( command.value ).to.equal( 'foo' );
+			expect( doc.selection.hasAttribute( 'font' ) ).to.be.true;
+
+			// Attribute should be stored.
+			// Simulate clicking somewhere else in the editor.
+			model.change( writer => {
+				writer.setSelection( [ new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 0, 2 ] ) ) ] );
+			} );
+
+			expect( command.value ).to.be.undefined;
+
+			// Go back to where attribute was stored.
+			model.change( writer => {
+				writer.setSelection( [ new Range( new Position( root, [ 1, 0 ] ), new Position( root, [ 1, 0 ] ) ) ] );
+			} );
+
+			// Attribute should be restored.
+			expect( command.value ).to.equal( 'foo' );
+
+			command.execute();
+
+			expect( command.value ).to.be.undefined;
+			expect( doc.selection.hasAttribute( 'font' ) ).to.be.false;
+		} );
+
+		it( 'should not apply attribute change where it would invalid schema', () => {
+			model.schema.register( 'image', { inheritAllFrom: '$block' } );
+			setData( model, '<paragraph>ab[c<img></img><$text font="foo">foobar</$text>xy<img></img>]z</paragraph>' );
+
+			expect( command.isEnabled ).to.be.true;
+
+			command.execute( { value: 'foo' } );
+
+			expect( getData( model ) ).to.equal(
+				'<paragraph>ab[<$text font="foo">c</$text><img></img><$text font="foo">foobarxy</$text><img></img>]z</paragraph>'
+			);
+		} );
+
+		it( 'should use parent batch for storing undo steps', () => {
+			setData( model, '<paragraph>a[bc<$text font="foo">fo]obar</$text>xyz</paragraph>' );
+
+			model.change( writer => {
+				expect( writer.batch.deltas.length ).to.equal( 0 );
+				command.execute( { value: 'foo' } );
+				expect( writer.batch.deltas.length ).to.equal( 1 );
+			} );
+
+			expect( getData( model ) ).to.equal( '<paragraph>a[<$text font="foo">bcfo]obar</$text>xyz</paragraph>' );
+		} );
+
+		describe( 'should cause firing model change event', () => {
+			let spy;
+
+			beforeEach( () => {
+				spy = sinon.spy();
+			} );
+
+			it( 'collapsed selection in non-empty parent', () => {
+				setData( model, '<paragraph>x[]y</paragraph>' );
+
+				model.document.on( 'change', spy );
+
+				command.execute( { value: 'foo' } );
+
+				expect( spy.called ).to.be.true;
+			} );
+
+			it( 'non-collapsed selection', () => {
+				setData( model, '<paragraph>[xy]</paragraph>' );
+
+				model.document.on( 'change', spy );
+
+				command.execute( { value: 'foo' } );
+
+				expect( spy.called ).to.be.true;
+			} );
+
+			it( 'in empty parent', () => {
+				setData( model, '<paragraph>[]</paragraph>' );
+
+				model.document.on( 'change', spy );
+
+				command.execute( { value: 'foo' } );
+
+				expect( spy.called ).to.be.true;
+			} );
+		} );
+	} );
+} );

+ 18 - 0
packages/ckeditor5-font/tests/fontfamily.js

@@ -0,0 +1,18 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import FontFamily from './../src/fontfamily';
+import FontFamilyEditing from './../src/fontfamily/fontfamilyediting';
+import FontFamilyUI from '../src/fontfamily/fontfamilyui';
+
+describe( 'FontFamily', () => {
+	it( 'requires FontFamilyEditing and FontFamilyUI', () => {
+		expect( FontFamily.requires ).to.deep.equal( [ FontFamilyEditing, FontFamilyUI ] );
+	} );
+
+	it( 'defines plugin name', () => {
+		expect( FontFamily.pluginName ).to.equal( 'FontFamily' );
+	} );
+} );

+ 35 - 0
packages/ckeditor5-font/tests/fontfamily/fontfamilycommand.js

@@ -0,0 +1,35 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import FontFamilyCommand from '../../src/fontfamily/fontfamilycommand';
+import FontCommand from '../../src/fontcommand';
+
+import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
+
+describe( 'FontFamilyCommand', () => {
+	let editor, command;
+
+	beforeEach( () => {
+		return ModelTestEditor.create()
+			.then( newEditor => {
+				editor = newEditor;
+
+				command = new FontFamilyCommand( editor );
+			} );
+	} );
+
+	afterEach( () => {
+		editor.destroy();
+	} );
+
+	it( 'is a FontCommand', () => {
+		expect( FontFamilyCommand.prototype ).to.be.instanceOf( FontCommand );
+		expect( command ).to.be.instanceOf( FontCommand );
+	} );
+
+	it( 'operates on fontFamily attribute', () => {
+		expect( command ).to.have.property( 'attributeKey', 'fontFamily' );
+	} );
+} );

+ 201 - 0
packages/ckeditor5-font/tests/fontfamily/fontfamilyediting.js

@@ -0,0 +1,201 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import FontFamilyEditing from './../../src/fontfamily/fontfamilyediting';
+
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+
+import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
+import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
+describe( 'FontFamilyEditing', () => {
+	let editor, doc;
+
+	beforeEach( () => {
+		return VirtualTestEditor
+			.create( {
+				plugins: [ FontFamilyEditing, Paragraph ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+
+				doc = editor.document;
+			} );
+	} );
+
+	afterEach( () => {
+		editor.destroy();
+	} );
+
+	it( 'should set proper schema rules', () => {
+		expect( editor.model.schema.checkAttribute( [ '$block', '$text' ], 'fontFamily' ) ).to.be.true;
+		expect( editor.model.schema.checkAttribute( [ '$clipboardHolder', '$text' ], 'fontFamily' ) ).to.be.true;
+
+		expect( editor.model.schema.checkAttribute( [ '$block' ], 'fontFamily' ) ).to.be.false;
+	} );
+
+	describe( 'config', () => {
+		describe( 'default value', () => {
+			it( 'should be set', () => {
+				expect( editor.config.get( 'fontFamily.options' ) ).to.deep.equal( [
+					'default',
+					'Arial, Helvetica, sans-serif',
+					'Courier New, Courier, monospace',
+					'Georgia, serif',
+					'Lucida Sans Unicode, Lucida Grande, sans-serif',
+					'Tahoma, Geneva, sans-serif',
+					'Times New Roman, Times, serif',
+					'Trebuchet MS, Helvetica, sans-serif',
+					'Verdana, Geneva, sans-serif'
+				] );
+			} );
+		} );
+	} );
+
+	describe( 'editing pipeline conversion', () => {
+		beforeEach( () => {
+			return VirtualTestEditor
+				.create( {
+					plugins: [ FontFamilyEditing, Paragraph ],
+					fontFamily: {
+						options: [
+							'Arial',
+							'Lucida Sans Unicode, Lucida Grande, sans-serif',
+							{
+								title: 'My font',
+								model: 'my',
+								view: {
+									name: 'mark',
+									class: 'my-style'
+								}
+							}
+						]
+					}
+				} )
+				.then( newEditor => {
+					editor = newEditor;
+
+					doc = editor.model;
+				} );
+		} );
+
+		it( 'should discard unknown fontFamily attribute values', () => {
+			setModelData( doc, '<paragraph>f<$text fontFamily="foo-bar">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>foo</p>' );
+		} );
+
+		it( 'should convert fontFamily attribute to configured simple preset', () => {
+			setModelData( doc, '<paragraph>f<$text fontFamily="Arial">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>f<span style="font-family:Arial;">o</span>o</p>' );
+		} );
+
+		it( 'should convert fontFamily attribute to configured complex preset', () => {
+			setModelData( doc, '<paragraph>f<$text fontFamily="Lucida Sans Unicode">o</$text>o</paragraph>' );
+
+			expect( editor.getData() )
+				.to.equal( '<p>f<span style="font-family:\'Lucida Sans Unicode\', \'Lucida Grande\', sans-serif;">o</span>o</p>' );
+		} );
+
+		it( 'should convert fontFamily attribute from user defined settings', () => {
+			setModelData( doc, '<paragraph>f<$text fontFamily="my">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>f<mark class="my-style">o</mark>o</p>' );
+		} );
+	} );
+
+	describe( 'data pipeline conversions', () => {
+		beforeEach( () => {
+			return VirtualTestEditor
+				.create( {
+					plugins: [ FontFamilyEditing, Paragraph ],
+					fontFamily: {
+						options: [
+							'Lucida Sans Unicode, Lucida Grande, sans-serif',
+							{
+								title: 'My other setting',
+								model: 'my-other',
+								view: {
+									name: 'span',
+									style: { 'font-family': 'Other' }
+								}
+							},
+							{
+								title: 'My setting',
+								model: 'my',
+								view: {
+									name: 'mark',
+									style: { 'font-family': 'Verdana' },
+									class: 'my-style'
+								}
+							},
+							{
+								title: 'Hybrid',
+								model: 'complex',
+								view: {
+									name: 'span',
+									class: [ 'text-complex' ]
+								},
+								upcastAlso: [
+									{ name: 'span', style: { 'font-family': 'Arial' } },
+									{ name: 'span', style: { 'font-family': 'Arial,sans-serif' } },
+									{ name: 'span', attribute: { 'data-font': 'Arial' } }
+								]
+							}
+						]
+					}
+				} )
+				.then( newEditor => {
+					editor = newEditor;
+
+					doc = editor.model;
+				} );
+		} );
+
+		it( 'should convert from element with defined style when with other styles', () => {
+			const data = '<p>f<span style="font-family: Other;font-size: 18px">o</span>o</p>';
+
+			editor.setData( data );
+
+			expect( getModelData( doc ) ).to.equal( '<paragraph>[]f<$text fontFamily="my-other">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>f<span style="font-family:Other;">o</span>o</p>' );
+		} );
+
+		it( 'should convert from user defined element', () => {
+			const data = '<p>f<mark class="my-style" style="font-family:Verdana;">o</mark>o</p>';
+
+			editor.setData( data );
+
+			expect( getModelData( doc ) ).to.equal( '<paragraph>[]f<$text fontFamily="my">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( data );
+		} );
+
+		it( 'should convert from complex definitions', () => {
+			editor.setData(
+				'<p>f<span style="font-family:Arial;">o</span>o</p>' +
+				'<p>f<span style="font-family: Arial,sans-serif">o</span>o</p>' +
+				'<p>b<span data-font="Arial">a</span>r</p>' +
+				'<p>b<span class="text-complex">a</span>z</p>'
+			);
+
+			expect( getModelData( doc ) ).to.equal(
+				'<paragraph>[]f<$text fontFamily="complex">o</$text>o</paragraph>' +
+				'<paragraph>f<$text fontFamily="complex">o</$text>o</paragraph>' +
+				'<paragraph>b<$text fontFamily="complex">a</$text>r</paragraph>' +
+				'<paragraph>b<$text fontFamily="complex">a</$text>z</paragraph>'
+			);
+
+			expect( editor.getData() ).to.equal(
+				'<p>f<span class="text-complex">o</span>o</p>' +
+				'<p>f<span class="text-complex">o</span>o</p>' +
+				'<p>b<span class="text-complex">a</span>r</p>' +
+				'<p>b<span class="text-complex">a</span>z</p>'
+			);
+		} );
+	} );
+} );

+ 163 - 0
packages/ckeditor5-font/tests/fontfamily/fontfamilyui.js

@@ -0,0 +1,163 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global document */
+
+import FontFamilyEditing from '../../src/fontfamily/fontfamilyediting';
+import FontFamilyUI from '../../src/fontfamily/fontfamilyui';
+
+import fontFamilyIcon from '../../theme/icons/font-family.svg';
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import { add as addTranslations, _clear as clearTranslations } from '@ckeditor/ckeditor5-utils/src/translation-service';
+
+testUtils.createSinonSandbox();
+
+describe( 'FontFamilyUI', () => {
+	let editor, command, element;
+
+	before( () => {
+		addTranslations( 'en', {
+			'Font Family': 'Font Family',
+			'Default': 'Default'
+		} );
+
+		addTranslations( 'pl', {
+			'Font Family': 'Czcionka',
+			'Default': 'Domyślna'
+		} );
+	} );
+
+	after( () => {
+		clearTranslations();
+	} );
+
+	beforeEach( () => {
+		element = document.createElement( 'div' );
+		document.body.appendChild( element );
+
+		return ClassicTestEditor
+			.create( element, {
+				plugins: [ FontFamilyEditing, FontFamilyUI ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+			} );
+	} );
+
+	afterEach( () => {
+		element.remove();
+
+		return editor.destroy();
+	} );
+
+	describe( 'fontFamily Dropdown', () => {
+		let dropdown;
+
+		beforeEach( () => {
+			command = editor.commands.get( 'fontFamily' );
+			dropdown = editor.ui.componentFactory.create( 'fontFamily' );
+		} );
+
+		it( 'button has the base properties', () => {
+			const button = dropdown.buttonView;
+
+			expect( button ).to.have.property( 'label', 'Font Family' );
+			expect( button ).to.have.property( 'tooltip', true );
+			expect( button ).to.have.property( 'icon', fontFamilyIcon );
+		} );
+
+		it( 'should add custom CSS class to dropdown', () => {
+			const dropdown = editor.ui.componentFactory.create( 'fontFamily' );
+
+			dropdown.render();
+
+			expect( dropdown.element.classList.contains( 'ck-font-family-dropdown' ) ).to.be.true;
+		} );
+
+		it( 'should focus view after command execution', () => {
+			const focusSpy = testUtils.sinon.spy( editor.editing.view, 'focus' );
+			const dropdown = editor.ui.componentFactory.create( 'fontFamily' );
+
+			dropdown.commandName = 'fontFamily';
+			dropdown.fire( 'execute' );
+
+			sinon.assert.calledOnce( focusSpy );
+		} );
+
+		it( 'should activate current option in dropdown', () => {
+			const listView = dropdown.listView;
+
+			command.value = undefined;
+
+			// The first item is 'default' font family.
+			expect( listView.items.map( item => item.isActive ) )
+				.to.deep.equal( [ true, false, false, false, false, false, false, false, false ] );
+
+			command.value = 'Arial';
+
+			// The second item is 'Arial' font family.
+			expect( listView.items.map( item => item.isActive ) )
+				.to.deep.equal( [ false, true, false, false, false, false, false, false, false ] );
+		} );
+
+		describe( 'model to command binding', () => {
+			it( 'isEnabled', () => {
+				command.isEnabled = false;
+
+				expect( dropdown.buttonView.isEnabled ).to.be.false;
+
+				command.isEnabled = true;
+				expect( dropdown.buttonView.isEnabled ).to.be.true;
+			} );
+		} );
+
+		describe( 'localization', () => {
+			beforeEach( () => {
+				return localizedEditor( [ 'default', 'Arial' ] );
+			} );
+
+			it( 'works for the #buttonView', () => {
+				const buttonView = dropdown.buttonView;
+
+				expect( buttonView.label ).to.equal( 'Czcionka' );
+			} );
+
+			it( 'works for the listView#items in the panel', () => {
+				const listView = dropdown.listView;
+
+				expect( listView.items.map( item => item.label ) ).to.deep.equal( [
+					'Domyślna',
+					'Arial'
+				] );
+			} );
+
+			function localizedEditor( options ) {
+				const editorElement = document.createElement( 'div' );
+				document.body.appendChild( editorElement );
+
+				return ClassicTestEditor
+					.create( editorElement, {
+						plugins: [ FontFamilyEditing, FontFamilyUI ],
+						toolbar: [ 'fontFamily' ],
+						language: 'pl',
+						fontFamily: {
+							options
+						}
+					} )
+					.then( newEditor => {
+						editor = newEditor;
+						dropdown = editor.ui.componentFactory.create( 'fontFamily' );
+						command = editor.commands.get( 'fontFamily' );
+
+						editorElement.remove();
+
+						return editor.destroy();
+					} );
+			}
+		} );
+	} );
+} );

+ 86 - 0
packages/ckeditor5-font/tests/fontfamily/utils.js

@@ -0,0 +1,86 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import { normalizeOptions } from '../../src/fontfamily/utils';
+
+describe( 'FontFamily utils', () => {
+	describe( 'normalizeOptions()', () => {
+		it( 'should discard unsupported values', () => {
+			expect( normalizeOptions( [ () => {}, 0, true ] ) ).to.deep.equal( [] );
+		} );
+
+		it( 'should pass through object definition', () => {
+			expect( normalizeOptions( [
+				'default',
+				{
+					title: 'Comic Sans',
+					model: 'comic',
+					view: {
+						name: 'span',
+						style: {
+							'font-family': 'Comic Sans'
+						}
+					}
+				}
+			] ) ).to.deep.equal( [
+				{
+					model: undefined,
+					title: 'Default'
+				},
+				{
+					title: 'Comic Sans',
+					model: 'comic',
+					view: {
+						name: 'span',
+						style: {
+							'font-family': 'Comic Sans'
+						}
+					}
+				}
+			] );
+		} );
+
+		describe( 'shorthand presets', () => {
+			it( 'should return full preset from string presets', () => {
+				expect( normalizeOptions( ( [
+					'Arial',
+					'"Comic Sans MS", sans-serif',
+					'Lucida Console, \'Courier New\', Courier, monospace'
+				] ) ) ).to.deep.equal( [
+					{
+						title: 'Arial',
+						model: 'Arial',
+						view: {
+							name: 'span',
+							style: {
+								'font-family': 'Arial'
+							}
+						}
+					},
+					{
+						title: 'Comic Sans MS',
+						model: 'Comic Sans MS',
+						view: {
+							name: 'span',
+							style: {
+								'font-family': '\'Comic Sans MS\', sans-serif'
+							}
+						}
+					},
+					{
+						title: 'Lucida Console',
+						model: 'Lucida Console',
+						view: {
+							name: 'span',
+							style: {
+								'font-family': '\'Lucida Console\', \'Courier New\', Courier, monospace'
+							}
+						}
+					}
+				] );
+			} );
+		} );
+	} );
+} );

+ 18 - 0
packages/ckeditor5-font/tests/fontsize.js

@@ -0,0 +1,18 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import FontSize from './../src/fontsize';
+import FontSizeEditing from './../src/fontsize/fontsizeediting';
+import FontSizeUI from './../src/fontsize/fontsizeui';
+
+describe( 'FontSize', () => {
+	it( 'requires FontSizeEditing & FontSizeUI', () => {
+		expect( FontSize.requires ).to.deep.equal( [ FontSizeEditing, FontSizeUI ] );
+	} );
+
+	it( 'defines plugin name', () => {
+		expect( FontSize.pluginName ).to.equal( 'FontSize' );
+	} );
+} );

+ 35 - 0
packages/ckeditor5-font/tests/fontsize/fontsizecommand.js

@@ -0,0 +1,35 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import FontSizeCommand from '../../src/fontsize/fontsizecommand';
+import FontCommand from '../../src/fontcommand';
+
+import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
+
+describe( 'FontSizeCommand', () => {
+	let editor, command;
+
+	beforeEach( () => {
+		return ModelTestEditor.create()
+			.then( newEditor => {
+				editor = newEditor;
+
+				command = new FontSizeCommand( editor );
+			} );
+	} );
+
+	afterEach( () => {
+		editor.destroy();
+	} );
+
+	it( 'is a FontCommand', () => {
+		expect( FontSizeCommand.prototype ).to.be.instanceOf( FontCommand );
+		expect( command ).to.be.instanceOf( FontCommand );
+	} );
+
+	it( 'operates on fontSize attribute', () => {
+		expect( command ).to.have.property( 'attributeKey', 'fontSize' );
+	} );
+} );

+ 220 - 0
packages/ckeditor5-font/tests/fontsize/fontsizeediting.js

@@ -0,0 +1,220 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import FontSizeEditing from './../../src/fontsize/fontsizeediting';
+
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+
+import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
+import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
+describe( 'FontSizeEditing', () => {
+	let editor, doc;
+
+	beforeEach( () => {
+		return VirtualTestEditor
+			.create( {
+				plugins: [ FontSizeEditing, Paragraph ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+
+				doc = editor.document;
+			} );
+	} );
+
+	afterEach( () => {
+		editor.destroy();
+	} );
+
+	it( 'should set proper schema rules', () => {
+		expect( editor.model.schema.checkAttribute( [ '$block', '$text' ], 'fontSize' ) ).to.be.true;
+		expect( editor.model.schema.checkAttribute( [ '$clipboardHolder', '$text' ], 'fontSize' ) ).to.be.true;
+
+		expect( editor.model.schema.checkAttribute( [ '$block' ], 'fontSize' ) ).to.be.false;
+	} );
+
+	describe( 'config', () => {
+		describe( 'default value', () => {
+			it( 'should be set', () => {
+				expect( editor.config.get( 'fontSize.options' ) ).to.deep.equal( [ 'tiny', 'small', 'default', 'big', 'huge' ] );
+			} );
+		} );
+	} );
+
+	describe( 'editing pipeline conversion', () => {
+		beforeEach( () => {
+			return VirtualTestEditor
+				.create( {
+					plugins: [ FontSizeEditing, Paragraph ],
+					fontSize: {
+						options: [
+							'tiny',
+							'default',
+							18,
+							{
+								title: 'My setting',
+								model: 'my',
+								view: {
+									name: 'mark',
+									style: { 'font-size': '30px' },
+									class: 'my-style'
+								}
+							}
+						]
+					}
+				} )
+				.then( newEditor => {
+					editor = newEditor;
+
+					doc = editor.model;
+				} );
+		} );
+
+		it( 'should discard unknown fontSize attribute values', () => {
+			setModelData( doc, '<paragraph>f<$text fontSize="foo-bar">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>foo</p>' );
+		} );
+
+		it( 'should convert fontSize attribute to predefined named preset', () => {
+			setModelData( doc, '<paragraph>f<$text fontSize="tiny">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>f<span class="text-tiny">o</span>o</p>' );
+		} );
+
+		it( 'should convert fontSize attribute to predefined pixel size preset', () => {
+			setModelData( doc, '<paragraph>f<$text fontSize="18">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>f<span style="font-size:18px;">o</span>o</p>' );
+		} );
+
+		it( 'should convert fontSize attribute from user defined settings', () => {
+			setModelData( doc, '<paragraph>f<$text fontSize="my">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>f<mark class="my-style" style="font-size:30px;">o</mark>o</p>' );
+		} );
+	} );
+
+	describe( 'data pipeline conversions', () => {
+		beforeEach( () => {
+			return VirtualTestEditor
+				.create( {
+					plugins: [ FontSizeEditing, Paragraph ],
+					fontSize: {
+						options: [
+							'tiny',
+							'default',
+							18,
+							{
+								title: 'My setting',
+								model: 'my',
+								view: {
+									name: 'mark',
+									style: { 'font-size': '30px' },
+									class: 'my-style'
+								}
+							},
+							{
+								title: 'Big multiple classes',
+								model: 'big-multiple',
+								view: {
+									name: 'span',
+									class: [ 'foo', 'foo-big' ]
+								}
+							},
+							{
+								title: 'Hybrid',
+								model: 'complex',
+								view: {
+									name: 'span',
+									class: [ 'text-complex' ]
+								},
+								upcastAlso: [
+									{ name: 'span', style: { 'font-size': '77em' } },
+									{ name: 'span', attribute: { 'data-size': '77em' } }
+								]
+							}
+						]
+					}
+				} )
+				.then( newEditor => {
+					editor = newEditor;
+
+					doc = editor.model;
+				} );
+		} );
+
+		it( 'should convert from element with defined class', () => {
+			const data = '<p>f<span class="text-tiny foo bar">o</span>o</p>';
+
+			editor.setData( data );
+
+			expect( getModelData( doc ) ).to.equal( '<paragraph>[]f<$text fontSize="tiny">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>f<span class="text-tiny">o</span>o</p>' );
+		} );
+
+		it( 'should convert from element with defined multiple classes', () => {
+			const data = '<p>f<span class="foo foo-big bar">o</span>o</p>';
+
+			editor.setData( data );
+
+			expect( getModelData( doc ) ).to.equal( '<paragraph>[]f<$text fontSize="big-multiple">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>f<span class="foo foo-big">o</span>o</p>' );
+		} );
+
+		it( 'should convert from element with defined style', () => {
+			const data = '<p>f<span style="font-size:18px;">o</span>o</p>';
+
+			editor.setData( data );
+
+			expect( getModelData( doc ) ).to.equal( '<paragraph>[]f<$text fontSize="18">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( data );
+		} );
+
+		it( 'should convert from element with defined style when with other styles', () => {
+			const data = '<p>f<span style="font-family: serif;font-size: 18px">o</span>o</p>';
+
+			editor.setData( data );
+
+			expect( getModelData( doc ) ).to.equal( '<paragraph>[]f<$text fontSize="18">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>f<span style="font-size:18px;">o</span>o</p>' );
+		} );
+
+		it( 'should convert from user defined element', () => {
+			const data = '<p>f<mark class="my-style" style="font-size:30px;">o</mark>o</p>';
+
+			editor.setData( data );
+
+			expect( getModelData( doc ) ).to.equal( '<paragraph>[]f<$text fontSize="my">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( data );
+		} );
+
+		it( 'should convert from complex definitions', () => {
+			editor.setData(
+				'<p>f<span style="font-size: 77em;">o</span>o</p>' +
+				'<p>b<span data-size="77em">a</span>r</p>' +
+				'<p>b<span class="text-complex">a</span>z</p>'
+			);
+
+			expect( getModelData( doc ) ).to.equal(
+				'<paragraph>[]f<$text fontSize="complex">o</$text>o</paragraph>' +
+				'<paragraph>b<$text fontSize="complex">a</$text>r</paragraph>' +
+				'<paragraph>b<$text fontSize="complex">a</$text>z</paragraph>'
+			);
+
+			expect( editor.getData() ).to.equal(
+				'<p>f<span class="text-complex">o</span>o</p>' +
+				'<p>b<span class="text-complex">a</span>r</p>' +
+				'<p>b<span class="text-complex">a</span>z</p>'
+			);
+		} );
+	} );
+} );

+ 257 - 0
packages/ckeditor5-font/tests/fontsize/fontsizeui.js

@@ -0,0 +1,257 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global document */
+
+import FontSizeEditing from '../../src/fontsize/fontsizeediting';
+import FontSizeUI from '../../src/fontsize/fontsizeui';
+
+import fontSizeIcon from '../../theme/icons/font-size.svg';
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import { _clear as clearTranslations, add as addTranslations } from '@ckeditor/ckeditor5-utils/src/translation-service';
+import { normalizeOptions } from '../../src/fontsize/utils';
+
+testUtils.createSinonSandbox();
+
+describe( 'FontSizeUI', () => {
+	let editor, command, element;
+
+	before( () => {
+		addTranslations( 'en', {
+			'Font Size': 'Font Size',
+			'Default': 'Default',
+			'Tiny': 'Tiny',
+			'Small': 'Small',
+			'Big': 'Big',
+			'Huge': 'Huge'
+		} );
+
+		addTranslations( 'pl', {
+			'Font Size': 'Rozmiar czcionki',
+			'Default': 'Domyślny',
+			'Tiny': 'Tyci',
+			'Small': 'Mały',
+			'Big': 'Duży',
+			'Huge': 'Ogromny'
+		} );
+	} );
+
+	after( () => {
+		clearTranslations();
+	} );
+
+	beforeEach( () => {
+		element = document.createElement( 'div' );
+		document.body.appendChild( element );
+
+		return ClassicTestEditor
+			.create( element, {
+				plugins: [ FontSizeEditing, FontSizeUI ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+			} );
+	} );
+
+	afterEach( () => {
+		element.remove();
+
+		return editor.destroy();
+	} );
+
+	describe( 'fontSize Dropdown', () => {
+		let dropdown;
+
+		beforeEach( () => {
+			command = editor.commands.get( 'fontSize' );
+			dropdown = editor.ui.componentFactory.create( 'fontSize' );
+		} );
+
+		it( 'button has the base properties', () => {
+			const button = dropdown.buttonView;
+
+			expect( button ).to.have.property( 'label', 'Font Size' );
+			expect( button ).to.have.property( 'tooltip', true );
+			expect( button ).to.have.property( 'icon', fontSizeIcon );
+		} );
+
+		it( 'should add custom CSS class to dropdown', () => {
+			dropdown.render();
+
+			expect( dropdown.element.classList.contains( 'ck-font-size-dropdown' ) ).to.be.true;
+		} );
+
+		it( 'should focus view after command execution', () => {
+			const focusSpy = testUtils.sinon.spy( editor.editing.view, 'focus' );
+
+			dropdown.commandName = 'fontSize';
+			dropdown.fire( 'execute' );
+
+			sinon.assert.calledOnce( focusSpy );
+		} );
+
+		it( 'should activate current option in dropdown', () => {
+			const listView = dropdown.listView;
+
+			command.value = undefined;
+
+			// The third item is 'default' font size.
+			expect( listView.items.map( item => item.isActive ) ).to.deep.equal( [ false, false, true, false, false ] );
+
+			command.value = 'tiny';
+
+			// The first item is 'tiny' font size.
+			expect( listView.items.map( item => item.isActive ) ).to.deep.equal( [ true, false, false, false, false ] );
+		} );
+
+		describe( 'model to command binding', () => {
+			it( 'isEnabled', () => {
+				command.isEnabled = false;
+
+				expect( dropdown.buttonView.isEnabled ).to.be.false;
+
+				command.isEnabled = true;
+				expect( dropdown.buttonView.isEnabled ).to.be.true;
+			} );
+		} );
+
+		describe( 'config', () => {
+			describe( 'using presets', () => {
+				beforeEach( () => {
+					element = document.createElement( 'div' );
+					document.body.appendChild( element );
+
+					return ClassicTestEditor
+						.create( element, {
+							plugins: [ FontSizeEditing, FontSizeUI ],
+							fontSize: {
+								options: [ 'tiny', 'small', 'default', 'big', 'huge' ]
+							}
+						} )
+						.then( newEditor => {
+							editor = newEditor;
+							dropdown = editor.ui.componentFactory.create( 'fontSize' );
+						} );
+				} );
+
+				it( 'adds css class to listView#items in the panel', () => {
+					const listView = dropdown.listView;
+
+					expect( listView.items.map( item => item.class ) ).to.deep.equal( [
+						'ck-fontsize-option text-tiny',
+						'ck-fontsize-option text-small',
+						'ck-fontsize-option',
+						'ck-fontsize-option text-big',
+						'ck-fontsize-option text-huge'
+					] );
+				} );
+			} );
+
+			describe( 'using numerical values', () => {
+				beforeEach( () => {
+					element = document.createElement( 'div' );
+					document.body.appendChild( element );
+
+					return ClassicTestEditor
+						.create( element, {
+							plugins: [ FontSizeEditing, FontSizeUI ],
+							fontSize: {
+								options: [ 10, 12, 'default', 16, 18 ]
+							}
+						} )
+						.then( newEditor => {
+							editor = newEditor;
+							dropdown = editor.ui.componentFactory.create( 'fontSize' );
+						} );
+				} );
+
+				it( 'adds css class to listView#items in the panel', () => {
+					const listView = dropdown.listView;
+
+					expect( listView.items.map( item => item.class ) ).to.deep.equal( [
+						'ck-fontsize-option',
+						'ck-fontsize-option',
+						'ck-fontsize-option',
+						'ck-fontsize-option',
+						'ck-fontsize-option'
+					] );
+				} );
+
+				it( 'adds font-size style to listView#items in the panel', () => {
+					const listView = dropdown.listView;
+
+					expect( listView.items.map( item => item.style ) ).to.deep.equal( [
+						'font-size:10px',
+						'font-size:12px',
+						undefined,
+						'font-size:16px',
+						'font-size:18px'
+					] );
+				} );
+			} );
+		} );
+
+		describe( 'localization', () => {
+			beforeEach( () => {
+				return localizedEditor( [ 'tiny', 'small', 'default', 'big', 'huge' ] );
+			} );
+
+			it( 'does not alter normalizeOptions() internals', () => {
+				const options = normalizeOptions( [ 'tiny', 'small', 'default', 'big', 'huge' ] );
+				expect( options ).to.deep.equal( [
+					{ title: 'Tiny', model: 'tiny', view: { name: 'span', class: 'text-tiny' } },
+					{ title: 'Small', model: 'small', view: { name: 'span', class: 'text-small' } },
+					{ title: 'Default', model: undefined },
+					{ title: 'Big', model: 'big', view: { name: 'span', class: 'text-big' } },
+					{ title: 'Huge', model: 'huge', view: { name: 'span', class: 'text-huge' } }
+				] );
+			} );
+
+			it( 'works for the #buttonView', () => {
+				const buttonView = dropdown.buttonView;
+
+				expect( buttonView.label ).to.equal( 'Rozmiar czcionki' );
+			} );
+
+			it( 'works for the listView#items in the panel', () => {
+				const listView = dropdown.listView;
+
+				expect( listView.items.map( item => item.label ) ).to.deep.equal( [
+					'Tyci',
+					'Mały',
+					'Domyślny',
+					'Duży',
+					'Ogromny'
+				] );
+			} );
+
+			function localizedEditor( options ) {
+				const editorElement = document.createElement( 'div' );
+				document.body.appendChild( editorElement );
+
+				return ClassicTestEditor
+					.create( editorElement, {
+						plugins: [ FontSizeEditing, FontSizeUI ],
+						toolbar: [ 'fontSize' ],
+						language: 'pl',
+						fontSize: {
+							options
+						}
+					} )
+					.then( newEditor => {
+						editor = newEditor;
+						dropdown = editor.ui.componentFactory.create( 'fontSize' );
+						command = editor.commands.get( 'fontSize' );
+
+						editorElement.remove();
+
+						return editor.destroy();
+					} );
+			}
+		} );
+	} );
+} );

+ 52 - 0
packages/ckeditor5-font/tests/fontsize/utils.js

@@ -0,0 +1,52 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import { normalizeOptions } from '../../src/fontsize/utils';
+
+describe( 'FontSizeEditing Utils', () => {
+	describe( 'normalizeOptions()', () => {
+		it( 'should discard unsupported values', () => {
+			expect( normalizeOptions( [ () => {}, 'default', 'unknown' ] ) ).to.deep.equal( [ { title: 'Default', model: undefined } ] );
+		} );
+
+		it( 'should pass through object definition', () => {
+			expect( normalizeOptions( [ {
+				title: 'My Size',
+				model: 'my-size',
+				view: { name: 'span', style: 'font-size: 12em;' }
+			} ] ) ).to.deep.equal( [
+				{
+					title: 'My Size',
+					model: 'my-size',
+					view: { name: 'span', style: 'font-size: 12em;' }
+				}
+			] );
+		} );
+
+		describe( 'named presets', () => {
+			it( 'should return defined presets', () => {
+				expect( normalizeOptions( [ 'tiny', 'small', 'default', 'big', 'huge' ] ) ).to.deep.equal( [
+					{ title: 'Tiny', model: 'tiny', view: { name: 'span', class: 'text-tiny' } },
+					{ title: 'Small', model: 'small', view: { name: 'span', class: 'text-small' } },
+					{ title: 'Default', model: undefined },
+					{ title: 'Big', model: 'big', view: { name: 'span', class: 'text-big' } },
+					{ title: 'Huge', model: 'huge', view: { name: 'span', class: 'text-huge' } }
+				] );
+			} );
+		} );
+
+		describe( 'numerical presets', () => {
+			it( 'should return generated presets', () => {
+				expect( normalizeOptions( [ '10', 12, 'default', '14.1', 18.3 ] ) ).to.deep.equal( [
+					{ title: '10', model: 10, view: { name: 'span', style: { 'font-size': '10px' } } },
+					{ title: '12', model: 12, view: { name: 'span', style: { 'font-size': '12px' } } },
+					{ title: 'Default', model: undefined },
+					{ title: '14.1', model: 14.1, view: { name: 'span', style: { 'font-size': '14.1px' } } },
+					{ title: '18.3', model: 18.3, view: { name: 'span', style: { 'font-size': '18.3px' } } }
+				] );
+			} );
+		} );
+	} );
+} );

+ 45 - 0
packages/ckeditor5-font/tests/manual/font-family.html

@@ -0,0 +1,45 @@
+<div id="editor">
+	<h2>Font Family feature sample.</h2>
+
+	<h3>Arial, Helvetica, sans-serif: </h3>
+	<p>
+		<span style="font-family: Arial, Helvetica, sans-serif;">Topping cheesecake cotton candy toffee cookie cookie lemon drops cotton candy. Carrot cake dessert jelly beans powder cake cupcake tiramisu pastry gummi bears.</span>
+	</p>
+
+	<h3>Courier New, Courier, monospace: </h3>
+	<p>
+		<span style="font-family: 'Courier New', Courier, monospace;">Jujubes sweet wafer. Pastry cotton candy sweet muffin dessert cookie. Chocolate cake candy canes dragée wafer donut bear claw.</span>
+	</p>
+
+	<h3>Georgia, serif: </h3>
+	<p>
+		<span style="font-family: Georgia, serif;">Chocolate bar candy fruitcake fruitcake. Lollipop gingerbread pudding sweet roll biscuit halvah marzipan croissant soufflé.</span>
+	</p>
+
+	<h3>Lucida Sans Unicode, Lucida Grande, sans-serif: </h3>
+	<p>
+		<span style="font-family: 'Lucida Sans Unicode', 'Lucida Grande', sans-serif;">Chocolate muffin apple pie toffee caramels chupa chups bear claw cotton candy. Lollipop dragée fruitcake fruitcake apple pie chocolate bar candy.</span>
+	</p>
+
+	<h3>Tahoma, Geneva, sans-serif: </h3>
+	<p>
+		<span style="font-family: Tahoma, Geneva, sans-serif;">Brownie biscuit donut gummies pie cheesecake. Dessert ice cream lemon drops candy soufflé cookie lemon drops. Chupa chups powder muffin sugar plum gummi bears fruitcake.</span>
+	</p>
+
+	<h3>Trebuchet MS, Helvetica, sans-serif: </h3>
+	<p>
+		<span style="font-family: 'Trebuchet MS', Helvetica, sans-serif;">Jujubes candy lollipop. Tootsie roll cookie chocolate gummies cupcake sweet fruitcake oat cake danish.</span>
+	</p>
+
+	<h3>Verdana, Geneva, sans-serif: </h3>
+	<p>
+		<span style="font-family: Verdana, Geneva, sans-serif;">Danish brownie powder pie danish. Topping dragée oat cake caramels. Chocolate bar bonbon pastry apple pie icing chocolate danish carrot cake gummies.</span>
+	</p>
+
+	<figure class="image">
+		<img src="sample.jpg" alt="CKEditor logo"/>
+		<figcaption>
+			Sample image with <span style="font-family: Verdana, Geneva, sans-serif;">changed font</span> caption.
+		</figcaption>
+	</figure>
+</div>

+ 24 - 0
packages/ckeditor5-font/tests/manual/font-family.js

@@ -0,0 +1,24 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
+import FontFamily from '../../src/fontfamily';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ ArticlePluginSet, FontFamily ],
+		toolbar: [
+			'headings', '|', 'fontFamily', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo'
+		]
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 12 - 0
packages/ckeditor5-font/tests/manual/font-family.md

@@ -0,0 +1,12 @@
+### Loading
+
+The data should be loaded with paragraphs, each with different font.
+Also the image caption should have "changed font" string with different font. 
+
+### Testing
+
+Try to:
+- Change font size by selecting many paragraphs.
+- Change font size by selecting some text.
+- Change to default font size by selecting many paragraphs.
+- Change to default font size by selecting some text.

+ 11 - 0
packages/ckeditor5-font/tests/manual/font-size-numeric.html

@@ -0,0 +1,11 @@
+<div id="editor">
+	<h2>Font Size feature sample.</h2>
+
+	<p><span style="font-size: 10px;">Some text with font-size set to: 10px.</span></p>
+	<p><span style="font-size: 12px;">Some text with font-size set to: 12px.</span></p>
+	<p><span style="font-size: 14px;">Some text with font-size set to: 14px.</span></p>
+	<p>Some text with the default size</p>
+	<p><span style="font-size: 18px;">Some text with font-size set to: 18px.</span></p>
+	<p><span style="font-size: 20px;">Some text with font-size set to: 20px.</span></p>
+	<p><span style="font-size: 22px;">Some text with font-size set to: 22px.</span></p>
+</div>

+ 25 - 0
packages/ckeditor5-font/tests/manual/font-size-numeric.js

@@ -0,0 +1,25 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
+import FontSize from '../../src/fontsize';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ ArticlePluginSet, FontSize ],
+		toolbar: [
+			'headings', '|', 'fontSize', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo'
+		],
+		fontSize: { options: [ 10, 12, 14, 'default', 18, 20, 22 ] }
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 12 - 0
packages/ckeditor5-font/tests/manual/font-size-numeric.md

@@ -0,0 +1,12 @@
+### Loading
+
+The data should be loaded with:
+- 7 paragraphs with font sizes (10, 12, 14, default, 18, 20, 22),
+
+### Testing
+
+Try to:
+- Change font by selecting many paragraphs.
+- Change font by selecting some text.
+- Change to default font by selecting many paragraphs.
+- Change to default font by selecting some text.

+ 43 - 0
packages/ckeditor5-font/tests/manual/font-size-presets.html

@@ -0,0 +1,43 @@
+<style>
+	.text-tiny {
+		font-size: 0.7em;
+	}
+
+	.text-small {
+		font-size: 0.85em;
+	}
+
+	.text-big {
+		font-size: 1.4em;
+	}
+
+	.text-huge {
+		font-size: 1.8em;
+	}
+</style>
+
+<div id="editor">
+	<h2>Font <span class="text-big">Size</span> feature sample.</h2>
+
+	<p>
+		This is a mixed text with different sizes of text:
+		<span class="text-tiny">tiny</span>,
+		<span class="text-small">small</span>,
+		<span class="text-big">big</span> and
+		<span class="text-huge">huge</span>.
+	</p>
+
+	<ul>
+		<li>It's a list item with <span class="text-tiny">tiny</span> text</li>
+		<li>It's a list item with <span class="text-small">small</span> text</li>
+		<li>It's a list item with <span class="text-big">big</span> text</li>
+		<li>It's a list item with <span class="text-huge">huge</span> text</li>
+	</ul>
+
+	<figure class="image">
+		<img src="sample.jpg" alt="CKEditor logo"/>
+		<figcaption>
+			Sample image with <span class="text-big">c</span><span class="text-tiny">r</span><span class="text-huge">a</span>z<span class="text-small">y</span> caption.
+		</figcaption>
+	</figure>
+</div>

+ 24 - 0
packages/ckeditor5-font/tests/manual/font-size-presets.js

@@ -0,0 +1,24 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
+import FontSize from '../../src/fontsize';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ ArticlePluginSet, FontSize ],
+		toolbar: [
+			'headings', '|', 'fontSize', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo'
+		]
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 15 - 0
packages/ckeditor5-font/tests/manual/font-size-presets.md

@@ -0,0 +1,15 @@
+### Loading
+
+The data should be loaded with:
+- heading with big fragment,
+- paragraph with fragments with all font sizes (tiny, small, big, huge),
+- list with 4 items - each with different font size fragment,
+- image with caption with styled word.
+
+### Testing
+
+Try to:
+- Change font by selecting many paragraphs.
+- Change font by selecting some text.
+- Change to default font by selecting many paragraphs.
+- Change to default font by selecting some text.

BIN
packages/ckeditor5-font/tests/manual/sample.jpg


+ 1 - 0
packages/ckeditor5-font/theme/icons/font-family.svg

@@ -0,0 +1 @@
+<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g fill="#000" fill-rule="evenodd"><path d="M11.03 3h6.149a.75.75 0 1 1 0 1.5h-5.514L11.03 3zm1.27 3h4.879a.75.75 0 1 1 0 1.5h-4.244L12.3 6zm1.27 3h3.609a.75.75 0 1 1 0 1.5h-2.973L13.57 9zM10.816 11.5L8.038 4.785 5.261 11.5h5.555zm.62 1.5H4.641l-1.666 4.028H1.312l5.789-14h1.875l5.789 14h-1.663L11.436 13z"/></g></svg>

+ 1 - 0
packages/ckeditor5-font/theme/icons/font-size.svg

@@ -0,0 +1 @@
+<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g fill="#000" fill-rule="evenodd"><path d="M9.816 11.5L7.038 4.785 4.261 11.5h5.555zm.62 1.5H3.641l-1.666 4.028H.312l5.789-14h1.875l5.789 14h-1.663L10.436 13zM17.986 15.279l.779-.779.707.707-2.265 2.265-2.193-2.265.707-.707.765.765V4.825c0-.042 0-.083.002-.123l-.77.77-.707-.707L17.207 2.5l2.265 2.265-.707.707-.782-.782c.002.043.003.089.003.135v10.454z"/></g></svg>