// 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 #include #include #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.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 ParseCSSNumber(WideStringView view) { DCHECK(!view.IsEmpty()); size_t nUsedLen = 0; float value = FXSYS_wcstof(view, &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 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 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 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_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 pValue, bool bImportant) { auto pHolder = std::make_unique(); 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 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 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(prop, value)); } RetainPtr CFX_CSSDeclaration::ParseNumber(WideStringView view) { std::optional maybe_number = ParseCSSNumber(view); if (!maybe_number.has_value()) { return nullptr; } return pdfium::MakeRetain(maybe_number.value()); } RetainPtr CFX_CSSDeclaration::ParseEnum(WideStringView value) { const CFX_CSSData::PropertyValue* pValue = CFX_CSSData::GetPropertyValueByName(value); return pValue ? pdfium::MakeRetain(pValue->eName) : nullptr; } RetainPtr CFX_CSSDeclaration::ParseColor(WideStringView value) { auto maybe_color = ParseCSSColor(value); if (!maybe_color.has_value()) { return nullptr; } return pdfium::MakeRetain(maybe_color.value()); } RetainPtr CFX_CSSDeclaration::ParseString(WideStringView value) { auto maybe_string = ParseCSSString(value); if (!maybe_string.has_value() || maybe_string.value().IsEmpty()) { return nullptr; } return pdfium::MakeRetain(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> 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(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(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(pPropValue->eName)); continue; } } if (dwType & CFX_CSSVALUETYPE_MaybeString) { list.push_back(pdfium::MakeRetain( 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(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(std::move(list)); AddPropertyHolder(pProperty->eName, std::move(value_list), bImportant); return; } } } void CFX_CSSDeclaration::Add4ValuesProperty( const std::vector>& 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_CSSDeclaration::ParseBorderProperty( WideStringView value) const { RetainPtr 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(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(pValue->eName); break; default: break; } break; } default: break; } } if (pWidth) { return pWidth; } return pdfium::MakeRetain( CFX_CSSNumber{CFX_CSSNumber::Unit::kNumber, 0.0f}); } void CFX_CSSDeclaration::ParseFontProperty(WideStringView value, bool bImportant) { RetainPtr pStyle; RetainPtr pVariant; RetainPtr pWeight; RetainPtr pFontSize; RetainPtr pLineHeight; std::vector> 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(pValue->eName); continue; case CFX_CSSPropertyValue::Bold: case CFX_CSSPropertyValue::Bolder: case CFX_CSSPropertyValue::Lighter: if (!pWeight) pWeight = pdfium::MakeRetain(pValue->eName); continue; case CFX_CSSPropertyValue::Italic: case CFX_CSSPropertyValue::Oblique: if (!pStyle) pStyle = pdfium::MakeRetain(pValue->eName); continue; case CFX_CSSPropertyValue::SmallCaps: if (!pVariant) pVariant = pdfium::MakeRetain(pValue->eName); continue; case CFX_CSSPropertyValue::Normal: if (!pStyle) pStyle = pdfium::MakeRetain(pValue->eName); else if (!pVariant) pVariant = pdfium::MakeRetain(pValue->eName); else if (!pWeight) pWeight = pdfium::MakeRetain(pValue->eName); else if (!pFontSize) pFontSize = pdfium::MakeRetain(pValue->eName); else if (!pLineHeight) pLineHeight = pdfium::MakeRetain(pValue->eName); continue; default: break; } } if (pFontSize) { family_list.push_back(pdfium::MakeRetain( 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(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( maybe_number.value()); } continue; } } if (!pFontSize) { pFontSize = pdfium::MakeRetain(maybe_number.value()); } else if (!pLineHeight) { pLineHeight = pdfium::MakeRetain(maybe_number.value()); } break; } default: break; } } if (!pStyle) { pStyle = pdfium::MakeRetain(CFX_CSSPropertyValue::Normal); } if (!pVariant) { pVariant = pdfium::MakeRetain(CFX_CSSPropertyValue::Normal); } if (!pWeight) { pWeight = pdfium::MakeRetain(CFX_CSSPropertyValue::Normal); } if (!pFontSize) { pFontSize = pdfium::MakeRetain(CFX_CSSPropertyValue::Medium); } if (!pLineHeight) { pLineHeight = pdfium::MakeRetain(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(std::move(family_list)); AddPropertyHolder(CFX_CSSProperty::FontFamily, value_list, bImportant); } } size_t CFX_CSSDeclaration::PropertyCountForTesting() const { return properties_.size(); }