From 046d13201f3836b78fe1b9237988c235288204ae Mon Sep 17 00:00:00 2001
From: aVadim <vadim483@gmail.com>
Date: Sat, 11 Jan 2025 15:41:29 +0300
Subject: [PATCH] upd: docs

---
 README.md                                     |  12 +-
 ...ple-usage.php => demo-01-simple-usage.php} |   0
 ...sheets.php => demo-02-multiple-sheets.php} |   0
 ...heights.php => demo-03-widths-heights.php} |   0
 ...5-04-formulas.php => demo-04-formulas.php} |   0
 ...-v5-05-borders.php => demo-05-borders.php} |   0
 ...eeze.php => demo-06-autofilter-freeze.php} |   0
 ...d-usage.php => demo-07-advanced-usage.php} |   0
 ...-to-left.php => demo-08-right-to-left.php} |   0
 ...rmats.php => demo-09-datetime-formats.php} |   0
 ...ikachu.php => demo-10-drawing-pikachu.php} |   0
 ...-protection.php => demo-11-protection.php} |   0
 ...dation.php => demo-12-data-validation.php} |   0
 demo/demo-13-conditional-formatting.php       | 119 +++
 ....php => demo-21-chart-area-3d-stacked.php} |   0
 ...-area-3d.php => demo-21-chart-area-3d.php} |   0
 ...ked.php => demo-21-chart-area-stacked.php} |   0
 ...-chart-area.php => demo-21-chart-area.php} |   0
 ...cked.php => demo-21-chart-bar-stacked.php} |   0
 ...21-chart-bar.php => demo-21-chart-bar.php} |   0
 ...d.php => demo-21-chart-column-grouped.php} |   0
 ...d.php => demo-21-chart-column-stacked.php} |   0
 ...rt-column.php => demo-21-chart-column.php} |   0
 ...hart-donut.php => demo-21-chart-donut.php} |   0
 ....php => demo-21-chart-line-3d-stacked.php} |   0
 ...-line-3d.php => demo-21-chart-line-3d.php} |   0
 ...ked.php => demo-21-chart-line-stacked.php} |   0
 ...-chart-line.php => demo-21-chart-line.php} |   0
 ...rt-pie-3d.php => demo-21-chart-pie-3d.php} |   0
 ...21-chart-pie.php => demo-21-chart-pie.php} |   0
 ...hart-combo.php => demo-22-chart-combo.php} |   0
 ....php => demo-22-chart-multiple-charts.php} |   0
 ...99-199k-rows.php => demo-99-199k-rows.php} |   0
 docs/90-api-reference.md                      |   1 +
 docs/91-api-class-excel.md                    |  36 +-
 docs/92-api-class-sheet.md                    |  82 +++
 docs/95-api-class-data-validation.md          |  20 +-
 docs/96-api-class-conditional.md              | 676 ++++++++++++++++++
 .../Conditional/Conditional.php               | 117 ++-
 src/FastExcelWriter/Sheet.php                 |   5 +-
 src/FastExcelWriter/StyleManager.php          |   1 +
 41 files changed, 1045 insertions(+), 24 deletions(-)
 rename demo/{demo-v5-01-simple-usage.php => demo-01-simple-usage.php} (100%)
 rename demo/{demo-v5-02-multiple-sheets.php => demo-02-multiple-sheets.php} (100%)
 rename demo/{demo-v5-03-widths-heights.php => demo-03-widths-heights.php} (100%)
 rename demo/{demo-v5-04-formulas.php => demo-04-formulas.php} (100%)
 rename demo/{demo-v5-05-borders.php => demo-05-borders.php} (100%)
 rename demo/{demo-v5-06-autofilter-freeze.php => demo-06-autofilter-freeze.php} (100%)
 rename demo/{demo-v5-07-advanced-usage.php => demo-07-advanced-usage.php} (100%)
 rename demo/{demo-v5-08-right-to-left.php => demo-08-right-to-left.php} (100%)
 rename demo/{demo-v5-09-datetime-formats.php => demo-09-datetime-formats.php} (100%)
 rename demo/{demo-v5-10-drawing-pikachu.php => demo-10-drawing-pikachu.php} (100%)
 rename demo/{demo-v5-11-protection.php => demo-11-protection.php} (100%)
 rename demo/{demo-v5-12-data-validation.php => demo-12-data-validation.php} (100%)
 create mode 100644 demo/demo-13-conditional-formatting.php
 rename demo/{demo-v5-21-chart-area-3d-stacked.php => demo-21-chart-area-3d-stacked.php} (100%)
 rename demo/{demo-v5-21-chart-area-3d.php => demo-21-chart-area-3d.php} (100%)
 rename demo/{demo-v5-21-chart-area-stacked.php => demo-21-chart-area-stacked.php} (100%)
 rename demo/{demo-v5-21-chart-area.php => demo-21-chart-area.php} (100%)
 rename demo/{demo-v5-21-chart-bar-stacked.php => demo-21-chart-bar-stacked.php} (100%)
 rename demo/{demo-v5-21-chart-bar.php => demo-21-chart-bar.php} (100%)
 rename demo/{demo-v5-21-chart-column-grouped.php => demo-21-chart-column-grouped.php} (100%)
 rename demo/{demo-v5-21-chart-column-stacked.php => demo-21-chart-column-stacked.php} (100%)
 rename demo/{demo-v5-21-chart-column.php => demo-21-chart-column.php} (100%)
 rename demo/{demo-v5-21-chart-donut.php => demo-21-chart-donut.php} (100%)
 rename demo/{demo-v5-21-chart-line-3d-stacked.php => demo-21-chart-line-3d-stacked.php} (100%)
 rename demo/{demo-v5-21-chart-line-3d.php => demo-21-chart-line-3d.php} (100%)
 rename demo/{demo-v5-21-chart-line-stacked.php => demo-21-chart-line-stacked.php} (100%)
 rename demo/{demo-v5-21-chart-line.php => demo-21-chart-line.php} (100%)
 rename demo/{demo-v5-21-chart-pie-3d.php => demo-21-chart-pie-3d.php} (100%)
 rename demo/{demo-v5-21-chart-pie.php => demo-21-chart-pie.php} (100%)
 rename demo/{demo-v5-22-chart-combo.php => demo-22-chart-combo.php} (100%)
 rename demo/{demo-v5-22-chart-multiple-charts.php => demo-22-chart-multiple-charts.php} (100%)
 rename demo/{demo-v5-99-199k-rows.php => demo-99-199k-rows.php} (100%)
 create mode 100644 docs/96-api-class-conditional.md

