Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proper css inline border #1670 #2728

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changes/1.x/1.4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- Writer ODText: Support for images inside a textRun by [@Progi1984](https://github.com/Progi1984) fixing [#2240](https://github.com/PHPOffice/PHPWord/issues/2240) in [#2668](https://github.com/PHPOffice/PHPWord/pull/2668)
- Allow vAlign and vMerge on Style\Cell to be set to null by [@SpraxDev](https://github.com/SpraxDev) fixing [#2673](https://github.com/PHPOffice/PHPWord/issues/2673) in [#2676](https://github.com/PHPOffice/PHPWord/pull/2676)
- Reader HTML: Support for differents size units for table by [@Progi1984](https://github.com/Progi1984) fixing [#2384](https://github.com/PHPOffice/PHPWord/issues/2384), [#2701](https://github.com/PHPOffice/PHPWord/issues/2701) in [#2725](https://github.com/PHPOffice/PHPWord/pull/2725)
- Html addHTML : Support different order for inline style border by [@Azamat8405](https://github.com/Azamat8405) fixing [#1670](https://github.com/PHPOffice/PHPWord/issues/1670)
- Reader Word2007: Respect paragraph indent units by [@tugmaks](https://github.com/tugmaks) & [@Progi1984](https://github.com/Progi1984) fixing [#507](https://github.com/PHPOffice/PHPWord/issues/507) in [#2726](https://github.com/PHPOffice/PHPWord/pull/2726)
- Reader Word2007: Support Header elements within Title elements by [@SpraxDev](https://github.com/SpraxDev) fixing [#2616](https://github.com/PHPOffice/PHPWord/issues/2616), [#2426](https://github.com/PHPOffice/PHPWord/issues/2426) in [#2674](https://github.com/PHPOffice/PHPWord/pull/2674)
- Reader HTML: Support for inherit value for property line-height by [@Progi1984](https://github.com/Progi1984) fixing [#2683](https://github.com/PHPOffice/PHPWord/issues/2683) in [#2733](https://github.com/PHPOffice/PHPWord/pull/2733)
Expand Down
51 changes: 51 additions & 0 deletions docs/usage/shared/html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# HTML

## HTML
You can generate a Word file from html.

``` php
<?php

$phpWordInstance = new PhpWord();
$phpSectionInstance = $phpWordInstance->addSection([
'orientation' => 'landscape',
'marginLeft' => (int)round(20 * 56.6929133858),
'marginRight' => (int)round(20 * 56.6929133858),
'marginTop' => (int)round(20 * 56.6929133858),
'marginBottom' => (int)round(20 * 56.6929133858),
]);

$html = '<!-- Any html. Table for example -->
<table>
<tr>
<td style="border-left:solid blue 2px;">border-left:solid blue 2px;</td>
<td style="border-right:solid 2px red;">border-right:solid 2px red;</td>
</tr>
<tr>
<td>
<img src="https://any.domain/path-to-img/name.jpg" />
</td>
<td></td>
</tr>
</table>';

$fullHTML = false;
$preserveWhiteSpace = true;

$options = [];
$options['IMG_SRC_SEARCH'] = 'path-to-img/name.jpg';
$options['IMG_SRC_REPLACE'] = 'another-path-to-img/new-name.jpg';

Html::addHtml($phpSectionInstance, $html, $fullHTML, $preserveWhiteSpace, $options);

$fqName = new PhpOffice\PhpWord\Writer\Word2007($phpWordInstance);
$fqName->save('./test.docx');
```

$html - $html param must have root node such as "html" if it is full html;

$fullHTML - If $html is not full html, it may not have a root element. In this case $fullHTML should be false.

$preserveWhiteSpace - Do not remove redundant white space. Default to true.

$options - which could be applied when such an element occurs in the parseNode function.
181 changes: 176 additions & 5 deletions src/PhpWord/Shared/Html.php
Original file line number Diff line number Diff line change
Expand Up @@ -895,9 +895,24 @@ protected static function parseStyleDeclarations(array $selectors, array $styles
case 'border-bottom':
case 'border-right':
case 'border-left':
// must have exact order [width color style], e.g. "1px #0011CC solid" or "2pt green solid"
$value = preg_replace('/ +/', ' ', trim($value));
$valueArr = explode(' ', strtolower($value));

$size = $color = $style = null;
// Word does not accept shortened hex colors e.g. #CCC, only full e.g. #CCCCCC
if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+|[a-zA-Z]+)\s+([a-z]+)/', $value, $matches)) {
// we determine the order of color and style by checking value of the color.
$namedColors = self::mapNamedBorderColor();
foreach ($valueArr as $tmpValue) {
if (strpos($tmpValue, '#') === 0 || isset($namedColors[$tmpValue])) {
$color = trim($tmpValue, '#');
} elseif (preg_match('/[\-\d]+(px|pt|cm|mm|in|pc)/', $tmpValue) === 1) {
$size = Converter::cssToTwip($tmpValue);
} else {
$style = self::mapBorderStyle($tmpValue);
}
}

if ($size !== null && $color !== null && $style !== null) {
if (false !== strpos($property, '-')) {
$tmp = explode('-', $property);
$which = $tmp[1];
Expand All @@ -911,12 +926,11 @@ protected static function parseStyleDeclarations(array $selectors, array $styles
// Therefore we need to normalize converted twip value to cca 1/2 of value.
// This may be adjusted, if better ratio or formula found.
// BC change: up to ver. 0.17.0 was $size converted to points - Converter::cssToPoint($size)
$size = Converter::cssToTwip($matches[1]);
$size = (int) ($size / 2);
// valid variants may be e.g. borderSize, borderTopSize, borderLeftColor, etc ..
$styles["border{$which}Size"] = $size; // twips
$styles["border{$which}Color"] = trim($matches[2], '#');
$styles["border{$which}Style"] = self::mapBorderStyle($matches[3]);
$styles["border{$which}Color"] = $color;
$styles["border{$which}Style"] = $style;
}

break;
Expand Down Expand Up @@ -1065,12 +1079,169 @@ protected static function mapBorderStyle($cssBorderStyle)
case 'dashed':
case 'dotted':
case 'double':
case 'inset':
case 'outset':
return $cssBorderStyle;
default:
return 'single';
}
}

protected static function mapNamedBorderColor(): array
{
$colors = [];
$colors['aliceblue'] = '#f0f8ff';
$colors['antiquewhite'] = '#faebd7';
$colors['aqua'] = '#00ffff';
$colors['aquamarine'] = '#7fffd4';
$colors['azure'] = '#f0ffff';
$colors['beige'] = '#f5f5dc';
$colors['bisque'] = '#ffe4c4';
$colors['black'] = '#000000';
$colors['blanchedalmond'] = '#ffebcd';
$colors['blue'] = '#0000ff';
$colors['blueviolet'] = '#8a2be2';
$colors['brown'] = '#a52a2a';
$colors['burlywood'] = '#deb887';
$colors['cadetblue'] = '#5f9ea0';
$colors['chartreuse'] = '#7fff00';
$colors['chocolate'] = '#d2691e';
$colors['coral'] = '#ff7f50';
$colors['cornflowerblue'] = '#6495ed';
$colors['cornsilk'] = '#fff8dc';
$colors['crimson'] = '#dc143c';
$colors['cyan'] = '#00ffff';
$colors['darkblue'] = '#00008b';
$colors['darkcyan'] = '#008b8b';
$colors['darkgoldenrod'] = '#b8860b';
$colors['darkgray'] = '#a9a9a9';
$colors['darkgrey'] = '#a9a9a9';
$colors['darkgreen'] = '#006400';
$colors['darkkhaki'] = '#bdb76b';
$colors['darkmagenta'] = '#8b008b';
$colors['darkolivegreen'] = '#556b2f';
$colors['darkorange'] = '#ff8c00';
$colors['darkorchid'] = '#9932cc';
$colors['darkred'] = '#8b0000';
$colors['darksalmon'] = '#e9967a';
$colors['darkseagreen'] = '#8fbc8f';
$colors['darkslateblue'] = '#483d8b';
$colors['darkslategray'] = '#2f4f4f';
$colors['darkslategrey'] = '#2f4f4f';
$colors['darkturquoise'] = '#00ced1';
$colors['darkviolet'] = '#9400d3';
$colors['deeppink'] = '#ff1493';
$colors['deepskyblue'] = '#00bfff';
$colors['dimgray'] = '#696969';
$colors['dimgrey'] = '#696969';
$colors['dodgerblue'] = '#1e90ff';
$colors['firebrick'] = '#b22222';
$colors['floralwhite'] = '#fffaf0';
$colors['forestgreen'] = '#228b22';
$colors['fuchsia'] = '#ff00ff';
$colors['gainsboro'] = '#dcdcdc';
$colors['ghostwhite'] = '#f8f8ff';
$colors['gold'] = '#ffd700';
$colors['goldenrod'] = '#daa520';
$colors['gray'] = '#808080';
$colors['grey'] = '#808080';
$colors['green'] = '#008000';
$colors['greenyellow'] = '#adff2f';
$colors['honeydew'] = '#f0fff0';
$colors['hotpink'] = '#ff69b4';
$colors['indianred'] = '#cd5c5c';
$colors['indigo'] = '#4b0082';
$colors['ivory'] = '#fffff0';
$colors['khaki'] = '#f0e68c';
$colors['lavender'] = '#e6e6fa';
$colors['lavenderblush'] = '#fff0f5';
$colors['lawngreen'] = '#7cfc00';
$colors['lemonchiffon'] = '#fffacd';
$colors['lightblue'] = '#add8e6';
$colors['lightcoral'] = '#f08080';
$colors['lightcyan'] = '#e0ffff';
$colors['lightgoldenrodyellow'] = '#fafad2';
$colors['lightgray'] = '#d3d3d3';
$colors['lightgrey'] = '#d3d3d3';
$colors['lightgreen'] = '#90ee90';
$colors['lightpink'] = '#ffb6c1';
$colors['lightsalmon'] = '#ffa07a';
$colors['lightseagreen'] = '#20b2aa';
$colors['lightskyblue'] = '#87cefa';
$colors['lightslategray'] = '#778899';
$colors['lightslategrey'] = '#778899';
$colors['lightsteelblue'] = '#b0c4de';
$colors['lightyellow'] = '#ffffe0';
$colors['lime'] = '#00ff00';
$colors['limegreen'] = '#32cd32';
$colors['linen'] = '#faf0e6';
$colors['magenta'] = '#ff00ff';
$colors['maroon'] = '#800000';
$colors['mediumaquamarine'] = '#66cdaa';
$colors['mediumblue'] = '#0000cd';
$colors['mediumorchid'] = '#ba55d3';
$colors['mediumpurple'] = '#9370db';
$colors['mediumseagreen'] = '#3cb371';
$colors['mediumslateblue'] = '#7b68ee';
$colors['mediumspringgreen'] = '#00fa9a';
$colors['mediumturquoise'] = '#48d1cc';
$colors['mediumvioletred'] = '#c71585';
$colors['midnightblue'] = '#191970';
$colors['mintcream'] = '#f5fffa';
$colors['mistyrose'] = '#ffe4e1';
$colors['moccasin'] = '#ffe4b5';
$colors['navajowhite'] = '#ffdead';
$colors['navy'] = '#000080';
$colors['oldlace'] = '#fdf5e6';
$colors['olive'] = '#808000';
$colors['olivedrab'] = '#6b8e23';
$colors['orange'] = '#ffa500';
$colors['orangered'] = '#ff4500';
$colors['orchid'] = '#da70d6';
$colors['palegoldenrod'] = '#eee8aa';
$colors['palegreen'] = '#98fb98';
$colors['paleturquoise'] = '#afeeee';
$colors['palevioletred'] = '#db7093';
$colors['papayawhip'] = '#ffefd5';
$colors['peachpuff'] = '#ffdab9';
$colors['peru'] = '#cd853f';
$colors['pink'] = '#ffc0cb';
$colors['plum'] = '#dda0dd';
$colors['powderblue'] = '#b0e0e6';
$colors['purple'] = '#800080';
$colors['rebeccapurple'] = '#663399';
$colors['red'] = '#ff0000';
$colors['rosybrown'] = '#bc8f8f';
$colors['royalblue'] = '#4169e1';
$colors['saddlebrown'] = '#8b4513';
$colors['salmon'] = '#fa8072';
$colors['sandybrown'] = '#f4a460';
$colors['seagreen'] = '#2e8b57';
$colors['seashell'] = '#fff5ee';
$colors['sienna'] = '#a0522d';
$colors['silver'] = '#c0c0c0';
$colors['skyblue'] = '#87ceeb';
$colors['slateblue'] = '#6a5acd';
$colors['slategray'] = '#708090';
$colors['slategrey'] = '#708090';
$colors['snow'] = '#fffafa';
$colors['springgreen'] = '#00ff7f';
$colors['steelblue'] = '#4682b4';
$colors['tan'] = '#d2b48c';
$colors['teal'] = '#008080';
$colors['thistle'] = '#d8bfd8';
$colors['tomato'] = '#ff6347';
$colors['turquoise'] = '#40e0d0';
$colors['violet'] = '#ee82ee';
$colors['wheat'] = '#f5deb3';
$colors['white'] = '#ffffff';
$colors['whitesmoke'] = '#f5f5f5';
$colors['yellow'] = '#ffff00';
$colors['yellowgreen'] = '#9acd32';

return $colors;
}

protected static function mapBorderColor(&$styles, $cssBorderColor): void
{
$numColors = substr_count($cssBorderColor, '#');
Expand Down
1 change: 1 addition & 0 deletions src/PhpWord/Writer/Word2007/Style/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ private function writeBorder(XMLWriter $xmlWriter, TableStyle $style): void
$styleWriter = new MarginBorder($xmlWriter);
$styleWriter->setSizes($style->getBorderSize());
$styleWriter->setColors($style->getBorderColor());
$styleWriter->setStyles($style->getBorderStyle());
$styleWriter->write();

$xmlWriter->endElement(); // w:tblBorders
Expand Down
72 changes: 72 additions & 0 deletions tests/PhpWordTests/Shared/HtmlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1415,4 +1415,76 @@ public function testParseRubyHtml(): void
$doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:ruby/w:rubyPr/w:lid', 'w:val')
);
}

public function testParseBorderOrderHtml(): void
{
$phpWord = new PhpWord();
$section = $phpWord->addSection();

$html = '<table>
<tbody>
<tr>
<td style="border:1px solid red;">border:1px solid red;</td>
<td style="border:1px green dotted;">border:1px green dotted;</td>
<td style="border:solid 2px #b8860b;">border:solid 2px #b8860b;</td>
<td style="border-left:solid blue 1px;">border-left:solid blue 1px;</td>
</tr>
</tbody>
</table>';
Html::addHtml($section, $html);
$doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');

// [1]
$path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcBorders/w:top';
self::assertTrue($doc->elementExists($path));
self::assertEquals('red', $doc->getElementAttribute($path, 'w:color'));
self::assertEquals(7, $doc->getElementAttribute($path, 'w:sz'));
self::assertEquals('single', $doc->getElementAttribute($path, 'w:val'));

$path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcBorders/w:left';
self::assertTrue($doc->elementExists($path));
self::assertEquals('red', $doc->getElementAttribute($path, 'w:color'));
self::assertEquals(7, $doc->getElementAttribute($path, 'w:sz'));
self::assertEquals('single', $doc->getElementAttribute($path, 'w:val'));

$path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcBorders/w:bottom';
self::assertTrue($doc->elementExists($path));
self::assertEquals('red', $doc->getElementAttribute($path, 'w:color'));
self::assertEquals(7, $doc->getElementAttribute($path, 'w:sz'));
self::assertEquals('single', $doc->getElementAttribute($path, 'w:val'));

$path = '/w:document/w:body/w:tbl/w:tr/w:tc[1]/w:tcPr/w:tcBorders/w:right';
self::assertTrue($doc->elementExists($path));
self::assertEquals('red', $doc->getElementAttribute($path, 'w:color'));
self::assertEquals(7, $doc->getElementAttribute($path, 'w:sz'));
self::assertEquals('single', $doc->getElementAttribute($path, 'w:val'));

// [2]
$path = '/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:tcPr/w:tcBorders/w:top';
self::assertTrue($doc->elementExists($path));
self::assertEquals('green', $doc->getElementAttribute($path, 'w:color'));
self::assertEquals(7, $doc->getElementAttribute($path, 'w:sz'));
self::assertEquals('dotted', $doc->getElementAttribute($path, 'w:val'));

// [3]
$path = '/w:document/w:body/w:tbl/w:tr/w:tc[3]/w:tcPr/w:tcBorders/w:top';
self::assertTrue($doc->elementExists($path));
self::assertEquals('b8860b', $doc->getElementAttribute($path, 'w:color'));
self::assertEquals(15, $doc->getElementAttribute($path, 'w:sz'));
self::assertEquals('single', $doc->getElementAttribute($path, 'w:val'));

// [4]
$path = '/w:document/w:body/w:tbl/w:tr/w:tc[4]/w:tcPr/w:tcBorders/w:left';
self::assertTrue($doc->elementExists($path));
self::assertEquals('blue', $doc->getElementAttribute($path, 'w:color'));
self::assertEquals(7, $doc->getElementAttribute($path, 'w:sz'));
self::assertEquals('single', $doc->getElementAttribute($path, 'w:val'));

$path = '/w:document/w:body/w:tbl/w:tr/w:tc[4]/w:tcPr/w:tcBorders/w:right';
self::assertNotTrue($doc->elementExists($path));
$path = '/w:document/w:body/w:tbl/w:tr/w:tc[4]/w:tcPr/w:tcBorders/w:top';
self::assertNotTrue($doc->elementExists($path));
$path = '/w:document/w:body/w:tbl/w:tr/w:tc[4]/w:tcPr/w:tcBorders/w:bottom';
self::assertNotTrue($doc->elementExists($path));
}
}