PaintableFragment.cpp 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /*
  2. * Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/DOM/Range.h>
  7. #include <LibWeb/Layout/Viewport.h>
  8. #include <LibWeb/Painting/PaintableBox.h>
  9. #include <LibWeb/Painting/TextPaintable.h>
  10. namespace Web::Painting {
  11. PaintableFragment::PaintableFragment(Layout::LineBoxFragment const& fragment)
  12. : m_layout_node(fragment.layout_node())
  13. , m_offset(fragment.offset())
  14. , m_size(fragment.size())
  15. , m_baseline(fragment.baseline())
  16. , m_start(fragment.start())
  17. , m_length(fragment.length())
  18. , m_glyph_run(fragment.glyph_run())
  19. {
  20. }
  21. CSSPixelRect const PaintableFragment::absolute_rect() const
  22. {
  23. CSSPixelRect rect { {}, size() };
  24. auto const* containing_block = paintable().containing_block();
  25. if (containing_block)
  26. rect.set_location(containing_block->absolute_position());
  27. rect.translate_by(offset());
  28. return rect;
  29. }
  30. int PaintableFragment::text_index_at(CSSPixels x) const
  31. {
  32. if (!is<TextPaintable>(paintable()))
  33. return 0;
  34. auto& layout_text = verify_cast<Layout::TextNode>(layout_node());
  35. auto& font = layout_text.first_available_font();
  36. Utf8View view(string_view());
  37. CSSPixels relative_x = x - absolute_rect().x();
  38. CSSPixels glyph_spacing = font.glyph_spacing();
  39. if (relative_x < 0)
  40. return 0;
  41. CSSPixels width_so_far = 0;
  42. for (auto it = view.begin(); it != view.end(); ++it) {
  43. auto previous_it = it;
  44. CSSPixels glyph_width = CSSPixels::nearest_value_for(font.glyph_or_emoji_width(it));
  45. if ((width_so_far + glyph_width + glyph_spacing / 2) > relative_x)
  46. return m_start + view.byte_offset_of(previous_it);
  47. width_so_far += glyph_width + glyph_spacing;
  48. }
  49. return m_start + m_length;
  50. }
  51. CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
  52. {
  53. if (paintable().selection_state() == Paintable::SelectionState::None)
  54. return {};
  55. if (paintable().selection_state() == Paintable::SelectionState::Full)
  56. return absolute_rect();
  57. auto selection = paintable().document().get_selection();
  58. if (!selection)
  59. return {};
  60. auto range = selection->range();
  61. if (!range)
  62. return {};
  63. // FIXME: m_start and m_length should be unsigned and then we won't need these casts.
  64. auto const start_index = static_cast<unsigned>(m_start);
  65. auto const end_index = static_cast<unsigned>(m_start) + static_cast<unsigned>(m_length);
  66. auto text = string_view();
  67. if (paintable().selection_state() == Paintable::SelectionState::StartAndEnd) {
  68. // we are in the start/end node (both the same)
  69. if (start_index > range->end_offset())
  70. return {};
  71. if (end_index < range->start_offset())
  72. return {};
  73. if (range->start_offset() == range->end_offset())
  74. return {};
  75. auto selection_start_in_this_fragment = max(0, range->start_offset() - m_start);
  76. auto selection_end_in_this_fragment = min(m_length, range->end_offset() - m_start);
  77. auto pixel_distance_to_first_selected_character = CSSPixels::nearest_value_for(font.width(text.substring_view(0, selection_start_in_this_fragment)));
  78. auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;
  79. auto rect = absolute_rect();
  80. rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
  81. rect.set_width(pixel_width_of_selection);
  82. return rect;
  83. }
  84. if (paintable().selection_state() == Paintable::SelectionState::Start) {
  85. // we are in the start node
  86. if (end_index < range->start_offset())
  87. return {};
  88. auto selection_start_in_this_fragment = max(0, range->start_offset() - m_start);
  89. auto selection_end_in_this_fragment = m_length;
  90. auto pixel_distance_to_first_selected_character = CSSPixels::nearest_value_for(font.width(text.substring_view(0, selection_start_in_this_fragment)));
  91. auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;
  92. auto rect = absolute_rect();
  93. rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
  94. rect.set_width(pixel_width_of_selection);
  95. return rect;
  96. }
  97. if (paintable().selection_state() == Paintable::SelectionState::End) {
  98. // we are in the end node
  99. if (start_index > range->end_offset())
  100. return {};
  101. auto selection_start_in_this_fragment = 0;
  102. auto selection_end_in_this_fragment = min(range->end_offset() - m_start, m_length);
  103. auto pixel_distance_to_first_selected_character = CSSPixels::nearest_value_for(font.width(text.substring_view(0, selection_start_in_this_fragment)));
  104. auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;
  105. auto rect = absolute_rect();
  106. rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
  107. rect.set_width(pixel_width_of_selection);
  108. return rect;
  109. }
  110. return {};
  111. }
  112. StringView PaintableFragment::string_view() const
  113. {
  114. if (!is<TextPaintable>(paintable()))
  115. return {};
  116. return static_cast<TextPaintable const&>(paintable()).text_for_rendering().bytes_as_string_view().substring_view(m_start, m_length);
  117. }
  118. }