diff --git a/README.md b/README.md
index a79ee10..8aade0a 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ This library is designed to be lightweight, super-fast and requires minimal memo
 * Supports workbook and sheet protection with/without passwords 
 * Supports page settings - page margins, page size
 * Inserting multiple charts
-* Supports data validations
+* Supports data validations and conditional formatting
 
 Jump To:
 * [Changes in version 6](#changes-in-version-6)
@@ -393,12 +393,12 @@ and with minimal memory usage.
 
 Benchmark of PhpSpreadsheet (generation without styles)
 
-| Rows x Cols | Time      | Memory     | 
+| Rows x Cols | Time      | Memory     |
 |-------------|-----------|------------|
-| 1000 x 5    | 0.98 sec  | 2,048 Kb   | 
-| 1000 x 25   | 4.68 sec  | 14,336 Kb  | 
-| 5000 x 25   | 23.19 sec | 77,824 Kb  | 
-| 10000 x 50  | 105.8 sec | 256,000 Kb | 
+| 1000 x 5    | 0.98 sec  | 2,048 Kb   |
+| 1000 x 25   | 4.68 sec  | 14,336 Kb  |
+| 5000 x 25   | 23.19 sec | 77,824 Kb  |
+| 10000 x 50  | 105.8 sec | 256,000 Kb |
 
 Benchmark of FastExcelWriter (generation without styles)
 
diff --git a/demo/demo-v5-01-simple-usage.php b/demo/demo-01-simple-usage.php
similarity index 100%
rename from demo/demo-v5-01-simple-usage.php
rename to demo/demo-01-simple-usage.php
diff --git a/demo/demo-v5-02-multiple-sheets.php b/demo/demo-02-multiple-sheets.php
similarity index 100%
rename from demo/demo-v5-02-multiple-sheets.php
rename to demo/demo-02-multiple-sheets.php
diff --git a/demo/demo-v5-03-widths-heights.php b/demo/demo-03-widths-heights.php
similarity index 100%
rename from demo/demo-v5-03-widths-heights.php
rename to demo/demo-03-widths-heights.php
diff --git a/demo/demo-v5-04-formulas.php b/demo/demo-04-formulas.php
similarity index 100%
rename from demo/demo-v5-04-formulas.php
rename to demo/demo-04-formulas.php
diff --git a/demo/demo-v5-05-borders.php b/demo/demo-05-borders.php
similarity index 100%
rename from demo/demo-v5-05-borders.php
rename to demo/demo-05-borders.php
diff --git a/demo/demo-v5-06-autofilter-freeze.php b/demo/demo-06-autofilter-freeze.php
similarity index 100%
rename from demo/demo-v5-06-autofilter-freeze.php
rename to demo/demo-06-autofilter-freeze.php
diff --git a/demo/demo-v5-07-advanced-usage.php b/demo/demo-07-advanced-usage.php
similarity index 100%
rename from demo/demo-v5-07-advanced-usage.php
rename to demo/demo-07-advanced-usage.php
diff --git a/demo/demo-v5-08-right-to-left.php b/demo/demo-08-right-to-left.php
similarity index 100%
rename from demo/demo-v5-08-right-to-left.php
rename to demo/demo-08-right-to-left.php
diff --git a/demo/demo-v5-09-datetime-formats.php b/demo/demo-09-datetime-formats.php
similarity index 100%
rename from demo/demo-v5-09-datetime-formats.php
rename to demo/demo-09-datetime-formats.php
diff --git a/demo/demo-v5-10-drawing-pikachu.php b/demo/demo-10-drawing-pikachu.php
similarity index 100%
rename from demo/demo-v5-10-drawing-pikachu.php
rename to demo/demo-10-drawing-pikachu.php
diff --git a/demo/demo-v5-11-protection.php b/demo/demo-11-protection.php
similarity index 100%
rename from demo/demo-v5-11-protection.php
rename to demo/demo-11-protection.php
diff --git a/demo/demo-v5-12-data-validation.php b/demo/demo-12-data-validation.php
similarity index 100%
rename from demo/demo-v5-12-data-validation.php
rename to demo/demo-12-data-validation.php
diff --git a/demo/demo-13-conditional-formatting.php b/demo/demo-13-conditional-formatting.php
new file mode 100644
index 0000000..15d2530
--- /dev/null
+++ b/demo/demo-13-conditional-formatting.php
@@ -0,0 +1,119 @@
+<?php
+
+require_once __DIR__ . '/../vendor/autoload.php';
+require_once __DIR__ . '/../src/autoload.php';
+
+$outFileName = __DIR__ . '/output/' . basename(__FILE__, '.php') . '.xlsx';
+
+use avadim\FastExcelWriter\Conditional\Conditional;
+use \avadim\FastExcelWriter\Excel;
+use avadim\FastExcelWriter\Style;
+
+$timer = microtime(true);
+
+// Create Excel workbook
+$excel = Excel::create();
+
+$sheet = $excel->sheet();
+
+$sheet->setColAutoWidth('a');
+
+$sheet->nextRow();
+$sheet->writeCell('cell: =5, >5');
+$sheet->writeCells(range(0, 9))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::make('=', 5, [Style::FILL_COLOR => '#cfc']))
+    ->applyConditionalFormatting(Conditional::make('>', 5, [Style::FILL_COLOR => '#fcc']))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('between [3, 6]');
+$sheet->writeCells(range(0, 9))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::between([3, 6], [Style::FILL_COLOR => '#ccf']))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('not between [3, 6]');
+$sheet->writeCells(range(0, 9))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::notBetween([3, 6], [Style::FILL_COLOR => '#ccf']))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('expression: MOD(RC,2)=0');
+$sheet->writeCells(range(0, 9))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::expression('MOD(RC,2)=0', [Style::FILL_COLOR => '#ff9']))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('colorScale');
+$sheet->writeCells(range(0, 9))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::colorScale('#f99', 'ff9', '9f9'))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('dataBar');
+$sheet->writeCells(range(0, 9))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::dataBar('#99f'))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('aboveAverage');
+$sheet->writeCells(range(0, 9))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::aboveAverage([Style::FILL_COLOR => '#9f9']))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('belowAverage');
+$sheet->writeCells(range(0, 9))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::belowAverage([Style::FILL_COLOR => '#f99']))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('uniqueValues');
+$sheet->writeCells(array_merge(range(0, 7), [7, 7]))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::uniqueValues([Style::FILL_COLOR => '#99f']))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('duplicateValues');
+$sheet->writeCells(array_merge(range(0, 7), [7, 7]))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::duplicateValues([Style::FILL_COLOR => '#ff9']))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('top: 3');
+$sheet->writeCells(range(0, 9))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::top(3, [Style::FILL_COLOR => '#f99']))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('topPercent: 10%');
+$sheet->writeCells(range(0, 9))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::topPercent(10, [Style::FILL_COLOR => '#ff9']))
+;
+
+$sheet->nextRow();
+$sheet->writeCell('lowPercent: 10%');
+$sheet->writeCells(range(0, 9))
+    ->applyOuterBorder(Style::BORDER_THIN)
+    ->applyConditionalFormatting(Conditional::lowPercent(10, [Style::FILL_COLOR => '#ff9']))
+;
+
+// Save to XLSX-file
+$excel->save($outFileName);
+
+echo '<b>', basename(__FILE__, '.php'), "</b><br>\n<br>\n";
+echo 'out filename: ', $outFileName, "<br>\n";
+echo 'elapsed time: ', round(microtime(true) - $timer, 3), ' sec', "<br>\n";
+echo 'memory peak usage: ', memory_get_peak_usage(true), "<br>\n";
\ No newline at end of file
diff --git a/demo/demo-v5-21-chart-area-3d-stacked.php b/demo/demo-21-chart-area-3d-stacked.php
similarity index 100%
rename from demo/demo-v5-21-chart-area-3d-stacked.php
rename to demo/demo-21-chart-area-3d-stacked.php
diff --git a/demo/demo-v5-21-chart-area-3d.php b/demo/demo-21-chart-area-3d.php
similarity index 100%
rename from demo/demo-v5-21-chart-area-3d.php
rename to demo/demo-21-chart-area-3d.php
diff --git a/demo/demo-v5-21-chart-area-stacked.php b/demo/demo-21-chart-area-stacked.php
similarity index 100%
rename from demo/demo-v5-21-chart-area-stacked.php
rename to demo/demo-21-chart-area-stacked.php
diff --git a/demo/demo-v5-21-chart-area.php b/demo/demo-21-chart-area.php
similarity index 100%
rename from demo/demo-v5-21-chart-area.php
rename to demo/demo-21-chart-area.php
diff --git a/demo/demo-v5-21-chart-bar-stacked.php b/demo/demo-21-chart-bar-stacked.php
similarity index 100%
rename from demo/demo-v5-21-chart-bar-stacked.php
rename to demo/demo-21-chart-bar-stacked.php
diff --git a/demo/demo-v5-21-chart-bar.php b/demo/demo-21-chart-bar.php
similarity index 100%
rename from demo/demo-v5-21-chart-bar.php
rename to demo/demo-21-chart-bar.php
diff --git a/demo/demo-v5-21-chart-column-grouped.php b/demo/demo-21-chart-column-grouped.php
similarity index 100%
rename from demo/demo-v5-21-chart-column-grouped.php
rename to demo/demo-21-chart-column-grouped.php
diff --git a/demo/demo-v5-21-chart-column-stacked.php b/demo/demo-21-chart-column-stacked.php
similarity index 100%
rename from demo/demo-v5-21-chart-column-stacked.php
rename to demo/demo-21-chart-column-stacked.php
diff --git a/demo/demo-v5-21-chart-column.php b/demo/demo-21-chart-column.php
similarity index 100%
rename from demo/demo-v5-21-chart-column.php
rename to demo/demo-21-chart-column.php
diff --git a/demo/demo-v5-21-chart-donut.php b/demo/demo-21-chart-donut.php
similarity index 100%
rename from demo/demo-v5-21-chart-donut.php
rename to demo/demo-21-chart-donut.php
diff --git a/demo/demo-v5-21-chart-line-3d-stacked.php b/demo/demo-21-chart-line-3d-stacked.php
similarity index 100%
rename from demo/demo-v5-21-chart-line-3d-stacked.php
rename to demo/demo-21-chart-line-3d-stacked.php
diff --git a/demo/demo-v5-21-chart-line-3d.php b/demo/demo-21-chart-line-3d.php
similarity index 100%
rename from demo/demo-v5-21-chart-line-3d.php
rename to demo/demo-21-chart-line-3d.php
diff --git a/demo/demo-v5-21-chart-line-stacked.php b/demo/demo-21-chart-line-stacked.php
similarity index 100%
rename from demo/demo-v5-21-chart-line-stacked.php
rename to demo/demo-21-chart-line-stacked.php
diff --git a/demo/demo-v5-21-chart-line.php b/demo/demo-21-chart-line.php
similarity index 100%
rename from demo/demo-v5-21-chart-line.php
rename to demo/demo-21-chart-line.php
diff --git a/demo/demo-v5-21-chart-pie-3d.php b/demo/demo-21-chart-pie-3d.php
similarity index 100%
rename from demo/demo-v5-21-chart-pie-3d.php
rename to demo/demo-21-chart-pie-3d.php
diff --git a/demo/demo-v5-21-chart-pie.php b/demo/demo-21-chart-pie.php
similarity index 100%
rename from demo/demo-v5-21-chart-pie.php
rename to demo/demo-21-chart-pie.php
diff --git a/demo/demo-v5-22-chart-combo.php b/demo/demo-22-chart-combo.php
similarity index 100%
rename from demo/demo-v5-22-chart-combo.php
rename to demo/demo-22-chart-combo.php
diff --git a/demo/demo-v5-22-chart-multiple-charts.php b/demo/demo-22-chart-multiple-charts.php
similarity index 100%
rename from demo/demo-v5-22-chart-multiple-charts.php
rename to demo/demo-22-chart-multiple-charts.php
diff --git a/demo/demo-v5-99-199k-rows.php b/demo/demo-99-199k-rows.php
similarity index 100%
rename from demo/demo-v5-99-199k-rows.php
rename to demo/demo-99-199k-rows.php
diff --git a/docs/90-api-reference.md b/docs/90-api-reference.md
index c08e636..238e2e0 100644
--- a/docs/90-api-reference.md
+++ b/docs/90-api-reference.md
@@ -11,6 +11,7 @@ namespace **avadim\FastExcelWriter**
 * [Class RichText](93-api-class-rich-text.md)
 * [Class Charts\Chart](94-api-class-chart.md)
 * [Class DataValidation\DataValidation](95-api-class-data-validation.md)
