blob: 3e4f67a45fa7044e22bfb47e50a7fa3e37e9832b [file] [log] [blame]
// Copyright 2014 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
#include "core/fxcrt/css/cfx_cssdeclaration.h"
#include <math.h>
#include <array>
#include <utility>
#include "core/fxcrt/check.h"
#include "core/fxcrt/check_op.h"
#include "core/fxcrt/css/cfx_csscolorvalue.h"
#include "core/fxcrt/css/cfx_csscustomproperty.h"
#include "core/fxcrt/css/cfx_cssenumvalue.h"
#include "core/fxcrt/css/cfx_cssnumbervalue.h"
#include "core/fxcrt/css/cfx_csspropertyholder.h"
#include "core/fxcrt/css/cfx_cssstringvalue.h"
#include "core/fxcrt/css/cfx_cssvaluelist.h"
#include "core/fxcrt/css/cfx_cssvaluelistparser.h"
#include "core/fxcrt/fx_extension.h"
#include "core/fxcrt/fx_system.h"
#include "core/fxcrt/notreached.h"
namespace {
uint8_t Hex2Dec(uint8_t hexHigh, uint8_t hexLow) {
return (FXSYS_HexCharToInt(hexHigh) << 4) + FXSYS_HexCharToInt(hexLow);
}
std::optional<CFX_CSSNumber> ParseCSSNumber(WideStringView view) {
DCHECK(!view.IsEmpty());
size_t nUsedLen = 0;
float value =
FXSYS_wcstof(view.unterminated_c_str(), view.GetLength(), &nUsedLen);
if (nUsedLen == 0 || !isfinite(value)) {
return std::nullopt;
}
view = view.Substr(nUsedLen);
if (view.Front() == '%') { // NOTE: empty-tolerant Front().
return CFX_CSSNumber{CFX_CSSNumber::Unit::kPercent, value};
}
if (view.GetLength() == 2) {
const CFX_CSSData::LengthUnit* pUnit =
CFX_CSSData::GetLengthUnitByName(view.First(2));
if (pUnit) {
return CFX_CSSNumber{pUnit->type, value};
}
}
return CFX_CSSNumber{CFX_CSSNumber::Unit::kNumber, value};
}
} // namespace
// static
std::optional<WideStringView> CFX_CSSDeclaration::ParseCSSString(
WideStringView value) {
if (value.GetLength() >= 2) {
wchar_t first = value.Front();
wchar_t last = value.Back();
if ((first == '\"' && last == '\"') || (first == '\'' && last == '\'')) {
value = value.Substr(1, value.GetLength() - 2);
}
}
if (value.IsEmpty()) {
return std::nullopt;
}
return value;
}
// static.
std::optional<FX_ARGB> CFX_CSSDeclaration::ParseCSSColor(WideStringView value) {
if (value.Front() == '#') { // Note: empty-tolerant Front().
switch (value.GetLength()) {
case 4: {
uint8_t red = Hex2Dec((uint8_t)value[1], (uint8_t)value[1]);
uint8_t green = Hex2Dec((uint8_t)value[2], (uint8_t)value[2]);
uint8_t blue = Hex2Dec((uint8_t)value[3], (uint8_t)value[3]);
return ArgbEncode(255, red, green, blue);
}
case 7: {
uint8_t red = Hex2Dec((uint8_t)value[1], (uint8_t)value[2]);
uint8_t green = Hex2Dec((uint8_t)value[3], (uint8_t)value[4]);
uint8_t blue = Hex2Dec((uint8_t)value[5], (uint8_t)value[6]);
return ArgbEncode(255, red, green, blue);
}
default:
return std::nullopt;
}
}
if (value.GetLength() >= 10) {
if (!value.First(4).EqualsASCIINoCase("rgb(") || value.Back() != ')') {
return std::nullopt;
}
std::array<uint8_t, 3> rgb = {};
CFX_CSSValueListParser list(value.Substr(4, value.GetLength() - 5), ',');
for (auto& component : rgb) {
auto maybe_value = list.NextValue();
if (!maybe_value.has_value() ||
maybe_value.value().type != CFX_CSSValue::PrimitiveType::kNumber) {
return std::nullopt;
}
auto maybe_number = ParseCSSNumber(maybe_value.value().string_view);
if (!maybe_number.has_value()) {
return std::nullopt;
}
component = maybe_number.value().unit == CFX_CSSNumber::Unit::kPercent
? FXSYS_roundf(maybe_number.value().value * 2.55f)
: FXSYS_roundf(maybe_number.value().value);
}
return ArgbEncode(255, rgb[0], rgb[1], rgb[2]);
}
const CFX_CSSData::Color* pColor = CFX_CSSData::GetColorByName(value);
if (!pColor) {
return std::nullopt;
}
return pColor->value;
}
CFX_CSSDeclaration::CFX_CSSDeclaration() = default;
CFX_CSSDeclaration::~CFX_CSSDeclaration() = default;
RetainPtr<CFX_CSSValue> CFX_CSSDeclaration::GetProperty(
CFX_CSSProperty eProperty,
bool* bImportant) const {
for (const auto& p : properties_) {
if (p->eProperty == eProperty) {
*bImportant = p->bImportant;
return p->pValue;
}
}
return nullptr;
}
void CFX_CSSDeclaration::AddPropertyHolder(CFX_CSSProperty eProperty,
RetainPtr<CFX_CSSValue> pValue,
bool bImportant) {
auto pHolder = std::make_unique<CFX_CSSPropertyHolder>();
pHolder->bImportant = bImportant;
pHolder->eProperty = eProperty;
pHolder->pValue = std::move(pValue);
properties_.push_back(std::move(pHolder));
}
void CFX_CSSDeclaration::AddProperty(const CFX_CSSData::Property* property,
WideStringView value) {
DCHECK(!value.IsEmpty());
bool bImportant = false;
WideStringView last_ten = value.Last(10); // NOTE: empty-tolerant Last().
if (last_ten.EqualsASCIINoCase("!important")) {
value = value.First(value.GetLength() - 10);
if (value.IsEmpty()) {
return;
}
bImportant = true;
}
const CFX_CSSValueTypeMask dwType = property->dwTypes;
switch (dwType & 0x0F) {
case CFX_CSSVALUETYPE_Primitive: {
static constexpr CFX_CSSVALUETYPE kValueGuessOrder[] = {
CFX_CSSVALUETYPE_MaybeNumber,
CFX_CSSVALUETYPE_MaybeEnum,
CFX_CSSVALUETYPE_MaybeColor,
CFX_CSSVALUETYPE_MaybeString,
};
for (CFX_CSSVALUETYPE guess : kValueGuessOrder) {
const CFX_CSSValueTypeMask dwMatch = dwType & guess;
if (dwMatch == 0) {
continue;
}
RetainPtr<CFX_CSSValue> pCSSValue;
switch (dwMatch) {
case CFX_CSSVALUETYPE_MaybeNumber:
pCSSValue = ParseNumber(value);
break;
case CFX_CSSVALUETYPE_MaybeEnum:
pCSSValue = ParseEnum(value);
break;
case CFX_CSSVALUETYPE_MaybeColor:
pCSSValue = ParseColor(value);
break;
case CFX_CSSVALUETYPE_MaybeString:
pCSSValue = ParseString(value);
break;
default:
break;
}
if (pCSSValue) {
AddPropertyHolder(property->eName, pCSSValue, bImportant);
return;
}
if ((dwType & ~guess) == CFX_CSSVALUETYPE_Primitive) {
return;
}
}
break;
}
case CFX_CSSVALUETYPE_Shorthand: {
switch (property->eName) {
case CFX_CSSProperty::Font: {
ParseFontProperty(value, bImportant);
return;
}
case CFX_CSSProperty::Border: {
RetainPtr<CFX_CSSValue> pWidth = ParseBorderProperty(value);
AddPropertyHolder(CFX_CSSProperty::BorderLeftWidth, pWidth,
bImportant);
AddPropertyHolder(CFX_CSSProperty::BorderTopWidth, pWidth,
bImportant);
AddPropertyHolder(CFX_CSSProperty::BorderRightWidth, pWidth,
bImportant);
AddPropertyHolder(CFX_CSSProperty::BorderBottomWidth, pWidth,
bImportant);
return;
}
case CFX_CSSProperty::BorderLeft: {
AddPropertyHolder(CFX_CSSProperty::BorderLeftWidth,
ParseBorderProperty(value), bImportant);
break;
}
case CFX_CSSProperty::BorderTop: {
AddPropertyHolder(CFX_CSSProperty::BorderTopWidth,
ParseBorderProperty(value), bImportant);
return;
}
case CFX_CSSProperty::BorderRight: {
AddPropertyHolder(CFX_CSSProperty::BorderRightWidth,
ParseBorderProperty(value), bImportant);
return;
}
case CFX_CSSProperty::BorderBottom: {
AddPropertyHolder(CFX_CSSProperty::BorderBottomWidth,
ParseBorderProperty(value), bImportant);
return;
}
default:
break;
}
break;
}
case CFX_CSSVALUETYPE_List:
ParseValueListProperty(property, value, bImportant);
return;
default:
NOTREACHED_NORETURN();
}
}
void CFX_CSSDeclaration::AddProperty(const WideString& prop,
const WideString& value) {
custom_properties_.push_back(
std::make_unique<CFX_CSSCustomProperty>(prop, value));
}
RetainPtr<CFX_CSSValue> CFX_CSSDeclaration::ParseNumber(WideStringView view) {
std::optional<CFX_CSSNumber> maybe_number = ParseCSSNumber(view);
if (!maybe_number.has_value()) {
return nullptr;
}
return pdfium::MakeRetain<CFX_CSSNumberValue>(maybe_number.value());
}
RetainPtr<CFX_CSSValue> CFX_CSSDeclaration::ParseEnum(WideStringView value) {
const CFX_CSSData::PropertyValue* pValue =
CFX_CSSData::GetPropertyValueByName(value);
return pValue ? pdfium::MakeRetain<CFX_CSSEnumValue>(pValue->eName) : nullptr;
}
RetainPtr<CFX_CSSValue> CFX_CSSDeclaration::ParseColor(WideStringView value) {
auto maybe_color = ParseCSSColor(value);
if (!maybe_color.has_value()) {
return nullptr;
}
return pdfium::MakeRetain<CFX_CSSColorValue>(maybe_color.value());
}
RetainPtr<CFX_CSSValue> CFX_CSSDeclaration::ParseString(WideStringView value) {
auto maybe_string = ParseCSSString(value);
if (!maybe_string.has_value() || maybe_string.value().IsEmpty()) {
return nullptr;
}
return pdfium::MakeRetain<CFX_CSSStringValue>(maybe_string.value());
}
void CFX_CSSDeclaration::ParseValueListProperty(
const CFX_CSSData::Property* pProperty,
WideStringView value,
bool bImportant) {
wchar_t separator =
(pProperty->eName == CFX_CSSProperty::FontFamily) ? ',' : ' ';
CFX_CSSValueListParser parser(value, separator);
const CFX_CSSValueTypeMask dwType = pProperty->dwTypes;
std::vector<RetainPtr<CFX_CSSValue>> list;
while (1) {
auto maybe_next = parser.NextValue();
if (!maybe_next.has_value()) {
break;
}
switch (maybe_next.value().type) {
case CFX_CSSValue::PrimitiveType::kNumber:
if (dwType & CFX_CSSVALUETYPE_MaybeNumber) {
auto maybe_number = ParseCSSNumber(maybe_next.value().string_view);
if (maybe_number.has_value()) {
list.push_back(
pdfium::MakeRetain<CFX_CSSNumberValue>(maybe_number.value()));
}
}
break;
case CFX_CSSValue::PrimitiveType::kString:
if (dwType & CFX_CSSVALUETYPE_MaybeColor) {
auto maybe_color = ParseCSSColor(maybe_next.value().string_view);
if (maybe_color.has_value()) {
list.push_back(
pdfium::MakeRetain<CFX_CSSColorValue>(maybe_color.value()));
continue;
}
}
if (dwType & CFX_CSSVALUETYPE_MaybeEnum) {
const CFX_CSSData::PropertyValue* pPropValue =
CFX_CSSData::GetPropertyValueByName(
maybe_next.value().string_view);
if (pPropValue) {
list.push_back(
pdfium::MakeRetain<CFX_CSSEnumValue>(pPropValue->eName));
continue;
}
}
if (dwType & CFX_CSSVALUETYPE_MaybeString) {
list.push_back(pdfium::MakeRetain<CFX_CSSStringValue>(
maybe_next.value().string_view));
}
break;
case CFX_CSSValue::PrimitiveType::kRGB:
if (dwType & CFX_CSSVALUETYPE_MaybeColor) {
FX_ARGB color =
ParseCSSColor(maybe_next.value().string_view).value_or(0);
list.push_back(pdfium::MakeRetain<CFX_CSSColorValue>(color));
}
break;
default:
break;
}
}
if (list.empty()) {
return;
}
switch (pProperty->eName) {
case CFX_CSSProperty::BorderWidth:
Add4ValuesProperty(list, bImportant, CFX_CSSProperty::BorderLeftWidth,
CFX_CSSProperty::BorderTopWidth,
CFX_CSSProperty::BorderRightWidth,
CFX_CSSProperty::BorderBottomWidth);
return;
case CFX_CSSProperty::Margin:
Add4ValuesProperty(list, bImportant, CFX_CSSProperty::MarginLeft,
CFX_CSSProperty::MarginTop,
CFX_CSSProperty::MarginRight,
CFX_CSSProperty::MarginBottom);
return;
case CFX_CSSProperty::Padding:
Add4ValuesProperty(list, bImportant, CFX_CSSProperty::PaddingLeft,
CFX_CSSProperty::PaddingTop,
CFX_CSSProperty::PaddingRight,
CFX_CSSProperty::PaddingBottom);
return;
default: {
auto value_list = pdfium::MakeRetain<CFX_CSSValueList>(std::move(list));
AddPropertyHolder(pProperty->eName, std::move(value_list), bImportant);
return;
}
}
}
void CFX_CSSDeclaration::Add4ValuesProperty(
const std::vector<RetainPtr<CFX_CSSValue>>& list,
bool bImportant,
CFX_CSSProperty eLeft,
CFX_CSSProperty eTop,
CFX_CSSProperty eRight,
CFX_CSSProperty eBottom) {
switch (list.size()) {
case 1:
AddPropertyHolder(eLeft, list[0], bImportant);
AddPropertyHolder(eTop, list[0], bImportant);
AddPropertyHolder(eRight, list[0], bImportant);
AddPropertyHolder(eBottom, list[0], bImportant);
return;
case 2:
AddPropertyHolder(eLeft, list[1], bImportant);
AddPropertyHolder(eTop, list[0], bImportant);
AddPropertyHolder(eRight, list[1], bImportant);
AddPropertyHolder(eBottom, list[0], bImportant);
return;
case 3:
AddPropertyHolder(eLeft, list[1], bImportant);
AddPropertyHolder(eTop, list[0], bImportant);
AddPropertyHolder(eRight, list[1], bImportant);
AddPropertyHolder(eBottom, list[2], bImportant);
return;
case 4:
AddPropertyHolder(eLeft, list[3], bImportant);
AddPropertyHolder(eTop, list[0], bImportant);
AddPropertyHolder(eRight, list[1], bImportant);
AddPropertyHolder(eBottom, list[2], bImportant);
return;
default:
break;
}
}
RetainPtr<CFX_CSSValue> CFX_CSSDeclaration::ParseBorderProperty(
WideStringView value) const {
RetainPtr<CFX_CSSValue> pWidth;
CFX_CSSValueListParser parser(value, ' ');
while (1) {
auto maybe_next = parser.NextValue();
if (!maybe_next.has_value()) {
break;
}
switch (maybe_next.value().type) {
case CFX_CSSValue::PrimitiveType::kNumber: {
if (pWidth) {
continue;
}
auto maybe_number = ParseCSSNumber(maybe_next.value().string_view);
if (maybe_number.has_value()) {
pWidth = pdfium::MakeRetain<CFX_CSSNumberValue>(maybe_number.value());
}
break;
}
case CFX_CSSValue::PrimitiveType::kString: {
const CFX_CSSData::Color* pColorItem =
CFX_CSSData::GetColorByName(maybe_next.value().string_view);
if (pColorItem) {
continue;
}
const CFX_CSSData::PropertyValue* pValue =
CFX_CSSData::GetPropertyValueByName(maybe_next.value().string_view);
if (!pValue) {
continue;
}
switch (pValue->eName) {
case CFX_CSSPropertyValue::Thin:
case CFX_CSSPropertyValue::Thick:
case CFX_CSSPropertyValue::Medium:
if (!pWidth)
pWidth = pdfium::MakeRetain<CFX_CSSEnumValue>(pValue->eName);
break;
default:
break;
}
break;
}
default:
break;
}
}
if (pWidth) {
return pWidth;
}
return pdfium::MakeRetain<CFX_CSSNumberValue>(
CFX_CSSNumber{CFX_CSSNumber::Unit::kNumber, 0.0f});
}
void CFX_CSSDeclaration::ParseFontProperty(WideStringView value,
bool bImportant) {
RetainPtr<CFX_CSSValue> pStyle;
RetainPtr<CFX_CSSValue> pVariant;
RetainPtr<CFX_CSSValue> pWeight;
RetainPtr<CFX_CSSValue> pFontSize;
RetainPtr<CFX_CSSValue> pLineHeight;
std::vector<RetainPtr<CFX_CSSValue>> family_list;
CFX_CSSValueListParser parser(value, '/');
while (1) {
auto maybe_next = parser.NextValue();
if (!maybe_next.has_value()) {
break;
}
switch (maybe_next.value().type) {
case CFX_CSSValue::PrimitiveType::kString: {
const CFX_CSSData::PropertyValue* pValue =
CFX_CSSData::GetPropertyValueByName(maybe_next.value().string_view);
if (pValue) {
switch (pValue->eName) {
case CFX_CSSPropertyValue::XxSmall:
case CFX_CSSPropertyValue::XSmall:
case CFX_CSSPropertyValue::Small:
case CFX_CSSPropertyValue::Medium:
case CFX_CSSPropertyValue::Large:
case CFX_CSSPropertyValue::XLarge:
case CFX_CSSPropertyValue::XxLarge:
case CFX_CSSPropertyValue::Smaller:
case CFX_CSSPropertyValue::Larger:
if (!pFontSize)
pFontSize = pdfium::MakeRetain<CFX_CSSEnumValue>(pValue->eName);
continue;
case CFX_CSSPropertyValue::Bold:
case CFX_CSSPropertyValue::Bolder:
case CFX_CSSPropertyValue::Lighter:
if (!pWeight)
pWeight = pdfium::MakeRetain<CFX_CSSEnumValue>(pValue->eName);
continue;
case CFX_CSSPropertyValue::Italic:
case CFX_CSSPropertyValue::Oblique:
if (!pStyle)
pStyle = pdfium::MakeRetain<CFX_CSSEnumValue>(pValue->eName);
continue;
case CFX_CSSPropertyValue::SmallCaps:
if (!pVariant)
pVariant = pdfium::MakeRetain<CFX_CSSEnumValue>(pValue->eName);
continue;
case CFX_CSSPropertyValue::Normal:
if (!pStyle)
pStyle = pdfium::MakeRetain<CFX_CSSEnumValue>(pValue->eName);
else if (!pVariant)
pVariant = pdfium::MakeRetain<CFX_CSSEnumValue>(pValue->eName);
else if (!pWeight)
pWeight = pdfium::MakeRetain<CFX_CSSEnumValue>(pValue->eName);
else if (!pFontSize)
pFontSize = pdfium::MakeRetain<CFX_CSSEnumValue>(pValue->eName);
else if (!pLineHeight)
pLineHeight =
pdfium::MakeRetain<CFX_CSSEnumValue>(pValue->eName);
continue;
default:
break;
}
}
if (pFontSize) {
family_list.push_back(pdfium::MakeRetain<CFX_CSSStringValue>(
maybe_next.value().string_view));
}
parser.UseCommaSeparator();
break;
}
case CFX_CSSValue::PrimitiveType::kNumber: {
auto maybe_number = ParseCSSNumber(maybe_next.value().string_view);
if (!maybe_number.has_value()) {
break;
}
if (maybe_number.value().unit == CFX_CSSNumber::Unit::kNumber) {
switch (static_cast<int32_t>(maybe_number.value().value)) {
case 100:
case 200:
case 300:
case 400:
case 500:
case 600:
case 700:
case 800:
case 900:
if (!pWeight) {
pWeight = pdfium::MakeRetain<CFX_CSSNumberValue>(
maybe_number.value());
}
continue;
}
}
if (!pFontSize) {
pFontSize =
pdfium::MakeRetain<CFX_CSSNumberValue>(maybe_number.value());
} else if (!pLineHeight) {
pLineHeight =
pdfium::MakeRetain<CFX_CSSNumberValue>(maybe_number.value());
}
break;
}
default:
break;
}
}
if (!pStyle) {
pStyle = pdfium::MakeRetain<CFX_CSSEnumValue>(CFX_CSSPropertyValue::Normal);
}
if (!pVariant) {
pVariant =
pdfium::MakeRetain<CFX_CSSEnumValue>(CFX_CSSPropertyValue::Normal);
}
if (!pWeight) {
pWeight =
pdfium::MakeRetain<CFX_CSSEnumValue>(CFX_CSSPropertyValue::Normal);
}
if (!pFontSize) {
pFontSize =
pdfium::MakeRetain<CFX_CSSEnumValue>(CFX_CSSPropertyValue::Medium);
}
if (!pLineHeight) {
pLineHeight =
pdfium::MakeRetain<CFX_CSSEnumValue>(CFX_CSSPropertyValue::Normal);
}
AddPropertyHolder(CFX_CSSProperty::FontStyle, pStyle, bImportant);
AddPropertyHolder(CFX_CSSProperty::FontVariant, pVariant, bImportant);
AddPropertyHolder(CFX_CSSProperty::FontWeight, pWeight, bImportant);
AddPropertyHolder(CFX_CSSProperty::FontSize, pFontSize, bImportant);
AddPropertyHolder(CFX_CSSProperty::LineHeight, pLineHeight, bImportant);
if (!family_list.empty()) {
auto value_list =
pdfium::MakeRetain<CFX_CSSValueList>(std::move(family_list));
AddPropertyHolder(CFX_CSSProperty::FontFamily, value_list, bImportant);
}
}
size_t CFX_CSSDeclaration::PropertyCountForTesting() const {
return properties_.size();
}