| /** |
| * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. |
| * (C) 2008 Torch Mobile Inc. All rights reserved. |
| * (http://www.torchmobile.com/) |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "core/layout/LayoutTextControl.h" |
| |
| #include "core/dom/StyleChangeReason.h" |
| #include "core/html/TextControlElement.h" |
| #include "core/layout/HitTestResult.h" |
| #include "platform/scroll/ScrollbarTheme.h" |
| |
| namespace blink { |
| |
| LayoutTextControl::LayoutTextControl(TextControlElement* element) |
| : LayoutBlockFlow(element) { |
| DCHECK(element); |
| } |
| |
| LayoutTextControl::~LayoutTextControl() {} |
| |
| TextControlElement* LayoutTextControl::GetTextControlElement() const { |
| return ToTextControlElement(GetNode()); |
| } |
| |
| HTMLElement* LayoutTextControl::InnerEditorElement() const { |
| return GetTextControlElement()->InnerEditorElement(); |
| } |
| |
| void LayoutTextControl::StyleDidChange(StyleDifference diff, |
| const ComputedStyle* old_style) { |
| LayoutBlockFlow::StyleDidChange(diff, old_style); |
| Element* inner_editor = InnerEditorElement(); |
| if (!inner_editor) |
| return; |
| LayoutBlock* inner_editor_layout_object = |
| ToLayoutBlock(inner_editor->GetLayoutObject()); |
| if (inner_editor_layout_object) { |
| // We may have set the width and the height in the old style in layout(). |
| // Reset them now to avoid getting a spurious layout hint. |
| inner_editor_layout_object->MutableStyleRef().SetHeight(Length()); |
| inner_editor_layout_object->MutableStyleRef().SetWidth(Length()); |
| inner_editor_layout_object->SetStyle(CreateInnerEditorStyle(StyleRef())); |
| inner_editor->SetNeedsStyleRecalc( |
| kSubtreeStyleChange, |
| StyleChangeReasonForTracing::Create(StyleChangeReason::kControl)); |
| } |
| GetTextControlElement()->UpdatePlaceholderVisibility(); |
| } |
| |
| static inline void UpdateUserModifyProperty(TextControlElement& node, |
| ComputedStyle& style) { |
| style.SetUserModify(node.IsDisabledOrReadOnly() |
| ? EUserModify::kReadOnly |
| : EUserModify::kReadWritePlaintextOnly); |
| } |
| |
| void LayoutTextControl::AdjustInnerEditorStyle( |
| ComputedStyle& text_block_style) const { |
| // The inner block, if present, always has its direction set to LTR, |
| // so we need to inherit the direction and unicode-bidi style from the |
| // element. |
| text_block_style.SetDirection(Style()->Direction()); |
| text_block_style.SetUnicodeBidi(Style()->GetUnicodeBidi()); |
| text_block_style.SetUserSelect(EUserSelect::kText); |
| |
| UpdateUserModifyProperty(*GetTextControlElement(), text_block_style); |
| } |
| |
| int LayoutTextControl::TextBlockLogicalHeight() const { |
| return (LogicalHeight() - BorderAndPaddingLogicalHeight()).ToInt(); |
| } |
| |
| int LayoutTextControl::TextBlockLogicalWidth() const { |
| Element* inner_editor = InnerEditorElement(); |
| DCHECK(inner_editor); |
| |
| LayoutUnit unit_width = LogicalWidth() - BorderAndPaddingLogicalWidth(); |
| if (inner_editor->GetLayoutObject()) |
| unit_width -= inner_editor->GetLayoutBox()->PaddingStart() + |
| inner_editor->GetLayoutBox()->PaddingEnd(); |
| |
| return unit_width.ToInt(); |
| } |
| |
| void LayoutTextControl::UpdateFromElement() { |
| Element* inner_editor = InnerEditorElement(); |
| if (inner_editor && inner_editor->GetLayoutObject()) |
| UpdateUserModifyProperty( |
| *GetTextControlElement(), |
| inner_editor->GetLayoutObject()->MutableStyleRef()); |
| } |
| |
| int LayoutTextControl::ScrollbarThickness() const { |
| // FIXME: We should get the size of the scrollbar from the LayoutTheme |
| // instead. |
| return ScrollbarTheme::GetTheme().ScrollbarThickness(); |
| } |
| |
| void LayoutTextControl::ComputeLogicalHeight( |
| LayoutUnit logical_height, |
| LayoutUnit logical_top, |
| LogicalExtentComputedValues& computed_values) const { |
| HTMLElement* inner_editor = InnerEditorElement(); |
| DCHECK(inner_editor); |
| if (LayoutBox* inner_editor_box = inner_editor->GetLayoutBox()) { |
| LayoutUnit non_content_height = inner_editor_box->BorderAndPaddingHeight() + |
| inner_editor_box->MarginHeight(); |
| logical_height = ComputeControlLogicalHeight( |
| inner_editor_box->LineHeight(true, kHorizontalLine, |
| kPositionOfInteriorLineBoxes), |
| non_content_height); |
| |
| // We are able to have a horizontal scrollbar if the overflow style is |
| // scroll, or if its auto and there's no word wrap. |
| if (Style()->OverflowInlineDirection() == EOverflow::kScroll || |
| (Style()->OverflowInlineDirection() == EOverflow::kAuto && |
| inner_editor->GetLayoutObject()->Style()->OverflowWrap() == |
| EOverflowWrap::kNormal)) |
| logical_height += ScrollbarThickness(); |
| |
| // FIXME: The logical height of the inner text box should have been added |
| // before calling computeLogicalHeight to avoid this hack. |
| SetIntrinsicContentLogicalHeight(logical_height); |
| |
| logical_height += BorderAndPaddingHeight(); |
| } |
| |
| LayoutBox::ComputeLogicalHeight(logical_height, logical_top, computed_values); |
| } |
| |
| void LayoutTextControl::HitInnerEditorElement( |
| HitTestResult& result, |
| const LayoutPoint& point_in_container, |
| const LayoutPoint& accumulated_offset) { |
| HTMLElement* inner_editor = InnerEditorElement(); |
| if (!inner_editor->GetLayoutObject()) |
| return; |
| |
| LayoutPoint adjusted_location = accumulated_offset + Location(); |
| LayoutPoint local_point = |
| point_in_container - |
| ToLayoutSize(adjusted_location + |
| inner_editor->GetLayoutBox()->Location()); |
| if (HasOverflowClip()) |
| local_point += ScrolledContentOffset(); |
| result.SetNodeAndPosition(inner_editor, local_point); |
| } |
| |
| static const char* const kFontFamiliesWithInvalidCharWidth[] = { |
| "American Typewriter", |
| "Arial Hebrew", |
| "Chalkboard", |
| "Cochin", |
| "Corsiva Hebrew", |
| "Courier", |
| "Euphemia UCAS", |
| "Geneva", |
| "Gill Sans", |
| "Hei", |
| "Helvetica", |
| "Hoefler Text", |
| "InaiMathi", |
| "Kai", |
| "Lucida Grande", |
| "Marker Felt", |
| "Monaco", |
| "Mshtakan", |
| "New Peninim MT", |
| "Osaka", |
| "Raanana", |
| "STHeiti", |
| "Symbol", |
| "Times", |
| "Apple Braille", |
| "Apple LiGothic", |
| "Apple LiSung", |
| "Apple Symbols", |
| "AppleGothic", |
| "AppleMyungjo", |
| "#GungSeo", |
| "#HeadLineA", |
| "#PCMyungjo", |
| "#PilGi", |
| }; |
| |
| // For font families where any of the fonts don't have a valid entry in the OS/2 |
| // table for avgCharWidth, fallback to the legacy webkit behavior of getting the |
| // avgCharWidth from the width of a '0'. This only seems to apply to a fixed |
| // number of Mac fonts, but, in order to get similar rendering across platforms, |
| // we do this check for all platforms. |
| bool LayoutTextControl::HasValidAvgCharWidth(const SimpleFontData* font_data, |
| const AtomicString& family) { |
| // Some fonts match avgCharWidth to CJK full-width characters. |
| // Heuristic check to avoid such fonts. |
| DCHECK(font_data); |
| if (!font_data) |
| return false; |
| const FontMetrics& metrics = font_data->GetFontMetrics(); |
| if (metrics.HasZeroWidth() && |
| font_data->AvgCharWidth() > metrics.ZeroWidth() * 1.7) |
| return false; |
| |
| static HashSet<AtomicString>* font_families_with_invalid_char_width_map = |
| nullptr; |
| |
| if (family.IsEmpty()) |
| return false; |
| |
| if (!font_families_with_invalid_char_width_map) { |
| font_families_with_invalid_char_width_map = new HashSet<AtomicString>; |
| |
| for (size_t i = 0; i < WTF_ARRAY_LENGTH(kFontFamiliesWithInvalidCharWidth); |
| ++i) |
| font_families_with_invalid_char_width_map->insert( |
| AtomicString(kFontFamiliesWithInvalidCharWidth[i])); |
| } |
| |
| return !font_families_with_invalid_char_width_map->Contains(family); |
| } |
| |
| float LayoutTextControl::GetAvgCharWidth(const AtomicString& family) const { |
| const Font& font = Style()->GetFont(); |
| |
| const SimpleFontData* primary_font = font.PrimaryFont(); |
| if (primary_font && HasValidAvgCharWidth(primary_font, family)) |
| return roundf(primary_font->AvgCharWidth()); |
| |
| const UChar kCh = '0'; |
| const String str = String(&kCh, 1); |
| TextRun text_run = |
| ConstructTextRun(font, str, StyleRef(), TextRun::kAllowTrailingExpansion); |
| return font.Width(text_run); |
| } |
| |
| float LayoutTextControl::ScaleEmToUnits(int x) const { |
| // This matches the unitsPerEm value for MS Shell Dlg and Courier New from the |
| // "head" font table. |
| float units_per_em = 2048.0f; |
| return roundf(Style()->GetFont().GetFontDescription().ComputedSize() * x / |
| units_per_em); |
| } |
| |
| void LayoutTextControl::ComputeIntrinsicLogicalWidths( |
| LayoutUnit& min_logical_width, |
| LayoutUnit& max_logical_width) const { |
| // Use average character width. Matches IE. |
| AtomicString family = |
| Style()->GetFont().GetFontDescription().Family().Family(); |
| max_logical_width = PreferredContentLogicalWidth( |
| const_cast<LayoutTextControl*>(this)->GetAvgCharWidth(family)); |
| if (InnerEditorElement()) { |
| if (LayoutBox* inner_editor_layout_box = |
| InnerEditorElement()->GetLayoutBox()) |
| max_logical_width += inner_editor_layout_box->PaddingStart() + |
| inner_editor_layout_box->PaddingEnd(); |
| } |
| if (!Style()->LogicalWidth().IsPercentOrCalc()) |
| min_logical_width = max_logical_width; |
| } |
| |
| void LayoutTextControl::ComputePreferredLogicalWidths() { |
| DCHECK(PreferredLogicalWidthsDirty()); |
| |
| min_preferred_logical_width_ = LayoutUnit(); |
| max_preferred_logical_width_ = LayoutUnit(); |
| const ComputedStyle& style_to_use = StyleRef(); |
| |
| if (style_to_use.LogicalWidth().IsFixed() && |
| style_to_use.LogicalWidth().Value() >= 0) |
| min_preferred_logical_width_ = max_preferred_logical_width_ = |
| AdjustContentBoxLogicalWidthForBoxSizing( |
| style_to_use.LogicalWidth().Value()); |
| else |
| ComputeIntrinsicLogicalWidths(min_preferred_logical_width_, |
| max_preferred_logical_width_); |
| |
| if (style_to_use.LogicalMinWidth().IsFixed() && |
| style_to_use.LogicalMinWidth().Value() > 0) { |
| max_preferred_logical_width_ = |
| std::max(max_preferred_logical_width_, |
| AdjustContentBoxLogicalWidthForBoxSizing( |
| style_to_use.LogicalMinWidth().Value())); |
| min_preferred_logical_width_ = |
| std::max(min_preferred_logical_width_, |
| AdjustContentBoxLogicalWidthForBoxSizing( |
| style_to_use.LogicalMinWidth().Value())); |
| } |
| |
| if (style_to_use.LogicalMaxWidth().IsFixed()) { |
| max_preferred_logical_width_ = |
| std::min(max_preferred_logical_width_, |
| AdjustContentBoxLogicalWidthForBoxSizing( |
| style_to_use.LogicalMaxWidth().Value())); |
| min_preferred_logical_width_ = |
| std::min(min_preferred_logical_width_, |
| AdjustContentBoxLogicalWidthForBoxSizing( |
| style_to_use.LogicalMaxWidth().Value())); |
| } |
| |
| LayoutUnit to_add = BorderAndPaddingLogicalWidth(); |
| |
| min_preferred_logical_width_ += to_add; |
| max_preferred_logical_width_ += to_add; |
| |
| ClearPreferredLogicalWidthsDirty(); |
| } |
| |
| void LayoutTextControl::AddOutlineRects(Vector<LayoutRect>& rects, |
| const LayoutPoint& additional_offset, |
| IncludeBlockVisualOverflowOrNot) const { |
| rects.push_back(LayoutRect(additional_offset, Size())); |
| } |
| |
| LayoutObject* LayoutTextControl::LayoutSpecialExcludedChild( |
| bool relayout_children, |
| SubtreeLayoutScope& layout_scope) { |
| HTMLElement* placeholder = |
| ToTextControlElement(GetNode())->PlaceholderElement(); |
| LayoutObject* placeholder_layout_object = |
| placeholder ? placeholder->GetLayoutObject() : nullptr; |
| if (!placeholder_layout_object) |
| return nullptr; |
| if (relayout_children) |
| layout_scope.SetChildNeedsLayout(placeholder_layout_object); |
| return placeholder_layout_object; |
| } |
| |
| LayoutUnit LayoutTextControl::FirstLineBoxBaseline() const { |
| LayoutUnit result = LayoutBlock::FirstLineBoxBaseline(); |
| if (result != -1) |
| return result; |
| |
| // When the text is empty, |LayoutBlock::firstLineBoxBaseline()| cannot |
| // compute the baseline because lineboxes do not exist. |
| Element* inner_editor = InnerEditorElement(); |
| if (!inner_editor || !inner_editor->GetLayoutObject()) |
| return LayoutUnit(-1); |
| |
| LayoutBlock* inner_editor_layout_object = |
| ToLayoutBlock(inner_editor->GetLayoutObject()); |
| const SimpleFontData* font_data = |
| inner_editor_layout_object->Style(true)->GetFont().PrimaryFont(); |
| DCHECK(font_data); |
| if (!font_data) |
| return LayoutUnit(-1); |
| |
| LayoutUnit baseline(font_data->GetFontMetrics().Ascent(kAlphabeticBaseline)); |
| for (LayoutObject* box = inner_editor_layout_object; box && box != this; |
| box = box->Parent()) { |
| if (box->IsBox()) |
| baseline += ToLayoutBox(box)->LogicalTop(); |
| } |
| return baseline; |
| } |
| |
| } // namespace blink |