Browse Source

Merge pull request #25 from ckeditor/t/ckeditor5/1457

t/ckeditor5/1457: Implemented Font Color and Font Background Color features
Marek Lewandowski 6 năm trước cách đây
mục cha
commit
20752ebb10
44 tập tin đã thay đổi với 3173 bổ sung26 xóa
  1. 6 1
      packages/ckeditor5-font/README.md
  2. 3 0
      packages/ckeditor5-font/docs/_snippets/features/custom-font-color-and-background-color-options.html
  3. 209 0
      packages/ckeditor5-font/docs/_snippets/features/custom-font-color-and-background-color-options.js
  4. 8 0
      packages/ckeditor5-font/docs/_snippets/features/font.html
  5. 11 1
      packages/ckeditor5-font/docs/_snippets/features/font.js
  6. 5 2
      packages/ckeditor5-font/docs/api/font.md
  7. 146 1
      packages/ckeditor5-font/docs/features/font.md
  8. 19 1
      packages/ckeditor5-font/lang/contexts.json
  9. 5 1
      packages/ckeditor5-font/src/font.js
  10. 143 0
      packages/ckeditor5-font/src/fontbackgroundcolor.js
  11. 31 0
      packages/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorcommand.js
  12. 121 0
      packages/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorediting.js
  13. 40 0
      packages/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorui.js
  14. 142 0
      packages/ckeditor5-font/src/fontcolor.js
  15. 30 0
      packages/ckeditor5-font/src/fontcolor/fontcolorcommand.js
  16. 121 0
      packages/ckeditor5-font/src/fontcolor/fontcolorediting.js
  17. 40 0
      packages/ckeditor5-font/src/fontcolor/fontcolorui.js
  18. 2 1
      packages/ckeditor5-font/src/fontfamily/fontfamilycommand.js
  19. 1 3
      packages/ckeditor5-font/src/fontfamily/fontfamilyediting.js
  20. 5 4
      packages/ckeditor5-font/src/fontfamily/fontfamilyui.js
  21. 2 1
      packages/ckeditor5-font/src/fontsize/fontsizecommand.js
  22. 1 3
      packages/ckeditor5-font/src/fontsize/fontsizeediting.js
  23. 6 5
      packages/ckeditor5-font/src/fontsize/fontsizeui.js
  24. 283 0
      packages/ckeditor5-font/src/ui/colortableview.js
  25. 131 0
      packages/ckeditor5-font/src/ui/colorui.js
  26. 172 0
      packages/ckeditor5-font/src/utils.js
  27. 4 2
      packages/ckeditor5-font/tests/font.js
  28. 18 0
      packages/ckeditor5-font/tests/fontbackgroundcolor.js
  29. 34 0
      packages/ckeditor5-font/tests/fontbackgroundcolor/fontbackgroundcolorcommand.js
  30. 257 0
      packages/ckeditor5-font/tests/fontbackgroundcolor/fontbackgroundcoloreditng.js
  31. 49 0
      packages/ckeditor5-font/tests/fontbackgroundcolor/fontbackgroundcolorui.js
  32. 18 0
      packages/ckeditor5-font/tests/fontcolor.js
  33. 34 0
      packages/ckeditor5-font/tests/fontcolor/fontcolorcommand.js
  34. 253 0
      packages/ckeditor5-font/tests/fontcolor/fontcolorediting.js
  35. 49 0
      packages/ckeditor5-font/tests/fontcolor/fontcolorui.js
  36. 12 0
      packages/ckeditor5-font/tests/manual/font-color.html
  37. 40 0
      packages/ckeditor5-font/tests/manual/font-color.js
  38. 16 0
      packages/ckeditor5-font/tests/manual/font-color.md
  39. 292 0
      packages/ckeditor5-font/tests/ui/colortableview.js
  40. 265 0
      packages/ckeditor5-font/tests/ui/colorui.js
  41. 137 0
      packages/ckeditor5-font/tests/utils.js
  42. 10 0
      packages/ckeditor5-font/theme/fontcolor.css
  43. 1 0
      packages/ckeditor5-font/theme/icons/font-background.svg
  44. 1 0
      packages/ckeditor5-font/theme/icons/font-color.svg

+ 6 - 1
packages/ckeditor5-font/README.md

