Bug 1541959 - Allow full-width and/or full-size-kana values of text-transform to be combined with a case transformation. r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D27402

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jonathan Kew 2019-04-14 11:06:41 +00:00
Родитель fa3b85a4da
Коммит 504abab69a
16 изменённых файлов: 238 добавлений и 55 удалений

Просмотреть файл

@ -2141,7 +2141,7 @@ already_AddRefed<gfxTextRun> BuildTextRunsScanner::BuildTextRunForFrames(
lastComputedStyle = f->Style();
// Detect use of text-transform or font-variant anywhere in the run
textStyle = f->StyleText();
if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform ||
if (!textStyle->mTextTransform.IsNone() ||
// text-combine-upright requires converting from full-width
// characters to non-full-width correspendent in some cases.
lastComputedStyle->IsTextCombined()) {
@ -2633,7 +2633,7 @@ void BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_NO_BREAKS) {
flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
}
if (textStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) {
if (textStyle->mTextTransform.case_ == StyleTextTransformCase::Capitalize) {
flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
}
if (textStyle->mHyphens == StyleHyphens::Auto) {
@ -9400,7 +9400,7 @@ static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle,
int32_t aFragLen, nsAString& aOut) {
nsAutoString fragString;
char16_t* out;
if (aStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_NONE) {
if (aStyle->mTextTransform.IsNone()) {
// No text-transform, so we can copy directly to the output string.
aOut.SetLength(aOut.Length() + aFragLen);
out = aOut.EndWriting() - aFragLen;
@ -9420,7 +9420,7 @@ static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle,
out[i] = ch;
}
if (aStyle->mTextTransform != NS_STYLE_TEXT_TRANSFORM_NONE) {
if (!aStyle->mTextTransform.IsNone()) {
MOZ_ASSERT(aTextRun->GetFlags2() &
nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED);
if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED) {

Просмотреть файл

@ -280,7 +280,10 @@ bool nsCaseTransformTextRunFactory::TransformString(
uint32_t sigmaIndex = uint32_t(-1);
nsUGenCategory cat;
uint8_t style = aAllUppercase ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE : 0;
StyleTextTransform style =
aAllUppercase ? StyleTextTransform{StyleTextTransformCase::Uppercase,
StyleTextTransformOther()}
: StyleTextTransform::None();
bool forceNonFullWidth = false;
const nsAtom* lang = aLanguage;
@ -302,8 +305,10 @@ bool nsCaseTransformTextRunFactory::TransformString(
RefPtr<nsTransformedCharStyle> charStyle;
if (aTextRun) {
charStyle = aTextRun->mStyles[aOffsetInTextRun];
style = aAllUppercase ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE
: charStyle->mTextTransform;
style = aAllUppercase
? StyleTextTransform{StyleTextTransformCase::Uppercase,
StyleTextTransformOther()}
: charStyle->mTextTransform;
forceNonFullWidth = charStyle->mForceNonFullWidth;
nsAtom* newLang =
@ -328,8 +333,11 @@ bool nsCaseTransformTextRunFactory::TransformString(
ch = SURROGATE_TO_UCS4(ch, str[i + 1]);
}
switch (style) {
case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
switch (style.case_) {
case StyleTextTransformCase::None:
break;
case StyleTextTransformCase::Lowercase:
if (languageSpecificCasing == eLSCB_Turkish) {
if (ch == 'I') {
ch = LATIN_SMALL_LETTER_DOTLESS_I;
@ -430,7 +438,7 @@ bool nsCaseTransformTextRunFactory::TransformString(
ch = ToLowerCase(ch);
break;
case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
case StyleTextTransformCase::Uppercase:
if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') {
ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE;
break;
@ -538,7 +546,7 @@ bool nsCaseTransformTextRunFactory::TransformString(
}
break;
case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
case StyleTextTransformCase::Capitalize:
if (aTextRun) {
if (capitalizeDutchIJ && ch == 'j') {
ch = 'J';
@ -575,13 +583,18 @@ bool nsCaseTransformTextRunFactory::TransformString(
}
break;
case NS_STYLE_TEXT_TRANSFORM_FULL_WIDTH:
if (!aCaseTransformsOnly) {
ch = mozilla::unicode::GetFullWidth(ch);
}
default:
MOZ_ASSERT_UNREACHABLE("all cases should be handled");
break;
}
case NS_STYLE_TEXT_TRANSFORM_FULL_SIZE_KANA: {
if (!aCaseTransformsOnly) {
if (!forceNonFullWidth &&
(style.other_ & StyleTextTransformOther_FULL_WIDTH)) {
ch = mozilla::unicode::GetFullWidth(ch);
}
if (style.other_ & StyleTextTransformOther_FULL_SIZE_KANA) {
// clang-format off
static const uint16_t kSmallKanas[] = {
// ぁ ぃ ぅ ぇ ぉ っ ゃ ゅ ょ
@ -615,18 +628,12 @@ bool nsCaseTransformTextRunFactory::TransformString(
0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF94, 0xFF95, 0xFF96, 0xFF82};
// clang-format on
if (!aCaseTransformsOnly) {
size_t index;
const uint16_t len = MOZ_ARRAY_LENGTH(kSmallKanas);
if (mozilla::BinarySearch(kSmallKanas, 0, len, ch, &index)) {
ch = kFullSizeKanas[index];
}
size_t index;
const uint16_t len = MOZ_ARRAY_LENGTH(kSmallKanas);
if (mozilla::BinarySearch(kSmallKanas, 0, len, ch, &index)) {
ch = kFullSizeKanas[index];
}
break;
}
default:
break;
}
if (forceNonFullWidth) {

Просмотреть файл

@ -34,7 +34,7 @@ struct nsTransformedCharStyle final {
RefPtr<nsAtom> mLanguage;
RefPtr<nsPresContext> mPresContext;
float mScriptSizeMultiplier;
uint8_t mTextTransform;
mozilla::StyleTextTransform mTextTransform;
uint8_t mMathVariant;
bool mExplicitLanguage;
bool mForceNonFullWidth = false;

Просмотреть файл

@ -459,6 +459,7 @@ cbindgen-types = [
{ gecko = "StyleTouchAction", servo = "values::computed::TouchAction" },
{ gecko = "StyleWillChangeBits", servo = "values::specified::box_::WillChangeBits" },
{ gecko = "StyleTextDecorationLine", servo = "values::computed::TextDecorationLine" },
{ gecko = "StyleTextTransform", servo = "values::computed::TextTransform" },
{ gecko = "StyleMozListReversed", servo = "values::computed::MozListReversed" },
{ gecko = "StyleOwned", servo = "gecko_bindings::sugar::ownership::Owned" },
{ gecko = "StyleOwnedOrNull", servo = "gecko_bindings::sugar::ownership::OwnedOrNull" },

Просмотреть файл

@ -3744,10 +3744,10 @@ static bool AreShadowArraysEqual(nsCSSShadowArray* lhs, nsCSSShadowArray* rhs) {
//
nsStyleText::nsStyleText(const Document& aDocument)
: mTextAlign(NS_STYLE_TEXT_ALIGN_START),
: mTextTransform(StyleTextTransform::None()),
mTextAlign(NS_STYLE_TEXT_ALIGN_START),
mTextAlignLast(NS_STYLE_TEXT_ALIGN_AUTO),
mTextJustify(StyleTextJustify::Auto),
mTextTransform(NS_STYLE_TEXT_TRANSFORM_NONE),
mWhiteSpace(StyleWhiteSpace::Normal),
mHyphens(StyleHyphens::Manual),
mRubyAlign(NS_STYLE_RUBY_ALIGN_SPACE_AROUND),
@ -3778,10 +3778,10 @@ nsStyleText::nsStyleText(const Document& aDocument)
}
nsStyleText::nsStyleText(const nsStyleText& aSource)
: mTextAlign(aSource.mTextAlign),
: mTextTransform(aSource.mTextTransform),
mTextAlign(aSource.mTextAlign),
mTextAlignLast(aSource.mTextAlignLast),
mTextJustify(aSource.mTextJustify),
mTextTransform(aSource.mTextTransform),
mWhiteSpace(aSource.mWhiteSpace),
mWordBreak(aSource.mWordBreak),
mOverflowWrap(aSource.mOverflowWrap),

Просмотреть файл

@ -1461,10 +1461,10 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleText {
nsChangeHint CalcDifference(const nsStyleText& aNewData) const;
mozilla::StyleTextTransform mTextTransform;
uint8_t mTextAlign; // NS_STYLE_TEXT_ALIGN_*
uint8_t mTextAlignLast; // NS_STYLE_TEXT_ALIGN_*
mozilla::StyleTextJustify mTextJustify;
uint8_t mTextTransform; // NS_STYLE_TEXT_TRANSFORM_*
mozilla::StyleWhiteSpace mWhiteSpace;
private:
@ -1596,6 +1596,13 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleVisibility {
namespace mozilla {
inline StyleTextTransform StyleTextTransform::None() {
return StyleTextTransform{StyleTextTransformCase::None,
StyleTextTransformOther()};
}
inline bool StyleTextTransform::IsNone() const { return *this == None(); }
struct StyleTransition {
StyleTransition() { /* leaves uninitialized; see also SetInitialValues */
}

Просмотреть файл

@ -4843,8 +4843,10 @@ var gCSSProperties = {
applies_to_first_line: true,
applies_to_placeholder: true,
initial_values: [ "none" ],
other_values: [ "capitalize", "uppercase", "lowercase", "full-width", "full-size-kana" ],
invalid_values: []
other_values: [ "capitalize", "uppercase", "lowercase", "full-width", "full-size-kana",
"uppercase full-width", "full-size-kana capitalize", "full-width lowercase full-size-kana" ],
invalid_values: [ "none none", "none uppercase", "full-width none", "uppercase lowercase",
"full-width capitalize full-width", "uppercase full-width lowercase" ]
},
"top": {
domProp: "top",

Просмотреть файл

@ -879,15 +879,15 @@ void nsTextBoxFrame::RecomputeTitle() {
// This doesn't handle language-specific uppercasing/lowercasing
// rules, unlike textruns.
uint8_t textTransform = StyleText()->mTextTransform;
if (textTransform == NS_STYLE_TEXT_TRANSFORM_UPPERCASE) {
StyleTextTransform textTransform = StyleText()->mTextTransform;
if (textTransform.case_ == StyleTextTransformCase::Uppercase) {
ToUpperCase(mTitle);
} else if (textTransform == NS_STYLE_TEXT_TRANSFORM_LOWERCASE) {
} else if (textTransform.case_ == StyleTextTransformCase::Lowercase) {
ToLowerCase(mTitle);
}
// We can't handle NS_STYLE_TEXT_TRANSFORM_CAPITALIZE because we
// We can't handle StyleTextTransformCase::Capitalize because we
// have no clue about word boundaries here. We also don't handle
// NS_STYLE_TEXT_TRANSFORM_FULL_WIDTH.
// the full-width or full-size-kana transforms.
}
void nsTextBoxFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {

Просмотреть файл

@ -347,6 +347,7 @@ class Longhand(object):
"TextAlign",
"TextDecorationLine",
"TextEmphasisPosition",
"TextTransform",
"TouchAction",
"TransformStyle",
"UserSelect",

Просмотреть файл

@ -19,11 +19,10 @@ ${helpers.predefined_type(
// CSS Text Module Level 3
// TODO(pcwalton): `full-width`
${helpers.single_keyword(
${helpers.predefined_type(
"text-transform",
"none capitalize uppercase lowercase",
extra_gecko_values="full-width full-size-kana",
"TextTransform",
"computed::TextTransform::none()",
animation_value_type="discrete",
flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
spec="https://drafts.csswg.org/css-text/#propdef-text-transform",

Просмотреть файл

@ -87,6 +87,7 @@ pub use self::transform::{TransformOrigin, TransformStyle, Translate};
pub use self::ui::CursorImage;
pub use self::ui::{Cursor, MozForceBrokenImageIcon, UserSelect};
pub use super::specified::{BorderStyle, TextDecorationLine};
pub use super::specified::TextTransform;
pub use super::{Auto, Either, None_};
pub use app_units::Au;

Просмотреть файл

@ -21,6 +21,7 @@ use style_traits::{CssWriter, ToCss};
pub use crate::values::specified::TextAlignKeyword as TextAlign;
pub use crate::values::specified::{OverflowWrap, WordBreak};
pub use crate::values::specified::{TextDecorationLine, TextEmphasisPosition};
pub use crate::values::specified::TextTransform;
/// A computed value for the `initial-letter` property.
pub type InitialLetter = GenericInitialLetter<CSSFloat, CSSInteger>;

Просмотреть файл

@ -83,6 +83,7 @@ pub use self::table::XSpan;
pub use self::text::{InitialLetter, LetterSpacing, LineHeight, TextAlign};
pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak};
pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing};
pub use self::text::TextTransform;
pub use self::time::Time;
pub use self::transform::{Rotate, Scale, Transform};
pub use self::transform::{TransformOrigin, TransformStyle, Translate};

Просмотреть файл

@ -351,6 +351,175 @@ impl TextDecorationLine {
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToResolvedValue,
ToShmem)]
#[repr(C)]
/// Specified value of the text-transform property, stored in two parts:
/// the case-related transforms (mutually exclusive, only one may be in effect), and others (non-exclusive).
pub struct TextTransform {
/// Case transform, if any.
pub case_: TextTransformCase,
/// Non-case transforms.
pub other_: TextTransformOther,
}
impl TextTransform {
#[inline]
/// Returns the initial value of text-transform
pub fn none() -> Self {
TextTransform {
case_: TextTransformCase::None,
other_: TextTransformOther::empty(),
}
}
#[inline]
/// Returns whether the value is 'none'
pub fn is_none(&self) -> bool {
self.case_ == TextTransformCase::None && self.other_.is_empty()
}
}
impl Parse for TextTransform {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let mut result = TextTransform::none();
// Case keywords are mutually exclusive; other transforms may co-occur.
loop {
let location = input.current_source_location();
let ident = match input.next() {
Ok(&Token::Ident(ref ident)) => ident,
Ok(other) => return Err(location.new_unexpected_token_error(other.clone())),
Err(..) => break,
};
match_ignore_ascii_case! { ident,
"none" if result.is_none() => {
return Ok(result);
},
"uppercase" if result.case_ == TextTransformCase::None => {
result.case_ = TextTransformCase::Uppercase
},
"lowercase" if result.case_ == TextTransformCase::None => {
result.case_ = TextTransformCase::Lowercase
},
"capitalize" if result.case_ == TextTransformCase::None => {
result.case_ = TextTransformCase::Capitalize
},
"full-width" if !result.other_.intersects(TextTransformOther::FULL_WIDTH) => {
result.other_.insert(TextTransformOther::FULL_WIDTH)
},
"full-size-kana" if !result.other_.intersects(TextTransformOther::FULL_SIZE_KANA) => {
result.other_.insert(TextTransformOther::FULL_SIZE_KANA)
}
_ => return Err(location.new_custom_error(
SelectorParseErrorKind::UnexpectedIdent(ident.clone())
)),
}
}
if result.is_none() {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} else {
Ok(result)
}
}
}
impl ToCss for TextTransform {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.is_none() {
return dest.write_str("none");
}
if self.case_ != TextTransformCase::None {
self.case_.to_css(dest)?;
if !self.other_.is_empty() {
dest.write_str(" ")?;
}
}
self.other_.to_css(dest)
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem)]
#[repr(C)]
/// Specified keyword values for case transforms in the text-transform property. (These are exclusive.)
pub enum TextTransformCase {
/// No case transform.
None,
/// All uppercase.
Uppercase,
/// All lowercase.
Lowercase,
/// Capitalize each word.
Capitalize,
}
bitflags! {
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
#[value_info(other_values = "none,full-width,full-size-kana")]
#[repr(C)]
/// Specified keyword values for non-case transforms in the text-transform property. (Non-exclusive.)
pub struct TextTransformOther: u8 {
/// full-width
const FULL_WIDTH = 1 << 0;
/// full-size-kana
const FULL_SIZE_KANA = 1 << 1;
}
}
impl ToCss for TextTransformOther {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
let mut writer = SequenceWriter::new(dest, " ");
let mut any = false;
macro_rules! maybe_write {
($ident:ident => $str:expr) => {
if self.contains(TextTransformOther::$ident) {
writer.raw_item($str)?;
any = true;
}
};
}
maybe_write!(FULL_WIDTH => "full-width");
maybe_write!(FULL_SIZE_KANA => "full-size-kana");
debug_assert!(any || self.is_empty());
Ok(())
}
}
/// Specified value of text-align keyword value.
#[derive(
Clone,

Просмотреть файл

@ -107,6 +107,7 @@ include = [
"TouchAction",
"WillChangeBits",
"TextDecorationLine",
"TextTransform",
"MozListReversed",
"Owned",
"OwnedOrNull",
@ -267,6 +268,11 @@ renaming_overrides_prefixing = true
static inline StyleRestyleHint ForAnimations();
"""
"TextTransform" = """
static inline StyleTextTransform None();
inline bool IsNone() const;
"""
# TODO(emilio): Add hooks to cbindgen to be able to generate MOZ_MUST_USE_TYPE
# or MOZ_MUST_USE on the functions.
"Owned" = """

Просмотреть файл

@ -2,18 +2,9 @@
[e.style['text-transform'\] = "full-size-kana full-width capitalize" should set the property value]
expected: FAIL
[e.style['text-transform'\] = "capitalize full-width" should set the property value]
expected: FAIL
[e.style['text-transform'\] = "full-size-kana full-width" should set the property value]
expected: FAIL
[e.style['text-transform'\] = "capitalize full-width full-size-kana" should set the property value]
expected: FAIL
[e.style['text-transform'\] = "full-width full-size-kana" should set the property value]
expected: FAIL
[e.style['text-transform'\] = "full-size-kana lowercase full-width" should set the property value]
expected: FAIL
@ -29,9 +20,6 @@
[e.style['text-transform'\] = "full-width full-size-kana uppercase" should set the property value]
expected: FAIL
[e.style['text-transform'\] = "uppercase full-size-kana" should set the property value]
expected: FAIL
[e.style['text-transform'\] = "full-width uppercase full-size-kana" should set the property value]
expected: FAIL