+* [Class Conditional\Conditional](96-api-class-conditional.md)
 
 ---
 
diff --git a/docs/91-api-class-excel.md b/docs/91-api-class-excel.md
index c431221..5f5b826 100644
--- a/docs/91-api-class-excel.md
+++ b/docs/91-api-class-excel.md
@@ -28,6 +28,7 @@
 * [addNamedRange()](#addnamedrange)
 * [addSharedString()](#addsharedstring)
 * [addStyle()](#addstyle)
+* [addStyleDxfs()](#addstyledxfs)
 * [setAuthor()](#setauthor)
 * [setCompany()](#setcompany)
 * [setDefaultFont()](#setdefaultfont) -- Set default font options
@@ -66,6 +67,7 @@
 * [sheet()](#sheet) -- Returns sheet by number or name of sheet.
 * [getSheet()](#getsheet) -- Alias of sheet()
 * [getSheets()](#getsheets) -- Returns all sheets
+* [getStyleDxfs()](#getstyledxfs)
 * [setSubject()](#setsubject)
 * [setTitle()](#settitle)
 * [unprotect()](#unprotect) -- Unprotect workbook
@@ -155,13 +157,14 @@ _Convert letter range to array of numbers (ZERO based)_
 ---
 
 ```php
-public static function colKeysToIndexes(array $data): array
+public static function colKeysToIndexes(array $data, $offset): array
 ```
 
 
 ### Parameters
 
 * `array $data`
+* `$offset`
 
 ---
 
@@ -505,6 +508,22 @@ public function addStyle($cellStyle, &$resultStyle): int
 
 ---
 
+## addStyleDxfs()
+
+---
+
+```php
+public function addStyleDxfs($style, &$resultStyle): int
+```
+
+
+### Parameters
+
+* `$style`
+* `$resultStyle`
+
+---
+
 ## setAuthor()
 
 ---
@@ -1072,6 +1091,21 @@ public function getSheets(): array
 ```
 _Returns all sheets_
 
+### Parameters
+
+_None_
+
+---
+
+## getStyleDxfs()
+
+---
+
+```php
+public function getStyleDxfs(): array
+```
+
+
 ### Parameters
 
 _None_
diff --git a/docs/92-api-class-sheet.md b/docs/92-api-class-sheet.md
index cde17ff..4c8b597 100644
--- a/docs/92-api-class-sheet.md
+++ b/docs/92-api-class-sheet.md
@@ -6,6 +6,7 @@
 * [setActiveCell()](#setactivecell) -- Set active cell
 * [addCellStyle()](#addcellstyle) -- Add additional styles to a cell
 * [addChart()](#addchart) -- Add chart object to the specified range of cells
+* [addConditionalFormatting()](#addconditionalformatting) -- Add conditional formatting object to the specified range of cells
 * [addDataValidation()](#adddatavalidation) -- Add data validation object to the specified range of cells
 * [addImage()](#addimage) -- Add image to the sheet from local file, URL or image string in base64
 * [addNamedRange()](#addnamedrange) -- Define named range
@@ -36,6 +37,7 @@
 * [applyBorderRight()](#applyborderright)
 * [applyBorderTop()](#applybordertop)
 * [applyColor()](#applycolor) -- Alias of 'setFontColor()'
+* [applyConditionalFormatting()](#applyconditionalformatting)
 * [applyDataValidation()](#applydatavalidation)
 * [applyFillColor()](#applyfillcolor) -- Fill background color
 * [applyFillGradient()](#applyfillgradient) -- Fill background by gradient
@@ -95,6 +97,7 @@
 * [setColWidth()](#setcolwidth) -- Set width of single or multiple column(s)
 * [setColWidthAuto()](#setcolwidthauto) -- Set width of single or multiple column(s)
 * [setColWidths()](#setcolwidths) -- Setting a multiple column's width
+* [getConditionalFormatting()](#getconditionalformatting)
 * [getCurrentCell()](#getcurrentcell) -- Returns address of the current cell
 * [getCurrentCol()](#getcurrentcol) -- Returns current column letter
 * [getCurrentColId()](#getcurrentcolid)
@@ -158,6 +161,7 @@
 * [pagePaperSize()](#pagepapersize) -- Set Paper size (when paperHeight and paperWidth are specified, paperSize should be ignored)
 * [pagePaperWidth()](#pagepaperwidth) -- Width of custom paper as a number followed by a unit identifier mm|cm|in (ex: 21cm, 8.5in)
 * [pagePortrait()](#pageportrait) -- Set page orientation as Portrait
+* [pageScale()](#pagescale)
 * [getPageSetup()](#getpagesetup)
 * [setPageSetup()](#setpagesetup)
 * [setPrintArea()](#setprintarea)
@@ -193,6 +197,7 @@
 * [writeArray()](#writearray) -- Write values from two-dimensional array
 * [writeArrayTo()](#writearrayto) -- Write 2d array form the specified cell
 * [writeCell()](#writecell) -- Write value to the current cell and move pointer to the next cell in the row
+* [writeCells()](#writecells) -- Write several values into cells of one row
 * [writeHeader()](#writeheader)
 * [writeRow()](#writerow) -- Write values to the current row
 * [writeTo()](#writeto) -- Write value to the specified cell and move pointer to the next cell in the row
@@ -262,6 +267,22 @@ _Add chart object to the specified range of cells_
 
 ---
 
+## addConditionalFormatting()
+
+---
+
+```php
+public function addConditionalFormatting(string $range, $conditionals): Sheet
+```
+_Add conditional formatting object to the specified range of cells_
+
+### Parameters
+
+* `string $range`
+* `Conditional|Conditional[] $conditionals`
+
+---
+
 ## addDataValidation()
 
 ---
@@ -760,6 +781,21 @@ _Alias of 'setFontColor()'_
 
 ---
 
+## applyConditionalFormatting()
+
+---
+
+```php
+public function applyConditionalFormatting($conditionals): Sheet
+```
+
+
+### Parameters
+
+* `Conditional|Conditional[] $conditionals`
+
+---
+
 ## applyDataValidation()
 
 ---
@@ -1756,6 +1792,21 @@ $sheet->setColWidths(['B' => 10, 'C' => 'auto', 'E' => 30, 'F' => 40]);
 ```
 
 
+---
+
+## getConditionalFormatting()
+
+---
+
+```php
+public function getConditionalFormatting(): array
+```
+
+
+### Parameters
+
+_None_
+
 ---
 
 ## getCurrentCell()
@@ -2753,6 +2804,21 @@ _None_
 
 ---
 
+## pageScale()
+
+---
+
+```php
+public function pageScale(int $scale): Sheet
+```
+
+
+### Parameters
+
+* `int $scale`
+
+---
+
 ## getPageSetup()
 
 ---
@@ -3370,6 +3436,22 @@ _Write value to the current cell and move pointer to the next cell in the row_
 
 ---
 
+## writeCells()
+
+---
+
+```php
+public function writeCells(array $values, ?array $cellStyles = null): Sheet
+```
+_Write several values into cells of one row_
+
+### Parameters
+
+* `array $values`
+* `array|null $cellStyles`
+
+---
+
 ## writeHeader()
 
 ---
diff --git a/docs/95-api-class-data-validation.md b/docs/95-api-class-data-validation.md
index 1942f82..0df8b61 100644
--- a/docs/95-api-class-data-validation.md
+++ b/docs/95-api-class-data-validation.md
@@ -7,6 +7,7 @@
 * [date()](#date) -- Make data validation as a date value
 * [decimal()](#decimal) -- Make data validation as a decimal value
 * [dropDown()](#dropdown) -- Make data validation as a dropdown list
+* [expression()](#expression) -- Make data validation as an expression (alias of self::custom())
 * [integer()](#integer) -- Make data validation as an integer value
 * [list()](#list) -- Alias of dropDown()
 * [make()](#make) -- Make a DataValidation instance
@@ -110,6 +111,21 @@ _Make data validation as a dropdown list_
 
 ---
 
+## expression()
+
+---
+
+```php
+public static function expression(string $formula): DataValidation
+```
+_Make data validation as an expression (alias of self::custom())_
+
+### Parameters
+
+* `string $formula`
+
+---
+
 ## integer()
 
 ---
@@ -440,12 +456,14 @@ _Show input message_
 ---
 
 ```php
-public function setSqref(string $sqref): DataValidation
+public function setSqref(avadim\FastExcelWriter\Sheet $sheet, 
+                         string $sqref): DataValidation
 ```
 
 
 ### Parameters
 
+* `Sheet $sheet`
 * `string $sqref`
 
 ---
diff --git a/docs/96-api-class-conditional.md b/docs/96-api-class-conditional.md
new file mode 100644
index 0000000..3e0f600
--- /dev/null
+++ b/docs/96-api-class-conditional.md
@@ -0,0 +1,676 @@
+# Class \avadim\FastExcelWriter\Conditional\Conditional
+
+---
+
+* [__construct()](#__construct) -- Create a new Conditional
+* [aboveAverage()](#aboveaverage)
+* [beginsWith()](#beginswith) -- Applies a style if the cell value starts with the specified text
+* [belowAverage()](#belowaverage)
+* [between()](#between) -- The cell value is between two given values
+* [colorScale()](#colorscale)
+* [colorScaleMax()](#colorscalemax)
+* [colorScaleMin()](#colorscalemin)
+* [colorScaleNum()](#colorscalenum)
+* [contains()](#contains) -- Applies a style if the cell value contains the specified text.
+* [dataBar()](#databar) -- Colored data bar inside a cell
+* [duplicateValues()](#duplicatevalues)
+* [isEmpty()](#isempty) -- Applies a style if the cell is empty
+* [endsWith()](#endswith) -- Applies a style if the cell value ends with the specified text
+* [equals()](#equals) -- The cell value is equal to the given value
+* [expression()](#expression) -- Applies the style if the expression evaluates to TRUE
+* [greaterThan()](#greaterthan) -- The cell value is greater than the specified value
+* [greaterThanOrEqual()](#greaterthanorequal) -- The cell value is greater than or equal to the specified value
+* [lessThan()](#lessthan) -- The cell value is less than the specified value
+* [lessThanOrEqual()](#lessthanorequal) -- The cell value is less than or equal to the specified value
+* [low()](#low)
+* [lowPercent()](#lowpercent)
+* [make()](#make) -- Cell value is compared to a specified value or formula
+* [notBetween()](#notbetween) -- The cell value is between two given values
+* [notContains()](#notcontains) -- Applies a style if the cell value does not contain the specified text.
+* [notEquals()](#notequals) -- The cell value is not equal to the specified value
+* [top()](#top)
+* [topPercent()](#toppercent)
+* [uniqueValues()](#uniquevalues)
+* [setDirectionRtl()](#setdirectionrtl) -- Determines the direction of the bars
+* [setDxfId()](#setdxfid)
+* [setFillColor()](#setfillcolor)
+* [setFontColor()](#setfontcolor)
+* [setGradient()](#setgradient) -- Enables or disables the gradient style of the bars
+* [setShowValue()](#setshowvalue) -- Controls the display of the value in a cell
+* [setSqref()](#setsqref)
+* [getStyle()](#getstyle)
+* [setStyle()](#setstyle)
+* [toXml()](#toxml)
+
+---
+
+## __construct()
+
+---
+
+```php
+public function __construct(string $type, string $operator, $options, $style)
+```
+_Create a new Conditional_
+
+### Parameters
+
+* `$type`
+* `$operator`
+* `$options`
+* `$style`
+
+---
+
+## aboveAverage()
+
+---
+
+```php
+public static function aboveAverage(array $style): Conditional
+```
+
+
+### Parameters
+
+* `array $style`
+
+---
+
+## beginsWith()
+
+---
+
+```php
+public static function beginsWith(string $text, 
+                                  ?array $style = null): Conditional
+```
+_Applies a style if the cell value starts with the specified text_
+
+### Parameters
+
+* `string $text`
+* `array|null $style`
+
+---
+
+## belowAverage()
+
+---
+
+```php
+public static function belowAverage(array $style): Conditional
+```
+
+
+### Parameters
+
+* `array $style`
+
+---
+
+## between()
+
+---
+
+```php
+public static function between(array $values, 
+                               ?array $style = null): Conditional
+```
+_The cell value is between two given values_
+
+### Parameters
+
+* `int[]|float[] $values`
+* `array|null $style`
+
+---
+
+## colorScale()
+
+---
+
+```php
+public static function colorScale(string $color1, string $color2, 
+                                  ?string $color3 = null): Conditional
+```
+
+
+### Parameters
+
+* `string $color1`
+* `string $color2`
+* `string|null $color3`
+
+---
+
+## colorScaleMax()
+
+---
+
+```php
+public static function colorScaleMax(string $color): Conditional
+```
+
+
+### Parameters
+
+* `string $color`
+
+---
+
+## colorScaleMin()
+
+---
+
+```php
+public static function colorScaleMin(string $color): Conditional
+```
+
+
+### Parameters
+
+* `string $color`
+
+---
+
+## colorScaleNum()
+
+---
+
+```php
+public static function colorScaleNum(array $values, string $color1, 
+                                     string $color2, 
+                                     ?string $color3 = null): Conditional
+```
+
+
+### Parameters
+
+* `array $values`
+* `string $color1`
+* `string $color2`
+* `string|null $color3`
+
+---
+
+## contains()
+
+---
+
+```php
+public static function contains(string $text, 
+                                ?array $style = null): Conditional
+```
+_Applies a style if the cell value contains the specified text._
+
+### Parameters
+
+* `string $text`
+* `array|null $style`
+
+---
+
+## dataBar()
+
+---
+
+```php
+public static function dataBar(string $color): Conditional
+```
+_Colored data bar inside a cell_
+
+### Parameters
+
+* `string $color`
+
+---
+
+## duplicateValues()
+
+---
+
+```php
+public static function duplicateValues(array $style): Conditional
+```
+
+
+### Parameters
+
+* `array $style`
+
+---
+
+## isEmpty()
+
+---
+
+```php
+public static function isEmpty(?string $cell = null, 
+                               ?array $style = null): Conditional
+```
+_Applies a style if the cell is empty_
+
+### Parameters
+
+* `string|null $cell`
+* `array|null $style`
+
+---
+
+## endsWith()
+
+---
+
+```php
+public static function endsWith(string $text, 
+                                ?array $style = null): Conditional
+```
+_Applies a style if the cell value ends with the specified text_
+
+### Parameters
+
+* `string $text`
+* `array|null $style`
+
+---
+
+## equals()
+
+---
+
+```php
+public static function equals($value, ?array $style = null): Conditional
+```
+_The cell value is equal to the given value_
+
+### Parameters
+
+* `int|float|string $value`
+* `array|null $style`
+
+---
+
+## expression()
+
+---
+
+```php
+public static function expression(string $formula, 
+                                  ?array $style = null): Conditional
+```
+_Applies the style if the expression evaluates to TRUE_
+
+### Parameters
+
+* `string $formula`
+* `array|null $style`
+
+---
+
+## greaterThan()
+
+---
+
+```php
+public static function greaterThan($value, ?array $style = null): Conditional
+```
+_The cell value is greater than the specified value_
+
+### Parameters
+
+* `int|float|string $value`
+* `array|null $style`
+
+---
+
+## greaterThanOrEqual()
+
+---
+
+```php
+public static function greaterThanOrEqual($value, 
+                                          ?array $style = null): Conditional
+```
+_The cell value is greater than or equal to the specified value_
+
+### Parameters
+
+* `int|float|string $value`
+* `array|null $style`
+
+---
+
+## lessThan()
+
+---
+
+```php
+public static function lessThan($value, ?array $style = null): Conditional
+```
+_The cell value is less than the specified value_
+
+### Parameters
+
+* `int|float|string $value`
+* `array|null $style`
+
+---
+
+## lessThanOrEqual()
+
+---
+
+```php
+public static function lessThanOrEqual($value, 
+                                       ?array $style = null): Conditional
+```
+_The cell value is less than or equal to the specified value_
+
+### Parameters
+
+* `int|float|string $value`
+* `array|null $style`
+
+---
+
+## low()
+
+---
+
+```php
+public static function low(int $rank, array $style): Conditional
+```
+
+
+### Parameters
+
+* `int $rank`
+* `array $style`
+
+---
+
+## lowPercent()
+
+---
+
+```php
+public static function lowPercent(int $rank, array $style): Conditional
+```
+
+
+### Parameters
+
+* `int $rank`
+* `array $style`
+
+---
+
+## make()
+
+---
+
+```php
+public static function make(string $operator, $formula, 
+                            ?array $style = null): Conditional
+```
+_Cell value is compared to a specified value or formula_
+
+### Parameters
+
+* `string $operator`
+* `int|float|string|array $formula`
+* `array|null $style`
+
+---
+
+## notBetween()
+
+---
+
+```php
+public static function notBetween(array $values, 
+                                  ?array $style = null): Conditional
+```
+_The cell value is between two given values_
+
+### Parameters
+
+* `int[]|float[] $values`
+* `array|null $style`
+
+---
+
+## notContains()
+
+---
+
+```php
+public static function notContains(string $text, 
+                                   ?array $style = null): Conditional
+```
+_Applies a style if the cell value does not contain the specified text._
+
+### Parameters
+
+* `string $text`
+* `array|null $style`
+
+---
+
+## notEquals()
+
+---
+
+```php
+public static function notEquals($value, ?array $style = null): Conditional
+```
+_The cell value is not equal to the specified value_
+
+### Parameters
+
+* `int|float|string $value`
+* `array|null $style`
+
+---
+
+## top()
+
+---
+
+```php
+public static function top(int $rank, array $style): Conditional
+```
+
+
+### Parameters
+
+* `int $rank`
+* `array $style`
+
+---
+
+## topPercent()
+
+---
+
+```php
+public static function topPercent(int $rank, array $style): Conditional
+```
+
+
+### Parameters
+
+* `int $rank`
+* `array $style`
+
+---
+
+## uniqueValues()
+
+---
+
+```php
+public static function uniqueValues(array $style): Conditional
+```
+
+
+### Parameters
+
+* `array $style`
+
+---
+
+## setDirectionRtl()
+
+---
+
+```php
+public function setDirectionRtl(bool $value): Conditional
+```
+_Determines the direction of the bars_
+
+### Parameters
+
+* `bool $value`
+
+---
+
+## setDxfId()
+
+---
+
+```php
+public function setDxfId(int $dxfId): Conditional
+```
+
+
+### Parameters
+
+* `int $dxfId`
+
+---
+
+## setFillColor()
+
+---
+
+```php
+public function setFillColor($color): Conditional
+```
+
+
+### Parameters
+
+* `$color`
+
+---
+
+## setFontColor()
+
+---
+
+```php
+public function setFontColor($color): Conditional
+```
+
+
+### Parameters
+
+* `$color`
+
+---
+
+## setGradient()
+
+---
+
+```php
+public function setGradient(bool $value): Conditional
+```
+_Enables or disables the gradient style of the bars_
+
+### Parameters
+
+* `bool $value`
+
+---
+
+## setShowValue()
+
+---
+
+```php
+public function setShowValue(bool $value): Conditional
+```
+_Controls the display of the value in a cell_
+
+### Parameters
+
+* `bool $value`
+
+---
+
+## setSqref()
+
+---
+
+```php
+public function setSqref(avadim\FastExcelWriter\Sheet $sheet, 
+                         string $sqref): Conditional
+```
+
+
+### Parameters
+
+* `Sheet $sheet`
+* `string $sqref`
+
+---
+
+## getStyle()
+
+---
+
+```php
+public function getStyle(): ?array
+```
+
+
+### Parameters
+
+_None_
+
+---
+
+## setStyle()
+
+---
+
+```php
+public function setStyle($style): Conditional
+```
+
+
+### Parameters
+
+* `string|array $style`
+
+---
+
+## toXml()
+
+---
+
+```php
+public function toXml(int $priority, $formulaConverter): string
+```
+
+
+### Parameters
+
+* `int $priority`
+* `$formulaConverter`
+
+---
+
diff --git a/src/FastExcelWriter/Conditional/Conditional.php b/src/FastExcelWriter/Conditional/Conditional.php
index 5621090..0a5255b 100644
--- a/src/FastExcelWriter/Conditional/Conditional.php
+++ b/src/FastExcelWriter/Conditional/Conditional.php
@@ -484,11 +484,21 @@ public function setDirectionRtl(bool $value): Conditional
         return $this;
     }
 
+    /**
+     * @param array $style
+     *
+     * @return Conditional
+     */
     public static function aboveAverage(array $style): Conditional
     {
         return new self(self::CONDITION_ABOVE_AVERAGE, '', null, $style);
     }
 
+    /**
+     * @param array $style
+     *
+     * @return Conditional
+     */
     public static function belowAverage(array $style): Conditional
     {
         $options = [
@@ -498,16 +508,32 @@ public static function belowAverage(array $style): Conditional
         return new self(self::CONDITION_BELOW_AVERAGE, '', $options, $style);
     }
 
+    /**
+     * @param array $style
+     *
+     * @return Conditional
+     */
     public static function uniqueValues(array $style): Conditional
     {
         return new self(self::CONDITION_UNIQUE_VALUES, '', null, $style);
     }
 
+    /**
+     * @param array $style
+     *
+     * @return Conditional
+     */
     public static function duplicateValues(array $style): Conditional
     {
         return new self(self::CONDITION_DUPLICATE_VALUES, '', null, $style);
     }
 
+    /**
+     * @param int $rank
+     * @param array $style
+     *
+     * @return Conditional
+     */
     public static function top(int $rank, array $style): Conditional
     {
         $options = [
@@ -518,14 +544,54 @@ public static function top(int $rank, array $style): Conditional
         return new self(self::CONDITION_TOP10, '', ['options' => $options], $style);
     }
 
+    /**
+     * @param int $rank
+     * @param array $style
+     *
+     * @return Conditional
+     */
     public static function topPercent(int $rank, array $style): Conditional
     {
-        $formula = [
+        $options = [
+            'rank' => $rank,
+            'percent' => 1,
+        ];
+
+        return new self(self::CONDITION_TOP10, '', ['options' => $options], $style);
+    }
+
+    /**
+     * @param int $rank
+     * @param array $style
+     *
+     * @return Conditional
+     */
+    public static function low(int $rank, array $style): Conditional
+    {
+        $options = [
+            'rank' => $rank,
+            'percent' => 0,
+            'bottom' => 1,
+        ];
+
+        return new self(self::CONDITION_TOP10, '', ['options' => $options], $style);
+    }
+
+    /**
+     * @param int $rank
+     * @param array $style
+     *
+     * @return Conditional
+     */
+    public static function lowPercent(int $rank, array $style): Conditional
+    {
+        $options = [
             'rank' => $rank,
             'percent' => 1,
+            'bottom' => 1,
         ];
 
-        return new self(self::CONDITION_TOP10, '', $formula, $style);
+        return new self(self::CONDITION_TOP10, '', ['options' => $options], $style);
     }
 
     /**
@@ -603,6 +669,23 @@ public function getStyle(): ?array
         return $this->style;
     }
 
+    /**
+     * @param array $attributes
+     *
+     * @return string
+     */
+    protected function _attr(array $attributes): string
+    {
+        $result = '';
+        foreach ($attributes as $attribute => $value) {
+            if ($value !== null) {
+                $result .= ' ' . $attribute . '="' . $value . '"';
+            }
+        }
+
+        return $result;
+    }
+
     /**
      * @param int $priority
      * @param $formulaConverter
@@ -612,19 +695,19 @@ public function getStyle(): ?array
     public function toXml(int $priority, $formulaConverter = null): string
     {
         $xml = '<conditionalFormatting sqref="' . $this->sqref . '">';
+        $firstCell = strpos($this->sqref, ':') ? strstr($this->sqref, ':', true) : $this->sqref;
         if ($this->conditionType === self::CONDITION_TEXT) {
-            $cell = strpos($this->sqref, ':') ? strstr($this->sqref, ':', true) : $this->sqref;
             if ($this->operator === self::OPERATOR_NOT_CONTAINS) {
-                $formula = 'ISERROR(SEARCH("' . $this->text . '",' . $cell . '))';
+                $formula = 'ISERROR(SEARCH("' . $this->text . '",' . $firstCell . '))';
             }
             elseif ($this->operator === self::OPERATOR_BEGINS_WITH) {
-                $formula = 'LEFT(' . $cell . ',' . mb_strlen($this->text) . ')="' . $this->text . '"';
+                $formula = 'LEFT(' . $firstCell . ',' . mb_strlen($this->text) . ')="' . $this->text . '"';
             }
             elseif ($this->operator === self::OPERATOR_ENDS_WITH) {
-                $formula = 'RIGHT(' . $cell . ',' . mb_strlen($this->text) . ')="' . $this->text . '"';
+                $formula = 'RIGHT(' . $firstCell . ',' . mb_strlen($this->text) . ')="' . $this->text . '"';
             }
             else {
-                $formula = 'NOT(ISERROR(SEARCH("' . $this->text . '",' . $cell . ')))';
+                $formula = 'NOT(ISERROR(SEARCH("' . $this->text . '",' . $firstCell . ')))';
             }
             $xml .= '<cfRule type="' . $this->conditionType . '" dxfId="' . $this->dxfId . '" priority="' . $priority . '" operator="' . $this->operator . '" text="' . $this->text . '">';
             $xml .= '<formula>' . $formula . '</formula>';
@@ -659,7 +742,17 @@ public function toXml(int $priority, $formulaConverter = null): string
             $xml .= '</cfRule>';
         }
         elseif ($this->conditionType === self::CONDITION_TOP10) {
-            $xml .= '<cfRule type="' . $this->conditionType . '" dxfId="' . $this->dxfId . '" priority="' . $priority . '" rank="' . $this->topOptions['rank'] . '" percent="' . ($this->topOptions['percent'] ? 1 : 0) . '"/>';
+            $attributes = [
+                'type' => $this->conditionType,
+                'dxfId' => $this->dxfId,
+                'priority' => $priority,
+                'rank' => $this->topOptions['rank'],
+                'percent' => ($this->topOptions['percent'] ? 1 : 0),
+            ];
+            if (!empty($this->topOptions['bottom'])) {
+                $attributes['bottom'] = $this->topOptions['bottom'];
+            }
+            $xml .= '<cfRule' . $this->_attr($attributes) . '/>';
         }
         else {
             if ($this->conditionType === self::CONDITION_BELOW_AVERAGE) {
@@ -678,16 +771,12 @@ public function toXml(int $priority, $formulaConverter = null): string
                 'text' => $this->text ?: null,
                 'aboveAverage' => $aboveAverage,
             ];
-            $xml .= '<cfRule';
-            foreach ($attributes as $attribute => $value) {
-                $xml .= ' ' . $attribute . '="' . $value . '"';
-            }
-            $xml .= '>';
+            $xml .= '<cfRule' . $this->_attr($attributes) . '>';
 
             foreach ($this->formula as $formula) {
                 if ($formula !== null && $formula !== '') {
                     if ($formula[0] === '=') {
-                        $formula = ($formulaConverter ? $formulaConverter($formula, $this->sqref) : substr($formula, 1));
+                        $formula = ($formulaConverter ? $formulaConverter($formula, $firstCell) : substr($formula, 1));
                     }
                     $xml .= '<formula>' . $formula . '</formula>';
                 }
diff --git a/src/FastExcelWriter/Sheet.php b/src/FastExcelWriter/Sheet.php
index 0050a51..7138c7b 100644
--- a/src/FastExcelWriter/Sheet.php
+++ b/src/FastExcelWriter/Sheet.php
@@ -2086,7 +2086,7 @@ public function writeCells(array $values, ?array $cellStyles = null): Sheet
             ++$this->currentColIdx;
         }
         $this->_touchStart($startRowIdx, $startColIdx, 'area');
-        $this->_touchEnd($startRowIdx, $this->currentColIdx, 'area');
+        $this->_touchEnd($startRowIdx, $this->currentColIdx - 1, 'area');
 
         return $this;
     }
@@ -5198,7 +5198,8 @@ public function applyConditionalFormatting($conditionals): Sheet
         }
         else {
             $conditional = clone $conditionals;
-            $address = Helper::cellAddress($this->lastTouch['cell']['row_idx'] + 1, $this->lastTouch['cell']['col_idx'] + 1);
+            $address = Helper::cellAddress($this->lastTouch['area']['row_idx1'] + 1, $this->lastTouch['area']['col_idx1'] + 1)
+                . ':' . Helper::cellAddress($this->lastTouch['area']['row_idx2'] + 1, $this->lastTouch['area']['col_idx2'] + 1);
             $this->addConditionalFormatting($address, $conditional);
         }
 
diff --git a/src/FastExcelWriter/StyleManager.php b/src/FastExcelWriter/StyleManager.php
index fd87579..7e83778 100644
--- a/src/FastExcelWriter/StyleManager.php
+++ b/src/FastExcelWriter/StyleManager.php
@@ -762,6 +762,7 @@ public static function normalize(array $style): array
                         }
                     }
                     elseif (is_array($styleVal)) {
+                        $s = self::normalizeFont($styleVal);
                         $result['font'] = $styleVal;
                     }
                     break;