@@ -10,7 +10,12 @@ CKEditor 5 font feature
 [![Dependency Status](https://david-dm.org/ckeditor/ckeditor5-font/status.svg)](https://david-dm.org/ckeditor/ckeditor5-font)
 [![Dependency Status](https://david-dm.org/ckeditor/ckeditor5-font/status.svg)](https://david-dm.org/ckeditor/ckeditor5-font)
 [![devDependency Status](https://david-dm.org/ckeditor/ckeditor5-font/dev-status.svg)](https://david-dm.org/ckeditor/ckeditor5-font?type=dev)
 [![devDependency Status](https://david-dm.org/ckeditor/ckeditor5-font/dev-status.svg)](https://david-dm.org/ckeditor/ckeditor5-font?type=dev)
 
 
-This package implements font size and font family support for CKEditor 5.
+This package implements support for the following CKEditor 5 features:
+
+* font size,
+* font family,
+* font color,
+* font background color.
 
 
 ## Demo
 ## Demo
 
 

+ 3 - 0
packages/ckeditor5-font/docs/_snippets/features/custom-font-color-and-background-color-options.html

@@ -0,0 +1,3 @@
+<div id="snippet-custom-font-color-and-background-color-options">
+	<p><span style="color:yellow;background-color:hsl(0,0%,0%);">Text</span> in this <span style="color:white;background-color:hsl(0,100%,50%);">sample</span> <span style="color:aqua;background-color:hsl(240,100%,50%);">has</span> <span style="color:lime;background-color:hsl(300,100%,20%);">m</span><span style="color:lime;background-color:hsl(300,100%,30%);">u</span><span style="color:lime;background-color:hsl(300,100%,40%);">l</span><span style="background-color:hsl(300,100%,50%);">t</span><span style="background-color:hsl(300,100%,60%);">i</span><span style="background-color:hsl(300,100%,70%);">p</span><span style="background-color:hsl(300,100%,80%);">l</span><span style="background-color:hsl(300,100%,90%);">e</span> <span style="color:purple;">font colors</span> and <span style="background-color:hsl(120,100%,90%);">font background</span> <span style="color:red;background-color:hsl(0,0%,62.50%);">colors</span>.</p>
+</div>

+ 209 - 0
packages/ckeditor5-font/docs/_snippets/features/custom-font-color-and-background-color-options.js

@@ -0,0 +1,209 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
+
+/* globals ClassicEditor, console, window, document */
+ClassicEditor
+	.create( document.querySelector( '#snippet-custom-font-color-and-background-color-options' ), {
+		cloudServices: CS_CONFIG,
+		toolbar: {
+			items: [
+				'heading', '|', 'fontColor', 'fontBackgroundColor', 'bulletedList', 'numberedList', 'undo', 'redo'
+			],
+			viewportTopOffset: window.getViewportTopOffsetConfig()
+		},
+		fontBackgroundColor: {
+			colors: [
+				'hsl(0,0%,0%)',
+				'hsl(0,0%,12.5%)',
+				'hsl(0,0%,25%)',
+				'hsl(0,0%,37.5%)',
+				'hsl(0,0%,50%)',
+				'hsl(0,0%,62.50%)',
+				'hsl(0,0%,75%)',
+				'hsl(0,0%,87.5%)',
+				{
+					color: 'hsl(0,0%,100%)',
+					hasBorder: true
+				},
+				'hsl(0,100%,10%)',
+				'hsl(0,100%,20%)',
+				'hsl(0,100%,30%)',
+				'hsl(0,100%,40%)',
+				'hsl(0,100%,50%)',
+				'hsl(0,100%,60%)',
+				'hsl(0,100%,70%)',
+				'hsl(0,100%,80%)',
+				{
+					color: 'hsl(0,100%,90%)',
+					hasBorder: true
+				},
+				'hsl(30,100%,10%)',
+				'hsl(30,100%,20%)',
+				'hsl(30,100%,30%)',
+				'hsl(30,100%,40%)',
+				'hsl(30,100%,50%)',
+				'hsl(30,100%,60%)',
+				'hsl(30,100%,70%)',
+				'hsl(30,100%,80%)',
+				{
+					color: 'hsl(30,100%,90%)',
+					hasBorder: true
+				},
+				'hsl(60,100%,10%)',
+				'hsl(60,100%,20%)',
+				'hsl(60,100%,30%)',
+				'hsl(60,100%,40%)',
+				'hsl(60,100%,50%)',
+				'hsl(60,100%,60%)',
+				'hsl(60,100%,70%)',
+				'hsl(60,100%,80%)',
+				{
+					color: 'hsl(60,100%,90%)',
+					hasBorder: true
+				},
+				'hsl(90,100%,10%)',
+				'hsl(90,100%,20%)',
+				'hsl(90,100%,30%)',
+				'hsl(90,100%,40%)',
+				'hsl(90,100%,50%)',
+				'hsl(90,100%,60%)',
+				'hsl(90,100%,70%)',
+				'hsl(90,100%,80%)',
+				{
+					color: 'hsl(90,100%,90%)',
+					hasBorder: true
+				},
+				'hsl(120,100%,10%)',
+				'hsl(120,100%,20%)',
+				'hsl(120,100%,30%)',
+				'hsl(120,100%,40%)',
+				'hsl(120,100%,50%)',
+				'hsl(120,100%,60%)',
+				'hsl(120,100%,70%)',
+				'hsl(120,100%,80%)',
+				{
+					color: 'hsl(120,100%,90%)',
+					hasBorder: true
+				},
+				'hsl(150,100%,10%)',
+				'hsl(150,100%,20%)',
+				'hsl(150,100%,30%)',
+				'hsl(150,100%,40%)',
+				'hsl(150,100%,50%)',
+				'hsl(150,100%,60%)',
+				'hsl(150,100%,70%)',
+				'hsl(150,100%,80%)',
+				{
+					color: 'hsl(150,100%,90%)',
+					hasBorder: true
+				},
+				'hsl(180,100%,10%)',
+				'hsl(180,100%,20%)',
+				'hsl(180,100%,30%)',
+				'hsl(180,100%,40%)',
+				'hsl(180,100%,50%)',
+				'hsl(180,100%,60%)',
+				'hsl(180,100%,70%)',
+				'hsl(180,100%,80%)',
+				{
+					color: 'hsl(180,100%,90%)',
+					hasBorder: true
+				},
+				'hsl(210,100%,10%)',
+				'hsl(210,100%,20%)',
+				'hsl(210,100%,30%)',
+				'hsl(210,100%,40%)',
+				'hsl(210,100%,50%)',
+				'hsl(210,100%,60%)',
+				'hsl(210,100%,70%)',
+				'hsl(210,100%,80%)',
+				{
+					color: 'hsl(210,100%,90%)',
+					hasBorder: true
+				},
+				'hsl(240,100%,10%)',
+				'hsl(240,100%,20%)',
+				'hsl(240,100%,30%)',
+				'hsl(240,100%,40%)',
+				'hsl(240,100%,50%)',
+				'hsl(240,100%,60%)',
+				'hsl(240,100%,70%)',
+				'hsl(240,100%,80%)',
+				{
+					color: 'hsl(240,100%,90%)',
+					hasBorder: true
+				},
+				'hsl(270,100%,10%)',
+				'hsl(270,100%,20%)',
+				'hsl(270,100%,30%)',
+				'hsl(270,100%,40%)',
+				'hsl(270,100%,50%)',
+				'hsl(270,100%,60%)',
+				'hsl(270,100%,70%)',
+				'hsl(270,100%,80%)',
+				{
+					color: 'hsl(270,100%,90%)',
+					hasBorder: true
+				},
+				'hsl(300,100%,10%)',
+				'hsl(300,100%,20%)',
+				'hsl(300,100%,30%)',
+				'hsl(300,100%,40%)',
+				'hsl(300,100%,50%)',
+				'hsl(300,100%,60%)',
+				'hsl(300,100%,70%)',
+				'hsl(300,100%,80%)',
+				{
+					color: 'hsl(300,100%,90%)',
+					hasBorder: true
+				},
+				'hsl(330,100%,10%)',
+				'hsl(330,100%,20%)',
+				'hsl(330,100%,30%)',
+				'hsl(330,100%,40%)',
+				'hsl(330,100%,50%)',
+				'hsl(330,100%,60%)',
+				'hsl(330,100%,70%)',
+				'hsl(330,100%,80%)',
+				{
+					color: 'hsl(330,100%,90%)',
+					hasBorder: true
+				},
+			],
+			columns: 9
+		},
+		fontColor: {
+			colors: [
+				'black',
+				'gray',
+				'silver',
+				{
+					color: 'white',
+					hasBorder: true
+				},
+				'maroon',
+				'red',
+				'purple',
+				'fuchsia',
+				'green',
+				'lime',
+				'olive',
+				'yellow',
+				'navy',
+				'blue',
+				'teal',
+				'aqua'
+			],
+			columns: 4
+		}
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

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

@@ -15,4 +15,12 @@
 		<span style="font-family: 'Courier New', Courier, monospace;">This text has the "Courier New, Courier, monospace" family set.</span>
 		<span style="font-family: 'Courier New', Courier, monospace;">This text has the "Courier New, Courier, monospace" family set.</span>
 	</p>
 	</p>
 
 
+	<p>
+		<span style="color:hsl(90,75%,60%);background-color:hsl(240,75%,60%)">This text has light green color and blue background.</span>
+	</p>
+
+	<p>
+		<span style="color:hsl(0,0%,100%);background-color:hsl(0,0%,0%)">This text has white color and black background.</span>
+	</p>
+
 </div>
 </div>

+ 11 - 1
packages/ckeditor5-font/docs/_snippets/features/font.js

@@ -12,7 +12,17 @@ ClassicEditor
 		cloudServices: CS_CONFIG,
 		cloudServices: CS_CONFIG,
 		toolbar: {
 		toolbar: {
 			items: [
 			items: [
-				'heading', '|', 'fontSize', 'fontFamily', '|', 'bulletedList', 'numberedList', 'undo', 'redo'
+				'heading',
+				'|',
+				'fontSize',
+				'fontFamily',
+				'fontColor',
+				'fontBackgroundColor',
+				'|',
+				'bulletedList',
+				'numberedList',
+				'undo',
+				'redo'
 			],
 			],
 			viewportTopOffset: window.getViewportTopOffsetConfig()
 			viewportTopOffset: window.getViewportTopOffsetConfig()
 		}
 		}

+ 5 - 2
packages/ckeditor5-font/docs/api/font.md

@@ -14,8 +14,11 @@ Check out the {@link features/font#demo demo in the Font feature} guide.
 
 
 ## Documentation
 ## Documentation
 
 
-See the {@link features/font Font feature} guide
-and {@link module:font/fontfamily~FontFamily} and {@link module:font/fontsize~FontSize} plugins documentation.
+See the {@link features/font Font feature} guide and plugins documentation:
+- {@link module:font/fontfamily~FontFamily}.
+- {@link module:font/fontsize~FontSize}.
+- {@link module:font/fontcolor~FontColor}.
+- {@link module:font/fontbackgroundcolor~FontBackgroundColor}.
 
 
 ## Installation
 ## Installation
 
 

+ 146 - 1
packages/ckeditor5-font/docs/features/font.md

@@ -8,6 +8,8 @@ category: features
 The {@link module:font/font~Font} plugin enables the following features in the editor:
 The {@link module:font/font~Font} plugin enables the following features in the editor:
 * {@link module:font/fontfamily~FontFamily} &ndash; Allows to change the font family by applying inline `<span>` elements with a `font-family` in the `style` attribute.
 * {@link module:font/fontfamily~FontFamily} &ndash; Allows to change the font family by applying inline `<span>` elements with a `font-family` in the `style` attribute.
 * {@link module:font/fontsize~FontSize} &ndash; Allows to control the font size by applying inline `<span>` elements that either have a CSS class or a `font-size` in the `style` attribute.
 * {@link module:font/fontsize~FontSize} &ndash; Allows to control the font size by applying inline `<span>` elements that either have a CSS class or a `font-size` in the `style` attribute.
+* {@link module:font/fontcolor~FontColor} &ndash; Allows to control the font color by applying inline `<span>` elements with a `color` in the `style` attribute.
+* {@link module:font/fontbackgroundcolor~FontBackgroundColor} &ndash; Allows to control the font background color by applying inline `<span>` elements with a `background-color` in the `style` attribute.
 
 
 ## Demo
 ## Demo
 
 
@@ -145,6 +147,115 @@ ClassicEditor
 
 
 {@snippet features/custom-font-size-numeric-options}
 {@snippet features/custom-font-size-numeric-options}
 
 
+## Configuring font color and font background color features
+
+Both font color and font background color features are configurable and share the same configuration format.
+
+<info-box info>
+	Please note that {@link module:font/fontcolor~FontColor font color} and {@link module:font/fontbackgroundcolor~FontBackgroundColor font background color} are separate plugins. They must be enabled and configured individually.
+</info-box>
+
+Check out the editor below with both features customized using the editor configuration:
+
+{@snippet features/custom-font-color-and-background-color-options}
+
+### Specifying the available colors
+
+It is possible to configure which colors are available in the color dropdown. Use the {@link module:font/fontcolor~FontColorConfig#colors `fontColor.colors`} and {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig#colors `fontBackgroundColor.colors`} configuration options to do so.
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		fontColor: {
+			colors: [
+				{
+					color: 'hsl(0, 0%, 0%)',
+					label: 'Black'
+				},
+				{
+					color: 'hsl(0, 0%, 30%)',
+					label: 'Dim grey'
+				},
+				{
+					color: 'hsl(0, 0%, 60%)',
+					label: 'Grey'
+				},
+				{
+					color: 'hsl(0, 0%, 90%)',
+					label: 'Light grey'
+				},
+				{
+					color: 'hsl(0, 0%, 100%)',
+					label: 'White',
+					hasBorder: true
+				},
+
+				// ...
+			]
+		},
+		fontBackgroundColor: {
+			colors: [
+				{
+					color: 'hsl(0, 75%, 60%)',
+					label: 'Red'
+				},
+				{
+					color: 'hsl(30, 75%, 60%)',
+					label: 'Orange'
+				},
+				{
+					color: 'hsl(60, 75%, 60%)',
+					label: 'Yellow'
+				},
+				{
+					color: 'hsl(90, 75%, 60%)',
+					label: 'Light green'
+				},
+				{
+					color: 'hsl(120, 75%, 60%)',
+					label: 'Green'
+				},
+
+				// ...
+			]
+		},
+		toolbar: [
+			'heading', 'bulletedList', 'numberedList', 'fontColor', 'fontBackgroundColor', 'undo', 'redo'
+		]
+	} )
+	.then( ... )
+	.catch( ... );
+```
+
+### Changing the geometry of the color grid
+
+It is also possible to configure in how many columns the colors in the grid are displayed. Use {@link module:font/fontcolor~FontColorConfig#columns `fontColor.columns`} and {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig#columns `fontBackgroundColor.columns`} to do so.
+
+<info-box>
+	The configuration of the columns also affects the number of recently used colors displayed under the color grid. The less columns, the fewer recently used colors will be displayed.
+</info-box>
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		fontColor: {
+			columns: 3,
+
+			// ...
+		},
+		fontBackgroundColor: {
+			columns: 6,
+
+			// ...
+		},
+		toolbar: [
+			'heading', 'bulletedList', 'numberedList', 'fontColor', 'fontBackgroundColor', 'undo', 'redo'
+		]
+	} )
+	.then( ... )
+	.catch( ... );
+```
+
 ## Installation
 ## Installation
 
 
 To add this feature to your editor, install the [`@ckeditor/ckeditor5-font`](https://www.npmjs.com/package/@ckeditor/ckeditor5-font) package:
 To add this feature to your editor, install the [`@ckeditor/ckeditor5-font`](https://www.npmjs.com/package/@ckeditor/ckeditor5-font) package:
@@ -161,7 +272,7 @@ import Font from '@ckeditor/ckeditor5-font/src/font';
 ClassicEditor
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
 	.create( document.querySelector( '#editor' ), {
 		plugins: [ Font, ... ],
 		plugins: [ Font, ... ],
-		toolbar: [ 'fontSize', 'fontFamily', ... ]
+		toolbar: [ 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', ... ]
 	} )
 	} )
 	.then( ... )
 	.then( ... )
 	.catch( ... );
 	.catch( ... );
@@ -256,6 +367,40 @@ The {@link module:font/fontsize~FontSize} plugin registers the following compone
 	editor.execute( 'fontSize' );
 	editor.execute( 'fontSize' );
 	```
 	```
 
 
+The {@link module:font/fontcolor~FontColor} plugin registers the following components:
+
+* The `'fontColor'` dropdown,
+* The {@link module:font/fontcolor/fontcolorcommand~FontColorCommand `'fontColor'`} command.
+
+	You can change the font color of the current selection by executing the command with a desired value:
+
+	```js
+	editor.execute( 'fontColor', { value: 'rgb(30, 188, 97)' } );
+	```
+
+	Passing an empty value will remove the font color from the selection:
+
+	```js
+	editor.execute( 'fontColor' );
+	```
+
+The {@link module:font/fontbackgroundcolor~FontBackgroundColor} plugin registers the following components:
+
+* The `'fontBackgroundColor'` dropdown,
+* The {@link module:font/fontbackgroundcolor/fontbackgroundcolorcommand~FontBackgroundColorCommand `'fontBackgroundColor'`} command.
+
+	You can change the font background color of the current selection by executing the command with a desired value:
+
+	```js
+	editor.execute( 'fontBackgroundColor', { value: 'rgb(30, 188, 97)' } );
+	```
+
+	Passing an empty value will remove the font background color from the selection:
+
+	```js
+	editor.execute( 'fontBackgroundColor' );
+	```
+
 <info-box>
 <info-box>
 	We recommend using the official {@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} for development and debugging. It will give you tons of useful information about the state of the editor such as internal data structures, selection, commands, and many more.
 	We recommend using the official {@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} for development and debugging. It will give you tons of useful information about the state of the editor such as internal data structures, selection, commands, and many more.
 </info-box>
 </info-box>

+ 19 - 1
packages/ckeditor5-font/lang/contexts.json

@@ -5,5 +5,23 @@
 	"Big": "Dropdown option label for the 'big' font size preset.",
 	"Big": "Dropdown option label for the 'big' font size preset.",
 	"Huge": "Dropdown option label for the 'huge' font size preset.",
 	"Huge": "Dropdown option label for the 'huge' font size preset.",
 	"Font Family": "Tooltip for the font family dropdown.",
 	"Font Family": "Tooltip for the font family dropdown.",
-	"Default": "Dropdown option label for the default font family."
+	"Default": "Dropdown option label for the default font family.",
+	"Font color": "Label of a button that allows selecting a font color.",
+	"Font Background Color": "Label of a button that allows selecting a font background color.",
+	"Remove color": "Label or a button that removes the color from the text (font color and background).",
+	"Black": "Label of a button that applies a black text color (font color and background)",
+	"Dim grey": "Label of a button that applies a dim grey text color (font color and background)",
+	"Grey": "Label of a button that applies a grey text color (font color and background)",
+	"Light grey": "Label of a button that applies a light grey text color (font color and background)",
+	"White": "Label of a button that applies a white text color (font color and background)",
+	"Red": "Label of a button that applies a red text color (font color and background)",
+	"Orange": "Label of a button that applies a orange text color (font color and background)",
+	"Yellow": "Label of a button that applies a yellow text color (font color and background)",
+	"Light green": "Label of a button that applies a light green text color (font color and background)",
+	"Green": "Label of a button that applies a green text color (font color and background)",
+	"Aquamarine": "Label of a button that applies a aquamarine text color (font color and background)",
+	"Turquoise": "Label of a button that applies a turquoise text color (font color and background)",
+	"Light blue": "Label of a button that applies a light blue text color (font color and background)",
+	"Blue": "Label of a button that applies a blue text color (font color and background)",
+	"Purple": "Label of a button that applies a purple text color (font color and background)"
 }
 }

+ 5 - 1
packages/ckeditor5-font/src/font.js

@@ -11,12 +11,16 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
 
 import FontFamily from './fontfamily';
 import FontFamily from './fontfamily';
 import FontSize from './fontsize';
 import FontSize from './fontsize';
+import FontColor from './fontcolor';
+import FontBackgroundColor from './fontbackgroundcolor';
 
 
 /**
 /**
  * A plugin that enables a set of text styling features:
  * A plugin that enables a set of text styling features:
  *
  *
  * * {@link module:font/fontsize~FontSize},
  * * {@link module:font/fontsize~FontSize},
  * * {@link module:font/fontfamily~FontFamily}.
  * * {@link module:font/fontfamily~FontFamily}.
+ * * {@link module:font/fontcolor~FontColor},
+ * * {@link module:font/fontbackgroundcolor~FontBackgroundColor}.
  *
  *
  * For a detailed overview, check the {@glink features/font Font feature} documentation
  * For a detailed overview, check the {@glink features/font Font feature} documentation
  * and the {@glink api/font package page}.
  * and the {@glink api/font package page}.
@@ -28,7 +32,7 @@ export default class Font extends Plugin {
 	 * @inheritDoc
 	 * @inheritDoc
 	 */
 	 */
 	static get requires() {
 	static get requires() {
-		return [ FontFamily, FontSize ];
+		return [ FontFamily, FontSize, FontColor, FontBackgroundColor ];
 	}
 	}
 
 
 	/**
 	/**

+ 143 - 0
packages/ckeditor5-font/src/fontbackgroundcolor.js

@@ -0,0 +1,143 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontbackgroundcolor
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import FontBackgroundColorEditing from './fontbackgroundcolor/fontbackgroundcolorediting';
+import FontBackgroundColorUI from './fontbackgroundcolor/fontbackgroundcolorui';
+
+/**
+ * The font background color plugin.
+ *
+ * For a detailed overview, check the {@glink features/font font feature} documentation
+ * and the {@glink api/font package page}.
+ *
+ * This is a "glue" plugin which loads
+ * the {@link module:font/fontbackgroundcolor/fontbackgroundcolorediting~FontBackgroundColorEditing} and
+ * {@link module:font/fontbackgroundcolor/fontbackgroundcolorui~FontBackgroundColorUI} features in the editor.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontBackgroundColor extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ FontBackgroundColorEditing, FontBackgroundColorUI ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'FontBackgroundColor';
+	}
+}
+
+/**
+ * The configuration of the font background color feature.
+ * This option is used by the {@link module:font/fontbackgroundcolor/fontbackgroundcolorediting~FontBackgroundColorEditing} feature.
+ *
+ *		ClassicEditor
+ *			.create( {
+ *				fontBackgroundColor: ... // Font background color feature configuration.
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
+ *
+ * See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
+ *
+ * @interface module:font/fontbackgroundcolor~FontBackgroundColorConfig
+ */
+
+/**
+ * Available font background colors defined as an array of strings or objects.
+ *
+ * The default value registers the following colors:
+ *
+ *		const fontBackgroundColorConfig = {
+ *			colors: [
+ *				{
+ *					color: 'hsl(0, 0%, 0%)',
+ *					label: 'Black'
+ *				},
+ *				{
+ *					color: 'hsl(0, 0%, 30%)',
+ *					label: 'Dim grey'
+ *				},
+ *				{
+ *					color: 'hsl(0, 0%, 60%)',
+ *					label: 'Grey'
+ *				},
+ *				{
+ *					color: 'hsl(0, 0%, 90%)',
+ *					label: 'Light grey'
+ *				},
+ *				{
+ *					color: 'hsl(0, 0%, 100%)',
+ *					label: 'White',
+ *					hasBorder: true
+ *				},
+ *				{
+ *					color: 'hsl(0, 75%, 60%)',
+ *					label: 'Red'
+ *				},
+ *				{
+ *					color: 'hsl(30, 75%, 60%)',
+ *					label: 'Orange'
+ *				},
+ *				{
+ *					color: 'hsl(60, 75%, 60%)',
+ *					label: 'Yellow'
+ *				},
+ *				{
+ *					color: 'hsl(90, 75%, 60%)',
+ *					label: 'Light green'
+ *				},
+ *				{
+ *					color: 'hsl(120, 75%, 60%)',
+ *					label: 'Green'
+ *				},
+ *				{
+ *					color: 'hsl(150, 75%, 60%)',
+ *					label: 'Aquamarine'
+ *				},
+ *				{
+ *					color: 'hsl(180, 75%, 60%)',
+ *					label: 'Turquoise'
+ *				},
+ *				{
+ *					color: 'hsl(210, 75%, 60%)',
+ *					label: 'Light blue'
+ *				},
+ *				{
+ *					color: 'hsl(240, 75%, 60%)',
+ *					label: 'Blue'
+ *				},
+ *				{
+ *					color: 'hsl(270, 75%, 60%)',
+ *					label: 'Purple'
+ *				}
+ *			]
+ *		};
+ *
+ * *Note*: The colors are displayed in the `'fontBackgroundColor'` dropdown.
+ *
+ * @member {Array.<String|Object>} module:font/fontbackgroundcolor~FontBackgroundColorConfig#colors
+ */
+
+/**
+ * Represents the amount of columns in the dropdown. It also represents the number of background colors
+ * recently used by the user displayed in the dropdown. The default value is:
+ *
+ *		const fontBackgroundColorConfig = {
+ *			columns: 5
+ *		}
+ *
+ * @member {Number} module:font/fontbackgroundcolor~FontBackgroundColorConfig#columns
+ */

+ 31 - 0
packages/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorcommand.js

@@ -0,0 +1,31 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontbackgroundcolor/fontbackgroundcolorcommand
+ */
+
+import FontCommand from '../fontcommand';
+import { FONT_BACKGROUND_COLOR } from '../utils';
+
+/**
+ * The font background color command. It's used by the
+ * {@link module:font/fontbackgroundcolor/fontbackgroundcolorediting~FontBackgroundColorEditing}
+ * to apply the font background color.
+ *
+ *		editor.execute( 'fontBackgroundColor', { value: 'rgb(250, 20, 20)' } );
+ *
+ * **Note**: Executing the command with the `null` value removes the attribute from the model.
+ *
+ * @extends module:font/fontcommand~FontCommand
+ */
+export default class FontBackgroundColorCommand extends FontCommand {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor, FONT_BACKGROUND_COLOR );
+	}
+}

+ 121 - 0
packages/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorediting.js

@@ -0,0 +1,121 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontbackgroundcolor/fontbackgroundcolorediting
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import FontBackgroundColorCommand from './fontbackgroundcolorcommand';
+import { FONT_BACKGROUND_COLOR, renderDowncastElement, renderUpcastAttribute } from '../utils';
+
+/**
+ * The font background color editing feature.
+ *
+ * It introduces the {@link module:font/fontbackgroundcolor/fontbackgroundcolorcommand~FontBackgroundColorCommand command} and
+ * the `fontBackgroundColor` attribute in the {@link module:engine/model/model~Model model} which renders
+ * in the {@link module:engine/view/view view} as a `<span>` element (`<span style="background-color: ...">`),
+ * depending on the {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig configuration}.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontBackgroundColorEditing extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		editor.config.define( FONT_BACKGROUND_COLOR, {
+			colors: [
+				{
+					color: 'hsl(0, 0%, 0%)',
+					label: 'Black'
+				},
+				{
+					color: 'hsl(0, 0%, 30%)',
+					label: 'Dim grey'
+				},
+				{
+					color: 'hsl(0, 0%, 60%)',
+					label: 'Grey'
+				},
+				{
+					color: 'hsl(0, 0%, 90%)',
+					label: 'Light grey'
+				},
+				{
+					color: 'hsl(0, 0%, 100%)',
+					label: 'White',
+					hasBorder: true
+				},
+				{
+					color: 'hsl(0, 75%, 60%)',
+					label: 'Red'
+				},
+				{
+					color: 'hsl(30, 75%, 60%)',
+					label: 'Orange'
+				},
+				{
+					color: 'hsl(60, 75%, 60%)',
+					label: 'Yellow'
+				},
+				{
+					color: 'hsl(90, 75%, 60%)',
+					label: 'Light green'
+				},
+				{
+					color: 'hsl(120, 75%, 60%)',
+					label: 'Green'
+				},
+				{
+					color: 'hsl(150, 75%, 60%)',
+					label: 'Aquamarine'
+				},
+				{
+					color: 'hsl(180, 75%, 60%)',
+					label: 'Turquoise'
+				},
+				{
+					color: 'hsl(210, 75%, 60%)',
+					label: 'Light blue'
+				},
+				{
+					color: 'hsl(240, 75%, 60%)',
+					label: 'Blue'
+				},
+				{
+					color: 'hsl(270, 75%, 60%)',
+					label: 'Purple'
+				}
+			],
+			columns: 5
+		} );
+
+		editor.conversion.for( 'upcast' ).elementToAttribute( {
+			view: {
+				name: 'span',
+				styles: {
+					'background-color': /[\s\S]+/
+				}
+			},
+			model: {
+				key: FONT_BACKGROUND_COLOR,
+				value: renderUpcastAttribute( 'background-color' )
+			}
+		} );
+
+		editor.conversion.for( 'downcast' ).attributeToElement( {
+			model: FONT_BACKGROUND_COLOR,
+			view: renderDowncastElement( 'background-color' )
+		} );
+
+		editor.commands.add( FONT_BACKGROUND_COLOR, new FontBackgroundColorCommand( editor ) );
+
+		// Allow fontBackgroundColor attribute on text nodes.
+		editor.model.schema.extend( '$text', { allowAttributes: FONT_BACKGROUND_COLOR } );
+	}
+}

+ 40 - 0
packages/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorui.js

@@ -0,0 +1,40 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontbackgroundcolor/fontbackgroundcolorui
+ */
+
+import ColorUI from '../ui/colorui';
+import { FONT_BACKGROUND_COLOR } from '../utils';
+import fontBackgroundColorIcon from '../../theme/icons/font-background.svg';
+
+/**
+ * The font background color UI plugin. It introduces the `'fontBackgroundColor'` dropdown.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontBackgroundColorUI extends ColorUI {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		const t = editor.locale.t;
+
+		super( editor, {
+			commandName: FONT_BACKGROUND_COLOR,
+			componentName: FONT_BACKGROUND_COLOR,
+			icon: fontBackgroundColorIcon,
+			dropdownLabel: t( 'Font Background Color' )
+		} );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'FontBackgroundColorUI';
+	}
+}

+ 142 - 0
packages/ckeditor5-font/src/fontcolor.js

@@ -0,0 +1,142 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontcolor
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import FontColorEditing from './fontcolor/fontcolorediting';
+import FontColorUI from './fontcolor/fontcolorui';
+
+/**
+ * The font color plugin.
+ *
+ * For a detailed overview, check the {@glink features/font font feature} documentation
+ * and the {@glink api/font package page}.
+ *
+ * This is a "glue" plugin which loads the {@link module:font/fontcolor/fontcolorediting~FontColorEditing} and
+ * {@link module:font/fontcolor/fontcolorui~FontColorUI} features in the editor.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontColor extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ FontColorEditing, FontColorUI ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'FontColor';
+	}
+}
+
+/**
+ * The configuration of the font color feature.
+ * This option is used by the {@link module:font/fontcolor/fontcolorediting~FontColorEditing} feature.
+ *
+ *		ClassicEditor
+ *			.create( {
+ *				fontColor: ... // Font color feature configuration.
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
+ *
+ * See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
+ *
+ * @interface module:font/fontcolor~FontColorConfig
+ */
+
+/**
+ * Available font colors defined as an array of strings or objects.
+ *
+ * The default value registers the following colors:
+ *
+ *		const fontColorConfig = {
+ *			colors: [
+ *				{
+ *					color: 'hsl(0, 0%, 0%)',
+ *					label: 'Black'
+ *				},
+ *				{
+ *					color: 'hsl(0, 0%, 30%)',
+ *					label: 'Dim grey'
+ *				},
+ *				{
+ *					color: 'hsl(0, 0%, 60%)',
+ *					label: 'Grey'
+ *				},
+ *				{
+ *					color: 'hsl(0, 0%, 90%)',
+ *					label: 'Light grey'
+ *				},
+ *				{
+ *					color: 'hsl(0, 0%, 100%)',
+ *					label: 'White',
+ *					hasBorder: true
+ *				},
+ *				{
+ *					color: 'hsl(0, 75%, 60%)',
+ *					label: 'Red'
+ *				},
+ *				{
+ *					color: 'hsl(30, 75%, 60%)',
+ *					label: 'Orange'
+ *				},
+ *				{
+ *					color: 'hsl(60, 75%, 60%)',
+ *					label: 'Yellow'
+ *				},
+ *				{
+ *					color: 'hsl(90, 75%, 60%)',
+ *					label: 'Light green'
+ *				},
+ *				{
+ *					color: 'hsl(120, 75%, 60%)',
+ *					label: 'Green'
+ *				},
+ *				{
+ *					color: 'hsl(150, 75%, 60%)',
+ *					label: 'Aquamarine'
+ *				},
+ *				{
+ *					color: 'hsl(180, 75%, 60%)',
+ *					label: 'Turquoise'
+ *				},
+ *				{
+ *					color: 'hsl(210, 75%, 60%)',
+ *					label: 'Light blue'
+ *				},
+ *				{
+ *					color: 'hsl(240, 75%, 60%)',
+ *					label: 'Blue'
+ *				},
+ *				{
+ *					color: 'hsl(270, 75%, 60%)',
+ *					label: 'Purple'
+ *				}
+ *			]
+ *		};
+ *
+ * *Note*: The colors are displayed in the `'fontColor'` dropdown.
+ *
+ * @member {Array.<String|Object>} module:font/fontcolor~FontColorConfig#colors
+ */
+
+/**
+ * Represents the amount of columns in the dropdown. It also represents the number of font colors
+ * recently used by the user displayed in the dropdown. The default value is:
+ *
+ *		const fontColorConfig = {
+ *			columns: 5
+ *		}
+ *
+ * @member {Number} module:font/fontcolor~FontColorConfig#columns
+ */

+ 30 - 0
packages/ckeditor5-font/src/fontcolor/fontcolorcommand.js

@@ -0,0 +1,30 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontcolor/fontcolorcommand
+ */
+
+import FontCommand from '../fontcommand';
+import { FONT_COLOR } from '../utils';
+
+/**
+ * The font color command. It's used by the {@link module:font/fontcolor/fontcolorediting~FontColorEditing}
+ * to apply the font color.
+ *
+ *		editor.execute( 'fontColor', { value: 'rgb(250, 20, 20)' } );
+ *
+ * **Note**: Executing the command with the `null` value removes the attribute from the model.
+ *
+ * @extends module:font/fontcommand~FontCommand
+ */
+export default class FontColorCommand extends FontCommand {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor, FONT_COLOR );
+	}
+}

+ 121 - 0
packages/ckeditor5-font/src/fontcolor/fontcolorediting.js

@@ -0,0 +1,121 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontcolor/fontcolorediting
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import FontColorCommand from './fontcolorcommand';
+import { FONT_COLOR, renderDowncastElement, renderUpcastAttribute } from '../utils';
+
+/**
+ * The font color editing feature.
+ *
+ * It introduces the {@link module:font/fontcolor/fontcolorcommand~FontColorCommand command} and
+ * the `fontColor` attribute in the {@link module:engine/model/model~Model model} which renders
+ * in the {@link module:engine/view/view view} as a `<span>` element (`<span style="color: ...">`),
+ * depending on the {@link module:font/fontcolor~FontColorConfig configuration}.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontColorEditing extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		editor.config.define( FONT_COLOR, {
+			colors: [
+				{
+					color: 'hsl(0, 0%, 0%)',
+					label: 'Black'
+				},
+				{
+					color: 'hsl(0, 0%, 30%)',
+					label: 'Dim grey'
+				},
+				{
+					color: 'hsl(0, 0%, 60%)',
+					label: 'Grey'
+				},
+				{
+					color: 'hsl(0, 0%, 90%)',
+					label: 'Light grey'
+				},
+				{
+					color: 'hsl(0, 0%, 100%)',
+					label: 'White',
+					hasBorder: true
+				},
+				{
+					color: 'hsl(0, 75%, 60%)',
+					label: 'Red'
+				},
+				{
+					color: 'hsl(30, 75%, 60%)',
+					label: 'Orange'
+				},
+				{
+					color: 'hsl(60, 75%, 60%)',
+					label: 'Yellow'
+				},
+				{
+					color: 'hsl(90, 75%, 60%)',
+					label: 'Light green'
+				},
+				{
+					color: 'hsl(120, 75%, 60%)',
+					label: 'Green'
+				},
+				{
+					color: 'hsl(150, 75%, 60%)',
+					label: 'Aquamarine'
+				},
+				{
+					color: 'hsl(180, 75%, 60%)',
+					label: 'Turquoise'
+				},
+				{
+					color: 'hsl(210, 75%, 60%)',
+					label: 'Light blue'
+				},
+				{
+					color: 'hsl(240, 75%, 60%)',
+					label: 'Blue'
+				},
+				{
+					color: 'hsl(270, 75%, 60%)',
+					label: 'Purple'
+				}
+			],
+			columns: 5
+		} );
+
+		editor.conversion.for( 'upcast' ).elementToAttribute( {
+			view: {
+				name: 'span',
+				styles: {
+					'color': /[\s\S]+/
+				}
+			},
+			model: {
+				key: FONT_COLOR,
+				value: renderUpcastAttribute( 'color' )
+			}
+		} );
+
+		editor.conversion.for( 'downcast' ).attributeToElement( {
+			model: FONT_COLOR,
+			view: renderDowncastElement( 'color' )
+		} );
+
+		editor.commands.add( FONT_COLOR, new FontColorCommand( editor ) );
+
+		// Allow fontColor attribute on text nodes.
+		editor.model.schema.extend( '$text', { allowAttributes: FONT_COLOR } );
+	}
+}

+ 40 - 0
packages/ckeditor5-font/src/fontcolor/fontcolorui.js

@@ -0,0 +1,40 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/fontcolor/fontcolorui
+ */
+
+import ColorUI from '../ui/colorui';
+import { FONT_COLOR } from '../utils';
+import fontColorIcon from '../../theme/icons/font-color.svg';
+
+/**
+ * The font color UI plugin. It introduces the `'fontColor'` dropdown.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class FontColorUI extends ColorUI {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		const t = editor.locale.t;
+
+		super( editor, {
+			commandName: FONT_COLOR,
+			componentName: FONT_COLOR,
+			icon: fontColorIcon,
+			dropdownLabel: t( 'Font Color' )
+		} );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'FontColorUI';
+	}
+}

+ 2 - 1
packages/ckeditor5-font/src/fontfamily/fontfamilycommand.js

@@ -8,6 +8,7 @@
  */
  */
 
 
 import FontCommand from '../fontcommand';
 import FontCommand from '../fontcommand';
+import { FONT_FAMILY } from '../utils';
 
 
 /**
 /**
  * The font family command. It is used by {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing}
  * The font family command. It is used by {@link module:font/fontfamily/fontfamilyediting~FontFamilyEditing}
@@ -24,6 +25,6 @@ export default class FontFamilyCommand extends FontCommand {
 	 * @inheritDoc
 	 * @inheritDoc
 	 */
 	 */
 	constructor( editor ) {
 	constructor( editor ) {
-		super( editor, 'fontFamily' );
+		super( editor, FONT_FAMILY );
 	}
 	}
 }
 }

+ 1 - 3
packages/ckeditor5-font/src/fontfamily/fontfamilyediting.js

@@ -11,9 +11,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
 
 import FontFamilyCommand from './fontfamilycommand';
 import FontFamilyCommand from './fontfamilycommand';
 import { normalizeOptions } from './utils';
 import { normalizeOptions } from './utils';
-import { buildDefinition } from '../utils';
-
-const FONT_FAMILY = 'fontFamily';
+import { buildDefinition, FONT_FAMILY } from '../utils';
 
 
 /**
 /**
  * The font family editing feature.
  * The font family editing feature.

+ 5 - 4
packages/ckeditor5-font/src/fontfamily/fontfamilyui.js

@@ -13,6 +13,7 @@ import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 
 
 import { createDropdown, addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
 import { createDropdown, addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
 import { normalizeOptions } from './utils';
 import { normalizeOptions } from './utils';
+import { FONT_FAMILY } from '../utils';
 import fontFamilyIcon from '../../theme/icons/font-family.svg';
 import fontFamilyIcon from '../../theme/icons/font-family.svg';
 
 
 /**
 /**
@@ -30,10 +31,10 @@ export default class FontFamilyUI extends Plugin {
 
 
 		const options = this._getLocalizedOptions();
 		const options = this._getLocalizedOptions();
 
 
-		const command = editor.commands.get( 'fontFamily' );
+		const command = editor.commands.get( FONT_FAMILY );
 
 
 		// Register UI component.
 		// Register UI component.
-		editor.ui.componentFactory.add( 'fontFamily', locale => {
+		editor.ui.componentFactory.add( FONT_FAMILY, locale => {
 			const dropdownView = createDropdown( locale );
 			const dropdownView = createDropdown( locale );
 			addListToDropdown( dropdownView, _prepareListOptions( options, command ) );
 			addListToDropdown( dropdownView, _prepareListOptions( options, command ) );
 
 
@@ -76,7 +77,7 @@ export default class FontFamilyUI extends Plugin {
 		const editor = this.editor;
 		const editor = this.editor;
 		const t = editor.t;
 		const t = editor.t;
 
 
-		const options = normalizeOptions( editor.config.get( 'fontFamily.options' ) );
+		const options = normalizeOptions( editor.config.get( FONT_FAMILY ).options );
 
 
 		return options.map( option => {
 		return options.map( option => {
 			// The only title to localize is "Default" others are font names.
 			// The only title to localize is "Default" others are font names.
@@ -101,7 +102,7 @@ function _prepareListOptions( options, command ) {
 		const def = {
 		const def = {
 			type: 'button',
 			type: 'button',
 			model: new Model( {
 			model: new Model( {
-				commandName: 'fontFamily',
+				commandName: FONT_FAMILY,
 				commandParam: option.model,
 				commandParam: option.model,
 				label: option.title,
 				label: option.title,
 				withText: true
 				withText: true

+ 2 - 1
packages/ckeditor5-font/src/fontsize/fontsizecommand.js

@@ -8,6 +8,7 @@
  */
  */
 
 
 import FontCommand from '../fontcommand';
 import FontCommand from '../fontcommand';
+import { FONT_SIZE } from '../utils';
 
 
 /**
 /**
  * The font size command. It is used by {@link module:font/fontsize/fontsizeediting~FontSizeEditing}
  * The font size command. It is used by {@link module:font/fontsize/fontsizeediting~FontSizeEditing}
@@ -24,6 +25,6 @@ export default class FontSizeCommand extends FontCommand {
 	 * @inheritDoc
 	 * @inheritDoc
 	 */
 	 */
 	constructor( editor ) {
 	constructor( editor ) {
-		super( editor, 'fontSize' );
+		super( editor, FONT_SIZE );
 	}
 	}
 }
 }

+ 1 - 3
packages/ckeditor5-font/src/fontsize/fontsizeediting.js

@@ -11,9 +11,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
 
 import FontSizeCommand from './fontsizecommand';
 import FontSizeCommand from './fontsizecommand';
 import { normalizeOptions } from './utils';
 import { normalizeOptions } from './utils';
-import { buildDefinition } from '../utils';
-
-const FONT_SIZE = 'fontSize';
+import { buildDefinition, FONT_SIZE } from '../utils';
 
 
 /**
 /**
  * The font size editing feature.
  * The font size editing feature.

+ 6 - 5
packages/ckeditor5-font/src/fontsize/fontsizeui.js

@@ -12,7 +12,8 @@ import Model from '@ckeditor/ckeditor5-ui/src/model';
 import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 
 
 import { createDropdown, addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
 import { createDropdown, addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
-import { normalizeOptions } from '../fontsize/utils';
+import { normalizeOptions } from './utils';
+import { FONT_SIZE } from '../utils';
 
 
 import fontSizeIcon from '../../theme/icons/font-size.svg';
 import fontSizeIcon from '../../theme/icons/font-size.svg';
 import '../../theme/fontsize.css';
 import '../../theme/fontsize.css';
@@ -32,10 +33,10 @@ export default class FontSizeUI extends Plugin {
 
 
 		const options = this._getLocalizedOptions();
 		const options = this._getLocalizedOptions();
 
 
-		const command = editor.commands.get( 'fontSize' );
+		const command = editor.commands.get( FONT_SIZE );
 
 
 		// Register UI component.
 		// Register UI component.
-		editor.ui.componentFactory.add( 'fontSize', locale => {
+		editor.ui.componentFactory.add( FONT_SIZE, locale => {
 			const dropdownView = createDropdown( locale );
 			const dropdownView = createDropdown( locale );
 			addListToDropdown( dropdownView, _prepareListOptions( options, command ) );
 			addListToDropdown( dropdownView, _prepareListOptions( options, command ) );
 
 
@@ -89,7 +90,7 @@ export default class FontSizeUI extends Plugin {
 			Huge: t( 'Huge' )
 			Huge: t( 'Huge' )
 		};
 		};
 
 
-		const options = normalizeOptions( editor.config.get( 'fontSize.options' ) );
+		const options = normalizeOptions( editor.config.get( FONT_SIZE ).options );
 
 
 		return options.map( option => {
 		return options.map( option => {
 			const title = localizedTitles[ option.title ];
 			const title = localizedTitles[ option.title ];
@@ -115,7 +116,7 @@ function _prepareListOptions( options, command ) {
 		const def = {
 		const def = {
 			type: 'button',
 			type: 'button',
 			model: new Model( {
 			model: new Model( {
-				commandName: 'fontSize',
+				commandName: FONT_SIZE,
 				commandParam: option.model,
 				commandParam: option.model,
 				label: option.title,
 				label: option.title,
 				class: 'ck-fontsize-option',
 				class: 'ck-fontsize-option',

+ 283 - 0
packages/ckeditor5-font/src/ui/colortableview.js

@@ -0,0 +1,283 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/ui/colortableview
+ */
+
+import View from '@ckeditor/ckeditor5-ui/src/view';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+import ColorTileView from '@ckeditor/ckeditor5-ui/src/colorgrid/colortileview';
+import ColorGridView from '@ckeditor/ckeditor5-ui/src/colorgrid/colorgridview';
+import Collection from '@ckeditor/ckeditor5-utils/src/collection';
+import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
+import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler';
+import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
+import removeButtonIcon from '@ckeditor/ckeditor5-core/theme/icons/eraser.svg';
+import '../../theme/fontcolor.css';
+
+/**
+ * Class which represents a view with the following sub–components:
+ *
+ * * a remove color button,
+ * * a {@link module:ui/colorgrid/colorgrid~ColorGridView},
+ * * a grid of recently used colors.
+ *
+ * @extends module:ui/view~View
+ */
+export default class ColorTableView extends View {
+	/**
+	 * Creates a view to be inserted as child of {@link module:ui/dropdown/dropdownview~DropdownView}.
+	 *
+	 * @param {module:utils/locale~Locale} [locale] The localization services instance.
+	 * @param {Object} config Configuration object
+	 * @param {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>} config.colors Array with definitions of colors to
+	 * be displayed in the table.
+	 * @param {Number} config.columns Number of columns in the color grid.
+	 * Also determines how many recent color will be displayed.
+	 * @param {String} config.removeButtonLabel A label of a button responsible for removing the color.
+	 */
+	constructor( locale, { colors, columns, removeButtonLabel } ) {
+		super( locale );
+
+		/**
+		 * Collection of the children of the table.
+		 *
+		 * @readonly
+		 * @member {module:ui/viewcollection~ViewCollection}
+		 */
+		this.items = this.createCollection();
+
+		/**
+		 * An array with objects representing colors to be displayed in the grid.
+		 *
+		 * @type {Arrray.<module:ui/colorgrid/colorgrid~ColorDefinition>}
+		 */
+		this.colorDefinitions = colors;
+
+		/**
+		 * Tracks information about DOM focus in the list.
+		 *
+		 * @readonly
+		 * @member {module:utils/focustracker~FocusTracker}
+		 */
+		this.focusTracker = new FocusTracker();
+
+		/**
+		 * Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
+		 *
+		 * @readonly
+		 * @member {module:utils/keystrokehandler~KeystrokeHandler}
+		 */
+		this.keystrokes = new KeystrokeHandler();
+
+		/**
+		 * Keeps value of the command associated with the table for current selection.
+		 *
+		 * @type {String}
+		 */
+		this.set( 'selectedColor' );
+
+		/**
+		 * A label of the button responsible for removing color attributes.
+		 *
+		 * @type {String}
+		 */
+		this.removeButtonLabel = removeButtonLabel;
+
+		/**
+		 * The number of columns in color grid. Also determines the number of recent color to be displayed.
+		 *
+		 * @type {Number}
+		 */
+		this.columns = columns;
+
+		/**
+		 * A collection storing definitions of recently used colors.
+		 *
+		 * @readonly
+		 * @member {module:utils/collection~Collection}
+		 */
+		this.recentlyUsedColors = new Collection();
+
+		/**
+		 * Helps cycling over focusable {@link #items} in the list.
+		 *
+		 * @readonly
+		 * @protected
+		 * @member {module:ui/focuscycler~FocusCycler}
+		 */
+		this._focusCycler = new FocusCycler( {
+			focusables: this.items,
+			focusTracker: this.focusTracker,
+			keystrokeHandler: this.keystrokes,
+			actions: {
+				// Navigate list items backwards using the arrowup key.
+				focusPrevious: 'arrowup',
+
+				// Navigate list items forwards using the arrowdown key.
+				focusNext: 'arrowdown',
+			}
+		} );
+
+		this.initRecentCollection();
+		this.setTemplate( {
+			tag: 'div',
+			attributes: {
+				class: [
+					'ck',
+					'ck-color-table'
+				]
+			},
+			children: this.items
+		} );
+
+		this.items.add( this.removeColorButton() );
+		this.items.add( this.createStaticColorTable() );
+		this.items.add( this.recentlyUsed() );
+	}
+
+	/**
+	 * Adds the remove color button as child for current view.
+	 *
+	 * @private
+	 */
+	removeColorButton() {
+		const buttonView = new ButtonView();
+
+		buttonView.set( {
+			withText: true,
+			icon: removeButtonIcon,
+			tooltip: true,
+			label: this.removeButtonLabel
+		} );
+
+		buttonView.class = 'ck-color-table__remove-color';
+		buttonView.on( 'execute', () => {
+			this.fire( 'execute', { value: null } );
+		} );
+
+		return buttonView;
+	}
+
+	/**
+	 * Creates a static color table grid based on editor config.
+	 *
+	 * @private
+	 */
+	createStaticColorTable() {
+		const colorGrid = new ColorGridView( this.locale, {
+			colorDefinitions: this.colorDefinitions,
+			columns: this.columns
+		} );
+
+		colorGrid.delegate( 'execute' ).to( this );
+		colorGrid.bind( 'selectedColor' ).to( this );
+
+		return colorGrid;
+	}
+
+	/**
+	 * Adds recently used colors section view and binds it to {@link #recentlyUsedColors}.
+	 *
+	 * @private
+	 */
+	recentlyUsed() {
+		const recentViews = new ColorGridView( this.locale, { columns: this.columns } );
+
+		recentViews.items.bindTo( this.recentlyUsedColors ).using(
+			colorObj => {
+				const colorTile = new ColorTileView();
+
+				colorTile.set( {
+					color: colorObj.color,
+					hasBorder: colorObj.hasBorder
+				} );
+
+				if ( colorObj.label ) {
+					colorTile.set( {
+						label: colorObj.label,
+						tooltip: true
+					} );
+				}
+
+				if ( colorObj.isEnabled === false ) {
+					colorTile.set( 'isEnabled', false );
+				}
+
+				colorTile.on( 'execute', () => {
+					this.fire( 'execute', {
+						value: colorObj.color,
+						hasBorder: colorObj.hasBorder,
+						label: colorObj.label
+					} );
+				} );
+
+				return colorTile;
+			}
+		);
+
+		this.recentlyUsedColors.on( 'add', ( evt, item ) => {
+			const duplicates = this.recentlyUsedColors.filter( element => element.color === item.color, this );
+
+			if ( duplicates.length === 2 ) {
+				this.recentlyUsedColors.remove( duplicates[ 1 ] );
+			}
+
+			if ( this.recentlyUsedColors.length > this.columns ) {
+				this.recentlyUsedColors.remove( this.recentlyUsedColors.length - 1 );
+			}
+		} );
+
+		recentViews.delegate( 'execute' ).to( this );
+
+		return recentViews;
+	}
+
+	/**
+	 * Populates {@link #recentlyUsedColors} with empty non-clickable buttons, which represent placeholders
+	 * for colors.
+	 *
+	 * @private
+	 */
+	initRecentCollection() {
+		for ( let i = 0; i < this.columns; i++ ) {
+			this.recentlyUsedColors.add( {
+				color: 'hsla(0, 0%, 0%, 0)',
+				isEnabled: false,
+				hasBorder: true
+			} );
+		}
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	render() {
+		super.render();
+
+		// Items added before rendering should be known to the #focusTracker.
+		for ( const item of this.items ) {
+			this.focusTracker.add( item.element );
+		}
+
+		// Start listening for the keystrokes coming from #element.
+		this.keystrokes.listenTo( this.element );
+	}
+
+	/**
+	 * Focuses the first focusable in {@link #items}.
+	 */
+	focus() {
+		this._focusCycler.focusFirst();
+	}
+
+	/**
+	 * Focuses the last focusable in {@link #items}.
+	 */
+	focusLast() {
+		this._focusCycler.focusLast();
+	}
+}

+ 131 - 0
packages/ckeditor5-font/src/ui/colorui.js

@@ -0,0 +1,131 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module font/ui/colorui
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
+import {
+	addColorTableToDropdown,
+	normalizeColorOptions,
+	getLocalizedColorOptions
+} from '../utils';
+
+/**
+ * The color UI plugin which isolates the common logic responsible for displaying dropdowns with color grids.
+ *
+ * It is used to create the `'fontBackgroundColor'` and the `'fontColor'` dropdowns, each hosting
+ * a {@link module:font/ui/colortableview~ColorTableView}.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class ColorUI extends Plugin {
+	/**
+	 * Creates a plugin which brings dropdown with a pre–configured {@link module:font/ui/colortableview~ColorTableView}
+	 *
+	 * @param {module:core/editor/editor~Editor} editor
+	 * @param {Object} config Configuration object
+	 * @param {String} config.commandName Name of command which will be executed when a color tile is clicked.
+	 * @param {String} config.componentName Name of the dropdown in the {@link module:ui/componentfactory~ComponentFactory}
+	 * and the configuration scope name in `editor.config`.
+	 * @param {String} config.icon SVG icon used by the dropdown.
+	 * @param {String} config.dropdownLabel Label used by the dropdown.
+	 */
+	constructor( editor, { commandName, icon, componentName, dropdownLabel } ) {
+		super( editor );
+
+		/**
+		 * Name of the command which will be executed when a color tile is clicked.
+		 * @type {String}
+		 */
+		this.commandName = commandName;
+
+		/**
+		 * Name of this component in the {@link module:ui/componentfactory~ComponentFactory}.
+		 * Also the configuration scope name in `editor.config`.
+		 * @type {String}
+		 */
+		this.componentName = componentName;
+
+		/**
+		 * SVG icon used by the dropdown.
+		 * @type {String}
+		 */
+		this.icon = icon;
+
+		/**
+		 * Label used by the dropdown.
+		 * @type {String}
+		 */
+		this.dropdownLabel = dropdownLabel;
+
+		/**
+		 * Number of columns in color grid. Determines the number of recent colors to be displayed.
+		 * @type {Number}
+		 */
+		this.columns = editor.config.get( `${ this.componentName }.columns` );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+		const t = editor.t;
+		const command = editor.commands.get( this.commandName );
+		const colorsConfig = normalizeColorOptions( editor.config.get( this.componentName ).colors );
+		const localizedColors = getLocalizedColorOptions( editor, colorsConfig );
+
+		// Register UI component.
+		editor.ui.componentFactory.add( this.componentName, locale => {
+			const dropdownView = createDropdown( locale );
+			const colorTableView = addColorTableToDropdown( {
+				dropdownView,
+				colors: localizedColors.map( option => ( {
+					label: option.label,
+					color: option.model,
+					options: {
+						hasBorder: option.hasBorder
+					}
+				} ) ),
+				columns: this.columns,
+				removeButtonLabel: t( 'Remove color' )
+			} );
+
+			colorTableView.bind( 'selectedColor' ).to( command, 'value' );
+
+			dropdownView.buttonView.set( {
+				label: this.dropdownLabel,
+				icon: this.icon,
+				tooltip: true
+			} );
+
+			dropdownView.extendTemplate( {
+				attributes: {
+					class: 'ck-color-ui-dropdown'
+				}
+			} );
+
+			dropdownView.bind( 'isEnabled' ).to( command );
+
+			dropdownView.on( 'execute', ( evt, data ) => {
+				if ( data.value !== null ) {
+					colorTableView.recentlyUsedColors.add( {
+						color: data.value,
+						hasBorder: data.hasBorder,
+						label: data.label
+					}, 0 );
+				}
+
+				editor.execute( this.commandName, data );
+				editor.editing.view.focus();
+			} );
+
+			return dropdownView;
+		} );
+	}
+}

+ 172 - 0
packages/ckeditor5-font/src/utils.js

@@ -7,6 +7,28 @@
  * @module font/utils
  * @module font/utils
  */
  */
 
 
+import ColorTableView from './ui/colortableview';
+
+/**
+ * Name of the font size plugin.
+ */
+export const FONT_SIZE = 'fontSize';
+
+/**
+ * Name of the font family plugin.
+ */
+export const FONT_FAMILY = 'fontFamily';
+
+/**
+ * Name of the font color plugin.
+ */
+export const FONT_COLOR = 'fontColor';
+
+/**
+ * Name of font font background color plugin.
+ */
+export const FONT_BACKGROUND_COLOR = 'fontBackgroundColor';
+
 /**
 /**
  * Builds a proper {@link module:engine/conversion/conversion~ConverterDefinition converter definition} out of input data.
  * Builds a proper {@link module:engine/conversion/conversion~ConverterDefinition converter definition} out of input data.
  *
  *
@@ -35,3 +57,153 @@ export function buildDefinition( modelAttributeKey, options ) {
 
 
 	return definition;
 	return definition;
 }
 }
+
+/**
+ * A {@link module:font/fontcolor~FontColor font color} and
+ * {@link module:font/fontbackgroundcolor~FontBackgroundColor font background color} helper
+ * responsible for upcasting data to model.
+ *
+ * **Note**: `styleAttr` should be either `'color'` or `'background-color'`.
+ *
+ * @param {String} styleAttr
+ * @return {String}
+ */
+export function renderUpcastAttribute( styleAttr ) {
+	return viewElement => normalizeColorCode( viewElement.getStyle( styleAttr ) );
+}
+
+/**
+ * A {@link module:font/fontcolor~FontColor font color} and
+ * {@link module:font/fontbackgroundcolor~FontBackgroundColor font background color} helper
+ * responsible for downcasting a color attribute to a span element.
+ *
+ * **Note**: `styleAttr` should be either `'color'` or `'background-color'`.
+ *
+ * @param {String} styleAttr
+ */
+export function renderDowncastElement( styleAttr ) {
+	return ( modelAttributeValue, viewWriter ) => viewWriter.createAttributeElement( 'span', {
+		style: `${ styleAttr }:${ modelAttributeValue }`
+	} );
+}
+
+/**
+ * Creates a unified color definition object from color configuration options.
+ * The object contains both the information necessary to render the UI and initialize a conversion.
+ *
+ * @param {module:ui/colorgrid/colorgrid~ColorDefinition} options
+ * @returns {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>}
+ */
+export function normalizeColorOptions( options ) {
+	return options
+		.map( normalizeSingleColorDefinition )
+		.filter( option => !!option );
+}
+
+/**
+ * Helper which add {@link module:font/ui/colortableview~ColorTableView} to a dropdown with proper initial values.
+ *
+ * @param {Object} config Configuration object
+ * @param {module:ui/dropdown/dropdownview~DropdownView} config.dropdownView Dropdown view to which
+ * a {@link module:font/ui/colortableview~ColorTableView} will be added.
+ * @param {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>} config.colors An array with definitions
+ * representing colors to be displayed in the color table.
+ * @param {String} config.removeButtonLabel A label of a button responsible for removing the color.
+ * @returns {module:font/ui/colortableview~ColorTableView} The new color table view.
+ */
+export function addColorTableToDropdown( { dropdownView, colors, columns, removeButtonLabel } ) {
+	const locale = dropdownView.locale;
+	const colorTableView = new ColorTableView( locale, { colors, columns, removeButtonLabel } );
+
+	dropdownView.colorTableView = colorTableView;
+	dropdownView.panelView.children.add( colorTableView );
+
+	colorTableView.delegate( 'execute' ).to( dropdownView, 'execute' );
+
+	return colorTableView;
+}
+
+/**
+ * Returns color configuration options as defined in the `editor.config.(fontColor|fontBackgroundColor).colors`
+ * but processed to account for editor localization, i.e. to display {@link module:font/fontcolor~FontColorConfig}
+ * or {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig} in the correct language.
+ *
+ * Note: The reason behind this method is that there is no way to use {@link module:utils/locale~Locale#t}
+ * when the user configuration is defined because the editor does not exist yet.
+ *
+ * @param {module:core/editor/editor~Editor} editor An editor instance.
+ * @param {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>} options
+ * @returns {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>}.
+ */
+export function getLocalizedColorOptions( editor, options ) {
+	const t = editor.t;
+	const localizedColorNames = {
+		Black: t( 'Black' ),
+		'Dim grey': t( 'Dim grey' ),
+		Grey: t( 'Grey' ),
+		'Light grey': t( 'Light grey' ),
+		White: t( 'White' ),
+		Red: t( 'Red' ),
+		Orange: t( 'Orange' ),
+		Yellow: t( 'Yellow' ),
+		'Light green': t( 'Light green' ),
+		Green: t( 'Green' ),
+		Aquamarine: t( 'Aquamarine' ),
+		Turquoise: t( 'Turquoise' ),
+		'Light blue': t( 'Light blue' ),
+		Blue: t( 'Blue' ),
+		Purple: t( 'Purple' )
+	};
+
+	return options.map( colorOption => {
+		const label = localizedColorNames[ colorOption.label ];
+
+		if ( label && label != colorOption.label ) {
+			colorOption.label = label;
+		}
+
+		return colorOption;
+	} );
+}
+
+// Fixes color value string
+//
+// @param {String} value
+// @returns {String}
+function normalizeColorCode( value ) {
+	return value.replace( /\s/g, '' );
+}
+
+// Creates normalized color definition from user defined configuration.
+//
+// @param {String|module:ui/colorgrid/colorgrid~ColorDefinition}
+// @returns {module:ui/colorgrid/colorgrid~ColorDefinition}
+function normalizeSingleColorDefinition( color ) {
+	if ( typeof color === 'string' ) {
+		return {
+			model: color.replace( / /g, '' ),
+			label: color,
+			hasBorder: false,
+			view: {
+				name: 'span',
+				styles: {
+					color
+				},
+				priority: 5
+			}
+		};
+	} else {
+		return {
+			model: color.color.replace( / /g, '' ),
+			label: color.label || color.color,
+			hasBorder: color.hasBorder === undefined ? false : color.hasBorder,
+			view: {
+				name: 'span',
+				styles: {
+					color: `${ color.color }`
+				},
+				priority: 5
+			}
+		};
+	}
+}

+ 4 - 2
packages/ckeditor5-font/tests/font.js

@@ -6,10 +6,12 @@
 import Font from './../src/font';
 import Font from './../src/font';
 import FontFamily from './../src/fontfamily';
 import FontFamily from './../src/fontfamily';
 import FontSize from './../src/fontsize';
 import FontSize from './../src/fontsize';
+import FontColor from './../src/fontcolor';
+import FontBackgroundColor from './../src/fontbackgroundcolor';
 
 
 describe( 'Font', () => {
 describe( 'Font', () => {
-	it( 'requires FontSize', () => {
-		expect( Font.requires ).to.deep.equal( [ FontFamily, FontSize ] );
+	it( 'requires FontFamily, FontSize, FontColor, FontBackgroundColor', () => {
+		expect( Font.requires ).to.deep.equal( [ FontFamily, FontSize, FontColor, FontBackgroundColor ] );
 	} );
 	} );
 
 
 	it( 'defines plugin name', () => {
 	it( 'defines plugin name', () => {

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

@@ -0,0 +1,18 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import FontBackgroundColor from './../src/fontbackgroundcolor';
+import FontBackgroundColorEditing from './../src/fontbackgroundcolor/fontbackgroundcolorediting';
+import FontBackgroundColorUI from '../src/fontbackgroundcolor/fontbackgroundcolorui';
+
+describe( 'FontBackgroundColor', () => {
+	it( 'requires FontBackgroundColorEditing and FontBackgroundColorUI', () => {
+		expect( FontBackgroundColor.requires ).to.deep.equal( [ FontBackgroundColorEditing, FontBackgroundColorUI ] );
+	} );
+
+	it( 'defines plugin name', () => {
+		expect( FontBackgroundColor.pluginName ).to.equal( 'FontBackgroundColor' );
+	} );
+} );

+ 34 - 0
packages/ckeditor5-font/tests/fontbackgroundcolor/fontbackgroundcolorcommand.js

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

+ 257 - 0
packages/ckeditor5-font/tests/fontbackgroundcolor/fontbackgroundcoloreditng.js

@@ -0,0 +1,257 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import FontBackgroundColorEditing from './../../src/fontbackgroundcolor/fontbackgroundcolorediting';
+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( 'FontBackgroundColorEditing', () => {
+	let editor, doc;
+
+	beforeEach( () => VirtualTestEditor
+		.create( {
+			plugins: [ FontBackgroundColorEditing, Paragraph ]
+		} )
+		.then( newEditor => {
+			editor = newEditor;
+
+			doc = editor.document;
+		} )
+	);
+
+	afterEach( () => {
+		editor.destroy();
+	} );
+
+	it( 'should set proper schema rules', () => {
+		expect( editor.model.schema.checkAttribute( [ '$block', '$text' ], 'fontBackgroundColor' ) ).to.be.true;
+		expect( editor.model.schema.checkAttribute( [ '$clipboardHolder', '$text' ], 'fontBackgroundColor' ) ).to.be.true;
+
+		expect( editor.model.schema.checkAttribute( [ '$block' ], 'fontBackgroundColor' ) ).to.be.false;
+	} );
+
+	describe( 'config', () => {
+		describe( 'default value', () => {
+			it( 'should be set', () => {
+				expect( editor.config.get( 'fontBackgroundColor.colors' ) ).to.deep.equal( [
+					{
+						color: 'hsl(0, 0%, 0%)',
+						label: 'Black'
+					},
+					{
+						color: 'hsl(0, 0%, 30%)',
+						label: 'Dim grey'
+					},
+					{
+						color: 'hsl(0, 0%, 60%)',
+						label: 'Grey'
+					},
+					{
+						color: 'hsl(0, 0%, 90%)',
+						label: 'Light grey'
+					},
+					{
+						color: 'hsl(0, 0%, 100%)',
+						label: 'White',
+						hasBorder: true
+					},
+					{
+						color: 'hsl(0, 75%, 60%)',
+						label: 'Red'
+					},
+					{
+						color: 'hsl(30, 75%, 60%)',
+						label: 'Orange'
+					},
+					{
+						color: 'hsl(60, 75%, 60%)',
+						label: 'Yellow'
+					},
+					{
+						color: 'hsl(90, 75%, 60%)',
+						label: 'Light green'
+					},
+					{
+						color: 'hsl(120, 75%, 60%)',
+						label: 'Green'
+					},
+					{
+						color: 'hsl(150, 75%, 60%)',
+						label: 'Aquamarine'
+					},
+					{
+						color: 'hsl(180, 75%, 60%)',
+						label: 'Turquoise'
+					},
+					{
+						color: 'hsl(210, 75%, 60%)',
+						label: 'Light blue'
+					},
+					{
+						color: 'hsl(240, 75%, 60%)',
+						label: 'Blue'
+					},
+					{
+						color: 'hsl(270, 75%, 60%)',
+						label: 'Purple'
+					}
+				] );
+				expect( editor.config.get( 'fontBackgroundColor.columns' ) ).to.equal( 5 );
+			} );
+		} );
+	} );
+
+	describe( 'editing pipeline conversion', () => {
+		beforeEach( () => VirtualTestEditor
+			.create( {
+				plugins: [ FontBackgroundColorEditing, Paragraph ],
+				fontBackgroundColor: {
+					colors: [
+						{
+							label: 'Color1',
+							color: '#000'
+						},
+						{
+							label: 'Color2',
+							color: '#123456'
+						},
+						{
+							label: 'Color3',
+							color: 'rgb( 0, 10, 20 )'
+						},
+						'hsl( 200,100%,50%)',
+						{
+							label: 'Color5 - Light Green',
+							color: 'lightgreen'
+						}
+					]
+				}
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+				doc = editor.model;
+			} )
+		);
+
+		describe( 'convert different color version', () => {
+			const tests = [
+				'#000',
+				'green',
+				'rgb( 0, 10, 20 )',
+				'rgba( 20, 30, 50, 0.4)',
+				'hsl( 10, 20%, 30%)',
+				'hsla( 300, 50%, 100%, .3)',
+				'rgb( 20%, 30%, 40% )',
+				'#345678'
+			];
+
+			tests.forEach( test => {
+				it( `should convert fontBackgroundColor attribute: "${ test }" to proper style value.`, () => {
+					setModelData( doc, `<paragraph>fo<$text fontBackgroundColor="${ test }">o b</$text>ar</paragraph>` );
+
+					expect( editor.getData() ).to.equal( `<p>fo<span style="background-color:${ test };">o b</span>ar</p>` );
+				} );
+			} );
+		} );
+	} );
+
+	describe( 'data pipeline conversions', () => {
+		beforeEach( () => {
+			return VirtualTestEditor
+				.create( {
+					plugins: [ FontBackgroundColorEditing, Paragraph ],
+					fontBackgroundColor: {
+						colors: [
+							{
+								label: 'Color1',
+								color: '#000'
+							},
+							{
+								label: 'Color2',
+								color: '#123456'
+							},
+							{
+								label: 'Color3',
+								color: 'rgb( 0, 10, 20 )'
+							},
+							'hsl( 200,100%,50%)',
+							{
+								label: 'Color5 - Light Green',
+								color: 'lightgreen'
+							}
+						]
+					}
+				} )
+				.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-size: 18px;background-color: rgb(10, 20, 30);">o</span>o</p>';
+
+			editor.setData( data );
+
+			expect( getModelData( doc ) ).to.equal( '<paragraph>[]f<$text fontBackgroundColor="rgb(10,20,30)">o</$text>o</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>f<span style="background-color:rgb(10,20,30);">o</span>o</p>' );
+		} );
+
+		describe( 'should convert from different color versions', () => {
+			const tests = [
+				'#000',
+				'green',
+				'rgb( 0, 10, 20 )',
+				'rgba( 20, 30, 50, 0.4)',
+				'hsl( 10, 20%, 30%)',
+				'hsla( 300, 50%, 100%, .3)',
+				'rgb( 20%, 30%, 40% )',
+				'#345678'
+			];
+
+			tests.forEach( test => {
+				it( `should convert fontBackgroundColor attribute: "${ test }" to proper style value.`, () => {
+					const data = `<p>f<span style="background-color: ${ test }">o</span>o</p>`;
+					editor.setData( data );
+
+					expect( getModelData( doc ) )
+						.to.equal( `<paragraph>[]f<$text fontBackgroundColor="${ test.replace( / /g, '' ) }">o</$text>o</paragraph>` );
+
+					expect( editor.getData() )
+						.to.equal( `<p>f<span style="background-color:${ test.replace( / /g, '' ) };">o</span>o</p>` );
+				} );
+			} );
+		} );
+
+		it( 'should convert from complex definition', () => {
+			editor.setData(
+				'<p>f<span style="background-color: lightgreen;">o</span>o</p>' +
+				'<p>f<span style="background-color: hsl( 200, 100%, 50% );">o</span>o</p>' +
+				'<p>b<span style="background-color: rgba(1,2,3,.4);">a</span>r</p>' +
+				'<p>b<span style="background-color:#fff;">a</span>z</p>'
+			);
+
+			expect( getModelData( doc ) ).to.equal(
+				'<paragraph>[]f<$text fontBackgroundColor="lightgreen">o</$text>o</paragraph>' +
+				'<paragraph>f<$text fontBackgroundColor="hsl(200,100%,50%)">o</$text>o</paragraph>' +
+				'<paragraph>b<$text fontBackgroundColor="rgba(1,2,3,.4)">a</$text>r</paragraph>' +
+				'<paragraph>b<$text fontBackgroundColor="#fff">a</$text>z</paragraph>'
+			);
+
+			expect( editor.getData() ).to.equal(
+				'<p>f<span style="background-color:lightgreen;">o</span>o</p>' +
+				'<p>f<span style="background-color:hsl(200,100%,50%);">o</span>o</p>' +
+				'<p>b<span style="background-color:rgba(1,2,3,.4);">a</span>r</p>' +
+				'<p>b<span style="background-color:#fff;">a</span>z</p>'
+			);
+		} );
+	} );
+} );

+ 49 - 0
packages/ckeditor5-font/tests/fontbackgroundcolor/fontbackgroundcolorui.js

@@ -0,0 +1,49 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global document */
+
+import FontBackgroundColorEditing from './../../src/fontbackgroundcolor/fontbackgroundcolorediting';
+import FontBackgroundColorUI from './../../src/fontbackgroundcolor/fontbackgroundcolorui';
+import ColorUI from './../../src/ui/colorui';
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import fontBackgroundColorIcon from '../../theme/icons/font-background.svg';
+
+describe( 'FontBckgroundColorUI', () => {
+	let element, editor;
+
+	beforeEach( () => {
+		element = document.createElement( 'div' );
+		document.body.appendChild( element );
+
+		return ClassicTestEditor
+			.create( element, {
+				plugins: [ FontBackgroundColorEditing, FontBackgroundColorUI ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+			} );
+	} );
+
+	afterEach( () => {
+		element.remove();
+
+		return editor.destroy();
+	} );
+
+	it( 'is ColorUI', () => {
+		expect( FontBackgroundColorUI.prototype ).to.be.instanceOf( ColorUI );
+	} );
+
+	it( 'has properly set initial values', () => {
+		const fontBackgroundColorUIPlugin = editor.plugins.get( 'FontBackgroundColorUI' );
+
+		expect( fontBackgroundColorUIPlugin.commandName ).to.equal( 'fontBackgroundColor' );
+		expect( fontBackgroundColorUIPlugin.componentName ).to.equal( 'fontBackgroundColor' );
+		expect( fontBackgroundColorUIPlugin.icon ).to.equal( fontBackgroundColorIcon );
+		expect( fontBackgroundColorUIPlugin.dropdownLabel ).to.equal( 'Font Background Color' );
+		expect( fontBackgroundColorUIPlugin.columns ).to.equal( 5 );
+	} );
+} );

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

@@ -0,0 +1,18 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import FontColor from './../src/fontcolor';
+import FontColorEditing from './../src/fontcolor/fontcolorediting';
+import FontColorUI from '../src/fontcolor/fontcolorui';
+
+describe( 'FontColor', () => {
+	it( 'requires FontColorEditing and FontColorUI', () => {
+		expect( FontColor.requires ).to.deep.equal( [ FontColorEditing, FontColorUI ] );
+	} );
+
+	it( 'defines plugin name', () => {
+		expect( FontColor.pluginName ).to.equal( 'FontColor' );
+	} );
+} );

+ 34 - 0
packages/ckeditor5-font/tests/fontcolor/fontcolorcommand.js

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

+ 253 - 0
packages/ckeditor5-font/tests/fontcolor/fontcolorediting.js

@@ -0,0 +1,253 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import FontColorEditing from './../../src/fontcolor/fontcolorediting';
+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( 'FontColorEditing', () => {
+	let editor, doc;
+
+	beforeEach( () => VirtualTestEditor
+		.create( {
+			plugins: [ FontColorEditing, Paragraph ]
+		} )
+		.then( newEditor => {
+			editor = newEditor;
+
+			doc = editor.document;
+		} )
+	);
+
+	afterEach( () => {
+		editor.destroy();
+	} );
+
+	it( 'should set proper schema rules', () => {
+		expect( editor.model.schema.checkAttribute( [ '$block', '$text' ], 'fontColor' ) ).to.be.true;
+		expect( editor.model.schema.checkAttribute( [ '$clipboardHolder', '$text' ], 'fontColor' ) ).to.be.true;
+		expect( editor.model.schema.checkAttribute( [ '$block' ], 'fontColor' ) ).to.be.false;
+	} );
+
+	describe( 'config', () => {
+		describe( 'default value', () => {
+			it( 'should be set', () => {
+				expect( editor.config.get( 'fontColor.colors' ) ).to.deep.equal( [
+					{
+						color: 'hsl(0, 0%, 0%)',
+						label: 'Black'
+					},
+					{
+						color: 'hsl(0, 0%, 30%)',
+						label: 'Dim grey'
+					},
+					{
+						color: 'hsl(0, 0%, 60%)',
+						label: 'Grey'
+					},
+					{
+						color: 'hsl(0, 0%, 90%)',
+						label: 'Light grey'
+					},
+					{
+						color: 'hsl(0, 0%, 100%)',
+						label: 'White',
+						hasBorder: true
+					},
+					{
+						color: 'hsl(0, 75%, 60%)',
+						label: 'Red'
+					},
+					{
+						color: 'hsl(30, 75%, 60%)',
+						label: 'Orange'
+					},
+					{
+						color: 'hsl(60, 75%, 60%)',
+						label: 'Yellow'
+					},
+					{
+						color: 'hsl(90, 75%, 60%)',
+						label: 'Light green'
+					},
+					{
+						color: 'hsl(120, 75%, 60%)',
+						label: 'Green'
+					},
+					{
+						color: 'hsl(150, 75%, 60%)',
+						label: 'Aquamarine'
+					},
+					{
+						color: 'hsl(180, 75%, 60%)',
+						label: 'Turquoise'
+					},
+					{
+						color: 'hsl(210, 75%, 60%)',
+						label: 'Light blue'
+					},
+					{
+						color: 'hsl(240, 75%, 60%)',
+						label: 'Blue'
+					},
+					{
+						color: 'hsl(270, 75%, 60%)',
+						label: 'Purple'
+					}
+				] );
+				expect( editor.config.get( 'fontColor.columns' ) ).to.equal( 5 );
+			} );
+		} );
+	} );
+
+	describe( 'editing pipeline conversion', () => {
+		beforeEach( () => VirtualTestEditor
+			.create( {
+				plugins: [ FontColorEditing, Paragraph ],
+				fontColor: {
+					colors: [
+						{
+							label: 'Color1',
+							color: '#000'
+						},
+						{
+							label: 'Color2',
+							color: '#123456'
+						},
+						{
+							label: 'Color3',
+							color: 'rgb( 0, 10, 20 )'
+						},
+						'hsl( 200,100%,50%)',
+						{
+							label: 'Color5 - Light Green',
+							color: 'lightgreen'
+						}
+					]
+				}
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+				doc = editor.model;
+			} )
+		);
+
+		describe( 'convert different color version', () => {
+			const tests = [
+				'#000',
+				'green',
+				'rgb( 0, 10, 20 )',
+				'rgba( 20, 30, 50, 0.4)',
+				'hsl( 10, 20%, 30%)',
+				'hsla( 300, 50%, 100%, .3)',
+				'rgb( 20%, 30%, 40% )',
+				'#345678'
+			];
+			tests.forEach( test => {
+				it( `should convert fontColor attribute: "${ test }" to proper style value.`, () => {
+					setModelData( doc, `<paragraph>fo<$text fontColor="${ test }">o b</$text>ar</paragraph>` );
+
+					expect( editor.getData() ).to.equal( `<p>fo<span style="color:${ test };">o b</span>ar</p>` );
+				} );
+			} );
+		} );
+	} );
+
+	describe( 'data pipeline conversions', () => {
+		beforeEach( () => {
+			return VirtualTestEditor
+				.create( {
+					plugins: [ FontColorEditing, Paragraph ],
+					fontColor: {
+						colors: [
+							{
+								label: 'Color1',
+								color: '#000'
+							},
+							{
+								label: 'Color2',
+								color: '#123456'
+							},
+							{
+								label: 'Color3',
+								color: 'rgb( 0, 10, 20 )'
+							},
+							'hsl( 200,100%,50%)',
+							{
+								label: 'Color5 - Light Green',
+								color: 'lightgreen'
+							}
+						]
+					}
+				} )
+				.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-size: 18px;color: rgb(10, 20, 30);">o</span>o</p>';
+
+			editor.setData( data );
+
+			expect( getModelData( doc ) ).to.equal( '<paragraph>[]f<$text fontColor="rgb(10,20,30)">o</$text>o</paragraph>' );
+			expect( editor.getData() ).to.equal( '<p>f<span style="color:rgb(10,20,30);">o</span>o</p>' );
+		} );
+
+		describe( 'should convert from different color versions', () => {
+			const tests = [
+				'#000',
+				'green',
+				'rgb( 0, 10, 20 )',
+				'rgba( 20, 30, 50, 0.4)',
+				'hsl( 10, 20%, 30%)',
+				'hsla( 300, 50%, 100%, .3)',
+				'rgb( 20%, 30%, 40% )',
+				'#345678'
+			];
+
+			tests.forEach( test => {
+				it( `should convert fontColor attribute: "${ test }" to proper style value.`, () => {
+					const data = `<p>f<span style="color: ${ test }">o</span>o</p>`;
+					editor.setData( data );
+
+					expect( getModelData( doc ) )
+						.to.equal( `<paragraph>[]f<$text fontColor="${ test.replace( / /g, '' ) }">o</$text>o</paragraph>` );
+
+					expect( editor.getData() ).to.equal( `<p>f<span style="color:${ test.replace( / /g, '' ) };">o</span>o</p>` );
+				} );
+			} );
+		} );
+
+		it( 'should convert from complex definition', () => {
+			editor.setData(
+				'<p>f<span style="color: lightgreen;">o</span>o</p>' +
+				'<p>f<span style="color: hsl( 200, 100%, 50% );">o</span>o</p>' +
+				'<p>b<span style="color: rgba(1,2,3,.4);">a</span>r</p>' +
+				'<p>b<span style="color:#fff;">a</span>z</p>'
+			);
+
+			expect( getModelData( doc ) ).to.equal(
+				'<paragraph>[]f<$text fontColor="lightgreen">o</$text>o</paragraph>' +
+				'<paragraph>f<$text fontColor="hsl(200,100%,50%)">o</$text>o</paragraph>' +
+				'<paragraph>b<$text fontColor="rgba(1,2,3,.4)">a</$text>r</paragraph>' +
+				'<paragraph>b<$text fontColor="#fff">a</$text>z</paragraph>'
+			);
+
+			expect( editor.getData() ).to.equal(
+				'<p>f<span style="color:lightgreen;">o</span>o</p>' +
+				'<p>f<span style="color:hsl(200,100%,50%);">o</span>o</p>' +
+				'<p>b<span style="color:rgba(1,2,3,.4);">a</span>r</p>' +
+				'<p>b<span style="color:#fff;">a</span>z</p>'
+			);
+		} );
+	} );
+} );

+ 49 - 0
packages/ckeditor5-font/tests/fontcolor/fontcolorui.js

@@ -0,0 +1,49 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global document */
+
+import FontColorEditing from './../../src/fontcolor/fontcolorediting';
+import FontColorUI from './../../src/fontcolor/fontcolorui';
+import ColorUI from './../../src/ui/colorui';
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import fontColorIcon from '../../theme/icons/font-color.svg';
+
+describe( 'FontColorUI', () => {
+	let element, editor;
+
+	beforeEach( () => {
+		element = document.createElement( 'div' );
+		document.body.appendChild( element );
+
+		return ClassicTestEditor
+			.create( element, {
+				plugins: [ FontColorEditing, FontColorUI ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+			} );
+	} );
+
+	afterEach( () => {
+		element.remove();
+
+		return editor.destroy();
+	} );
+
+	it( 'is ColorUI', () => {
+		expect( FontColorUI.prototype ).to.be.instanceOf( ColorUI );
+	} );
+
+	it( 'has properly set initial values', () => {
+		const fontColorUIPlugin = editor.plugins.get( 'FontColorUI' );
+
+		expect( fontColorUIPlugin.commandName ).to.equal( 'fontColor' );
+		expect( fontColorUIPlugin.componentName ).to.equal( 'fontColor' );
+		expect( fontColorUIPlugin.icon ).to.equal( fontColorIcon );
+		expect( fontColorUIPlugin.dropdownLabel ).to.equal( 'Font Color' );
+		expect( fontColorUIPlugin.columns ).to.equal( 5 );
+	} );
+} );

+ 12 - 0
packages/ckeditor5-font/tests/manual/font-color.html

@@ -0,0 +1,12 @@
+<div id="editor">
+	<p><span>01. no-color; no-color</span></p>
+	<p><span style="color:hsl(0, 0%, 100%);background-color:hsl(0, 0%, 0%);">02. color:hsl(0, 0%, 100%);background-color:hsl(0, 0%, 0%);</span></p>
+	<p><span style="color:hsl(0, 75%, 60%);">03. color:hsl(0, 75%, 60%); no-color;</span></p>
+	<p><span style="background-color:hsl(90, 75%, 60%);">04. no-color; background-color:hsl(90, 75%, 60%);</span></p>
+	<p><span style="color:hsl(30, 75%, 60%);background-color:hsl(0, 0%, 30%);">05. color:hsl(30, 75%, 60%); background-color:hsl(0, 0%, 30%);</span></p>
+	<p><span style="color:#00FFFF;background-color:rgb(255, 0, 0);">06. color:#00FFFF;background-color:rgb(255, 0, 0);</span></p>
+	<p><span style="color:hsla( 0, 0%, 0%, .7);background-color:gold;">07. color:hsla( 0, 0%, 0%, .7); background-color:gold;</span></p>
+	<p><span style="color:rgba( 0, 120, 250, 0.8);background-color:hsla(270, 100%, 50%, 0.3);">08. color:;background-color:hsla(270, 100%, 50%, 0.3);</span></p>
+	<p><span style="color:#ddd;background-color:hsl(150, 75%, 60%);">09. color:#ddd;background-color:hsl(150, 75%, 60%);</span></p>
+	<p><span style="color:hsl(270, 75%, 60%);background-color:#d82;">10. color:hsl(270, 75%, 60%);background-color:#d82;</span></p>
+</div>

+ 40 - 0
packages/ckeditor5-font/tests/manual/font-color.js

@@ -0,0 +1,40 @@
+/**
+ * @license Copyright (c) 2003-2019, 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 FontColor from '../../src/fontcolor';
+import FontBackgroundColor from '../../src/fontbackgroundcolor';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [
+			ArticlePluginSet,
+			FontColor,
+			FontBackgroundColor
+		],
+		toolbar: [
+			'heading',
+			'|',
+			'fontColor',
+			'fontBackgroundColor',
+			'bold',
+			'italic',
+			'link',
+			'bulletedList',
+			'numberedList',
+			'blockQuote',
+			'undo',
+			'redo'
+		]
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 16 - 0
packages/ckeditor5-font/tests/manual/font-color.md

@@ -0,0 +1,16 @@
+### Loading
+
+The data should be loaded with different text and background colors in the following order:
+
+* Colors 1-5 use the predefined palette,
+* Colors 6-8 use custom CSS colors, not defined in the configuration,
+* Color 9-10 use a mix from the predefined palette and custom ones.
+
+The format in the editor content is: `N. [font color]; [background color]`.
+
+### Testing
+
+- Change the font color and font background color on selected text.
+- Change the font color and font background color across many paragraphs.
+- Check whether the colors are added to recent colors list.
+- Try to re-apply a color from recent colors list: the color should move to the beginning of the list.

+ 292 - 0
packages/ckeditor5-font/tests/ui/colortableview.js

@@ -0,0 +1,292 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals Event */
+
+import ColorTableView from './../../src/ui/colortableview';
+import Collection from '@ckeditor/ckeditor5-utils/src/collection';
+import ViewCollection from '@ckeditor/ckeditor5-ui/src/viewcollection';
+import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
+import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
+import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler';
+import removeButtonIcon from '@ckeditor/ckeditor5-core/theme/icons/eraser.svg';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+
+describe( 'ColorTableView', () => {
+	let locale, colorTableView;
+
+	const colorDefinitions = [
+		{
+			color: '#000',
+			label: 'Black',
+			options: {
+				hasBorder: false
+			}
+		},
+		{
+			color: 'rgb(255, 255, 255)',
+			label: 'White',
+			options: {
+				hasBorder: true
+			}
+		},
+		{
+			color: 'red',
+			label: 'Red',
+			options: {
+				hasBorder: false
+			}
+		}
+	];
+
+	beforeEach( () => {
+		locale = { t() {} };
+		colorTableView = new ColorTableView( locale, {
+			colors: colorDefinitions,
+			columns: 5,
+			removeButtonLabel: 'Remove color'
+		} );
+		colorTableView.render();
+	} );
+
+	testUtils.createSinonSandbox();
+
+	describe( 'constructor()', () => {
+		it( 'creates items collection', () => {
+			expect( colorTableView.items ).to.be.instanceOf( ViewCollection );
+		} );
+
+		it( 'creates focus tracker', () => {
+			expect( colorTableView.focusTracker ).to.be.instanceOf( FocusTracker );
+		} );
+
+		it( 'creates keystroke handler', () => {
+			expect( colorTableView.keystrokes ).to.be.instanceOf( KeystrokeHandler );
+		} );
+
+		it( 'creates observable for selected color', () => {
+			expect( colorTableView.selectedColor ).to.be.undefined;
+
+			colorTableView.set( 'selectedColor', 'white' );
+
+			expect( colorTableView.selectedColor ).to.equal( 'white' );
+		} );
+
+		it( 'sets tooltip for the remove color button', () => {
+			expect( colorTableView.removeButtonLabel ).to.equal( 'Remove color' );
+		} );
+
+		it( 'sets number of drawed columns', () => {
+			expect( colorTableView.columns ).to.equal( 5 );
+		} );
+
+		it( 'creaets collection of recently used colors', () => {
+			expect( colorTableView.recentlyUsedColors ).to.be.instanceOf( Collection );
+		} );
+
+		it( 'creates focus cycler', () => {
+			expect( colorTableView._focusCycler ).to.be.instanceOf( FocusCycler );
+		} );
+
+		it( 'has proper class', () => {
+			expect( colorTableView.element.classList.contains( 'ck' ) ).to.be.true;
+			expect( colorTableView.element.classList.contains( 'ck-color-table' ) ).to.be.true;
+		} );
+	} );
+
+	describe( 'update elements in focus tracker', () => {
+		it( 'focuses the tile in DOM', () => {
+			const spy = sinon.spy( colorTableView._focusCycler, 'focusFirst' );
+
+			colorTableView.focus();
+
+			sinon.assert.calledOnce( spy );
+		} );
+
+		it( 'focuses last the tile in DOM', () => {
+			const spy = sinon.spy( colorTableView._focusCycler, 'focusLast' );
+
+			colorTableView.focusLast();
+
+			sinon.assert.calledOnce( spy );
+		} );
+	} );
+
+	describe( 'remove color button', () => {
+		let removeButton;
+
+		beforeEach( () => {
+			removeButton = colorTableView.items.first;
+		} );
+
+		it( 'has proper class', () => {
+			expect( removeButton.element.classList.contains( 'ck-color-table__remove-color' ) ).to.be.true;
+		} );
+
+		it( 'has proper setting', () => {
+			expect( removeButton.withText ).to.be.true;
+			expect( removeButton.icon ).to.equal( removeButtonIcon );
+			expect( removeButton.tooltip ).to.be.true;
+			expect( removeButton.label ).to.equal( 'Remove color' );
+		} );
+
+		it( 'executes event with "null" value', () => {
+			const spy = sinon.spy();
+			colorTableView.on( 'execute', spy );
+
+			removeButton.element.dispatchEvent( new Event( 'click' ) );
+
+			sinon.assert.calledOnce( spy );
+			sinon.assert.calledWith( spy, sinon.match.any, { value: null } );
+		} );
+	} );
+
+	describe( 'static colors grid', () => {
+		let staticColorTable;
+
+		beforeEach( () => {
+			staticColorTable = colorTableView.items.get( 1 );
+		} );
+
+		it( 'has added 3 children from definition', () => {
+			expect( staticColorTable.items.length ).to.equal( 3 );
+		} );
+
+		it( 'binds #selectedColor to the table', () => {
+			colorTableView.selectedColor = 'foo';
+			expect( staticColorTable.selectedColor ).to.equal( 'foo' );
+
+			colorTableView.selectedColor = 'bar';
+			expect( staticColorTable.selectedColor ).to.equal( 'bar' );
+		} );
+
+		colorDefinitions.forEach( ( item, index ) => {
+			it( `dispatch event to parent element for color: ${ item.color }`, () => {
+				const spy = sinon.spy();
+				colorTableView.on( 'execute', spy );
+
+				staticColorTable.items.get( index ).element.dispatchEvent( new Event( 'click' ) );
+
+				sinon.assert.calledOnce( spy );
+				sinon.assert.calledWith( spy, sinon.match.any, {
+					value: item.color,
+					label: item.label,
+					hasBorder: item.options.hasBorder
+				} );
+			} );
+		} );
+	} );
+
+	describe( 'recent colors grid', () => {
+		const colorBlack = {
+			color: '#000000',
+			label: 'Black',
+			hasBorder: false
+		};
+		const colorWhite = {
+			color: '#FFFFFF',
+			label: 'Black',
+			hasBorder: true
+		};
+		const colorRed = {
+			color: 'rgb(255,0,0)',
+			hasBorder: false
+		};
+		const emptyColor = {
+			color: 'hsla(0, 0%, 0%, 0)',
+			isEnabled: false,
+			hasBorder: true
+		};
+
+		let recentColorsGridView, recentColorModel;
+
+		beforeEach( () => {
+			recentColorModel = colorTableView.recentlyUsedColors;
+			recentColorsGridView = colorTableView.items.last;
+		} );
+
+		describe( 'initialization', () => {
+			it( 'has proper length of populated items', () => {
+				expect( recentColorModel.length ).to.equal( 5 );
+			} );
+
+			for ( let i = 0; i < 5; i++ ) {
+				it( `initialized item with index: "${ i }" has proper attributes`, () => {
+					const modelItem = recentColorModel.get( i );
+					const viewItem = recentColorsGridView.items.get( i );
+
+					expect( modelItem.color ).to.equal( 'hsla(0, 0%, 0%, 0)' );
+					expect( modelItem.isEnabled ).to.be.false;
+					expect( modelItem.hasBorder ).to.be.true;
+					expect( viewItem.isEnabled ).to.be.false;
+					expect( viewItem.color ).to.equal( 'hsla(0, 0%, 0%, 0)' );
+					expect( viewItem.hasBorder ).to.be.true;
+					expect( viewItem.label ).to.be.undefined;
+				} );
+			}
+		} );
+
+		describe( 'model manipulation', () => {
+			it( 'adding item will preserve length of collection', () => {
+				expect( recentColorModel.length ).to.equal( 5 );
+
+				recentColorModel.add( Object.assign( {}, colorBlack ), 0 );
+
+				expect( recentColorModel.length ).to.equal( 5 );
+				expect( recentColorModel.first.color ).to.equal( '#000000' );
+				expect( recentColorModel.first.label ).to.equal( 'Black' );
+				expect( recentColorModel.first.hasBorder ).to.be.false;
+			} );
+
+			it( 'adding multiple times same color should not work', () => {
+				recentColorModel.add( Object.assign( {}, colorBlack ), 0 );
+
+				expect( recentColorModel.first ).to.own.include( colorBlack );
+				expect( recentColorModel.get( 1 ) ).to.own.include( emptyColor );
+
+				recentColorModel.add( Object.assign( {}, colorBlack ), 0 );
+
+				expect( recentColorModel.first ).to.own.include( colorBlack );
+				expect( recentColorModel.get( 1 ) ).to.own.include( emptyColor );
+			} );
+
+			it( 'adding duplicates move color to the front', () => {
+				recentColorModel.add( Object.assign( {}, colorBlack ), 0 );
+				recentColorModel.add( Object.assign( {}, colorWhite ), 0 );
+				recentColorModel.add( Object.assign( {}, colorRed ), 0 );
+
+				expect( recentColorModel.get( 0 ) ).to.own.include( colorRed );
+				expect( recentColorModel.get( 1 ) ).to.own.include( colorWhite );
+				expect( recentColorModel.get( 2 ) ).to.own.include( colorBlack );
+				expect( recentColorModel.get( 3 ) ).to.own.include( emptyColor );
+
+				recentColorModel.add( Object.assign( {}, colorBlack ), 0 );
+
+				expect( recentColorModel.get( 0 ) ).to.own.include( colorBlack );
+				expect( recentColorModel.get( 1 ) ).to.own.include( colorRed );
+				expect( recentColorModel.get( 2 ) ).to.own.include( colorWhite );
+				expect( recentColorModel.get( 3 ) ).to.own.include( emptyColor );
+			} );
+		} );
+
+		describe( 'events', () => {
+			it( 'added colors delegates execute to parent', () => {
+				const spy = sinon.spy();
+				colorTableView.on( 'execute', spy );
+
+				recentColorModel.add( Object.assign( {}, colorBlack ), 0 );
+
+				recentColorsGridView.items.first.element.dispatchEvent( new Event( 'click' ) );
+
+				sinon.assert.calledOnce( spy );
+				sinon.assert.calledWith( spy, sinon.match.any, {
+					value: '#000000',
+					label: 'Black',
+					hasBorder: false
+				} );
+			} );
+		} );
+	} );
+} );

+ 265 - 0
packages/ckeditor5-font/tests/ui/colorui.js

@@ -0,0 +1,265 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global document, Event */
+
+import ColorUI from './../../src/ui/colorui';
+import FontColorCommand from './../../src/fontcolor/fontcolorcommand';
+
+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';
+
+describe( 'ColorUI', () => {
+	class TestColorPlugin extends ColorUI {
+		constructor( editor ) {
+			super( editor, {
+				commandName: 'testColorCommand',
+				componentName: 'testColor',
+				icon: '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"></svg>',
+				dropdownLabel: editor.locale.t( 'Test Color' )
+			} );
+
+			editor.commands.add( 'testColorCommand', new FontColorCommand( editor ) );
+		}
+
+		static get pluginName() {
+			return 'TestColorPlugin';
+		}
+	}
+
+	const testColorConfig = {
+		colors: [
+			'yellow',
+			{
+				color: '#000',
+			},
+			{
+				color: 'rgb(255, 255, 255)',
+				label: 'White',
+				hasBorder: true
+			},
+			{
+				color: 'red',
+				label: 'Red'
+			},
+			{
+				color: '#00FF00',
+				label: 'Green',
+				hasBorder: false
+			}
+		],
+		columns: 3
+	};
+
+	testUtils.createSinonSandbox();
+
+	before( () => {
+		addTranslations( 'pl', {
+			'Test Color': 'Testowy plugin',
+			'Remove color': 'Usuń kolor',
+			'Yellow': 'Żółty',
+			'White': 'Biały',
+			'Red': 'Czerwony',
+			'Green': 'Zielony'
+		} );
+	} );
+
+	after( () => {
+		clearTranslations();
+	} );
+
+	let editor, element, testColorPlugin, command;
+
+	beforeEach( () => {
+		element = document.createElement( 'div' );
+		document.body.appendChild( element );
+
+		return ClassicTestEditor
+			.create( element, {
+				plugins: [ TestColorPlugin ],
+				testColor: testColorConfig
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+				testColorPlugin = newEditor.plugins.get( 'TestColorPlugin' );
+			} );
+	} );
+
+	afterEach( () => {
+		element.remove();
+
+		return editor.destroy();
+	} );
+
+	describe( 'constructor()', () => {
+		it( 'has assigned proper commandName', () => {
+			expect( testColorPlugin.commandName ).to.equal( 'testColorCommand' );
+		} );
+
+		it( 'has assigned proper componentName', () => {
+			expect( testColorPlugin.componentName ).to.equal( 'testColor' );
+		} );
+
+		it( 'has assigned proper icon', () => {
+			expect( testColorPlugin.icon ).to.equal( '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"></svg>' );
+		} );
+
+		it( 'has assigned proper dropdownLabel', () => {
+			expect( testColorPlugin.dropdownLabel ).to.equal( 'Testowy plugin' );
+		} );
+
+		it( 'has assigned proper amount of columns', () => {
+			// Value taken from editor's config above.
+			expect( testColorPlugin.columns ).to.equal( 3 );
+		} );
+	} );
+
+	describe( 'testColor Dropdown', () => {
+		let dropdown;
+
+		beforeEach( () => {
+			command = editor.commands.get( 'testColorCommand' );
+			dropdown = editor.ui.componentFactory.create( 'testColor' );
+		} );
+
+		it( 'button has the base properties', () => {
+			const button = dropdown.buttonView;
+
+			expect( button ).to.have.property( 'label', 'Testowy plugin' );
+			expect( button ).to.have.property( 'tooltip', true );
+			expect( button ).to.have.property( 'icon', '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"></svg>' );
+		} );
+
+		it( 'should add custom CSS class to dropdown', () => {
+			const dropdown = editor.ui.componentFactory.create( 'testColor' );
+
+			dropdown.render();
+
+			expect( dropdown.element.classList.contains( 'ck-color-ui-dropdown' ) ).to.be.true;
+		} );
+
+		it( 'should focus view after command execution from dropdown', () => {
+			const focusSpy = testUtils.sinon.spy( editor.editing.view, 'focus' );
+			const dropdown = editor.ui.componentFactory.create( 'testColor' );
+
+			dropdown.commandName = 'testColorCommand';
+			dropdown.fire( 'execute', { value: null } );
+
+			sinon.assert.calledOnce( focusSpy );
+		} );
+
+		it( 'static color grid should impact on recent colors', () => {
+			const firstStaticTile = dropdown.colorTableView.items.get( 1 ).items.first;
+			const recentColorsModel = dropdown.colorTableView.recentlyUsedColors;
+			const spy = sinon.spy();
+
+			dropdown.on( 'execute', spy );
+
+			firstStaticTile.element.dispatchEvent( new Event( 'click' ) );
+
+			sinon.assert.calledOnce( spy );
+			sinon.assert.calledWith( spy, sinon.match.any, {
+				value: 'yellow',
+				label: 'yellow',
+				hasBorder: false
+			} );
+			expect( recentColorsModel.get( 0 ) ).to.include( {
+				color: 'yellow',
+				label: 'yellow',
+				hasBorder: 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', () => {
+			let editor, editorElement;
+
+			beforeEach( () => {
+				editorElement = document.createElement( 'div' );
+				document.body.appendChild( editorElement );
+
+				return createLocalizedEditor( editorElement )
+					.then( localizedEditor => {
+						editor = localizedEditor;
+					} );
+			} );
+
+			afterEach( () => {
+				editorElement.remove();
+
+				return editor.destroy();
+			} );
+
+			it( 'works for the colorTableView#items in the panel', () => {
+				const colorTableView = dropdown.colorTableView;
+
+				expect( colorTableView.removeButtonLabel ).to.equal( 'Usuń kolor' );
+				expect( colorTableView.items.first.label ).to.equal( 'Usuń kolor' );
+			} );
+
+			describe( 'works for', () => {
+				const colors = [
+					{
+						color: 'yellow',
+						label: 'yellow'
+					},
+					{
+						color: '#000',
+						label: '#000'
+					},
+					{
+						color: 'rgb(255,255,255)',
+						label: 'Biały'
+					},
+					{
+						color: 'red',
+						label: 'Czerwony'
+					},
+					{
+						color: '#00FF00',
+						label: 'Zielony'
+					}
+				];
+
+				colors.forEach( test => {
+					it( `tested color "${ test.color }" translated to "${ test.label }".`, () => {
+						const colorGrid = dropdown.colorTableView.items.get( 1 );
+						const tile = colorGrid.items.find( colorTile => test.color === colorTile.color );
+
+						expect( tile.label ).to.equal( test.label );
+					} );
+				} );
+			} );
+
+			function createLocalizedEditor( editorElement ) {
+				return ClassicTestEditor
+					.create( editorElement, {
+						plugins: [ TestColorPlugin ],
+						testColor: testColorConfig,
+						toolbar: [ 'testColor' ],
+						language: 'pl',
+					} )
+					.then( newEditor => {
+						editor = newEditor;
+						dropdown = editor.ui.componentFactory.create( 'testColor' );
+						command = editor.commands.get( 'testColorCommand' );
+
+						return editor;
+					} );
+			}
+		} );
+	} );
+} );

+ 137 - 0
packages/ckeditor5-font/tests/utils.js

@@ -0,0 +1,137 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import {
+	FONT_COLOR,
+	FONT_BACKGROUND_COLOR,
+	normalizeColorOptions,
+	addColorTableToDropdown
+} from './../src/utils';
+import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
+import ColorTableView from './../src/ui/colortableview';
+
+describe( 'utils', () => {
+	describe( 'color and background color related', () => {
+		it( 'plugin names has proper values', () => {
+			expect( FONT_COLOR ).to.equal( 'fontColor' );
+			expect( FONT_BACKGROUND_COLOR ).to.equal( 'fontBackgroundColor' );
+		} );
+
+		it( 'normalizeColorOptions can produce the same output object', () => {
+			const normalizedArray = normalizeColorOptions( [
+				'black',
+				{
+					color: 'black'
+				},
+				{
+					color: 'black',
+					label: 'Black'
+				},
+				{
+					color: 'black',
+					label: 'Black',
+					hasBorder: true
+				},
+				{
+					color: 'black',
+					hasBorder: true
+				}
+			] );
+
+			expect( normalizedArray ).to.deep.equal( [
+				{
+					model: 'black',
+					label: 'black',
+					hasBorder: false,
+					view: {
+						name: 'span',
+						styles: {
+							color: 'black'
+						},
+						priority: 5
+					}
+				},
+				{
+					model: 'black',
+					label: 'black',
+					hasBorder: false,
+					view: {
+						name: 'span',
+						styles: {
+							color: 'black'
+						},
+						priority: 5
+					}
+				},
+				{
+					model: 'black',
+					label: 'Black',
+					hasBorder: false,
+					view: {
+						name: 'span',
+						styles: {
+							color: 'black'
+						},
+						priority: 5
+					}
+				},
+				{
+					model: 'black',
+					label: 'Black',
+					hasBorder: true,
+					view: {
+						name: 'span',
+						styles: {
+							color: 'black'
+						},
+						priority: 5
+					}
+				},
+				{
+					model: 'black',
+					label: 'black',
+					hasBorder: true,
+					view: {
+						name: 'span',
+						styles: {
+							color: 'black'
+						},
+						priority: 5
+					}
+				},
+			] );
+		} );
+
+		it( 'adding colors table to dropdown works', () => {
+			const dropdown = createDropdown();
+			dropdown.render();
+
+			addColorTableToDropdown( {
+				dropdownView: dropdown,
+				colors: [
+					{
+						label: 'Black',
+						color: '#000',
+						options: {
+							hasBorder: false
+						}
+					},
+					{
+						label: 'White',
+						color: '#FFFFFF',
+						options: {
+							hasBorder: true
+						}
+					}
+				],
+				columns: 2,
+				removeButtonTooltip: 'Remove Color'
+			} );
+
+			expect( dropdown.colorTableView ).to.be.instanceOf( ColorTableView );
+			expect( dropdown.panelView.children.length ).to.equal( 1 );
+		} );
+	} );
+} );

+ 10 - 0
packages/ckeditor5-font/theme/fontcolor.css

@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+.ck .ck-button.ck-color-table__remove-color {
+	display: flex;
+	align-items: center;
+	width: 100%;
+}

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

@@ -0,0 +1 @@
+<svg  viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M4 2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2zm8.38 9.262H7.62L10 5.506l2.38 5.756zm.532 1.285L14.34 16h1.426L10.804 4H9.196L4.234 16H5.66l1.428-3.453h5.824z" fill-rule="evenodd"/></svg>

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

@@ -0,0 +1 @@
+<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M12.4 10.3L10 4.5l-2.4 5.8h4.8zm.5 1.2H7.1L5.7 15H4.2l5-12h1.6l5 12h-1.5L13 11.5z"/><path stroke="#333" d="M3 17h14v1H3z"/></g></svg>