Position.cpp 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
  3. * Copyright (c) 2021, Max Wipfli <mail@maxwipfli.ch>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/Utf8View.h>
  8. #include <LibUnicode/CharacterTypes.h>
  9. #include <LibUnicode/Segmenter.h>
  10. #include <LibWeb/DOM/Node.h>
  11. #include <LibWeb/DOM/Position.h>
  12. #include <LibWeb/DOM/Text.h>
  13. namespace Web::DOM {
  14. JS_DEFINE_ALLOCATOR(Position);
  15. Position::Position(JS::GCPtr<Node> node, unsigned offset)
  16. : m_node(node)
  17. , m_offset(offset)
  18. {
  19. }
  20. void Position::visit_edges(Visitor& visitor)
  21. {
  22. Base::visit_edges(visitor);
  23. visitor.visit(m_node);
  24. }
  25. ErrorOr<String> Position::to_string() const
  26. {
  27. if (!node())
  28. return String::formatted("DOM::Position(nullptr, {})", offset());
  29. return String::formatted("DOM::Position({} ({})), {})", node()->node_name(), node().ptr(), offset());
  30. }
  31. bool Position::increment_offset()
  32. {
  33. if (!is<DOM::Text>(*m_node))
  34. return false;
  35. auto& node = verify_cast<DOM::Text>(*m_node);
  36. if (auto offset = node.grapheme_segmenter().next_boundary(m_offset); offset.has_value()) {
  37. m_offset = *offset;
  38. return true;
  39. }
  40. // NOTE: Already at end of current node.
  41. return false;
  42. }
  43. bool Position::decrement_offset()
  44. {
  45. if (!is<DOM::Text>(*m_node))
  46. return false;
  47. auto& node = verify_cast<DOM::Text>(*m_node);
  48. if (auto offset = node.grapheme_segmenter().previous_boundary(m_offset); offset.has_value()) {
  49. m_offset = *offset;
  50. return true;
  51. }
  52. // NOTE: Already at beginning of current node.
  53. return false;
  54. }
  55. static bool should_continue_beyond_word(Utf8View const& word)
  56. {
  57. for (auto code_point : word) {
  58. if (!Unicode::code_point_has_punctuation_general_category(code_point) && !Unicode::code_point_has_separator_general_category(code_point))
  59. return false;
  60. }
  61. return true;
  62. }
  63. bool Position::increment_offset_to_next_word()
  64. {
  65. if (!is<DOM::Text>(*m_node) || offset_is_at_end_of_node())
  66. return false;
  67. auto& node = static_cast<DOM::Text&>(*m_node);
  68. while (true) {
  69. if (auto offset = node.word_segmenter().next_boundary(m_offset); offset.has_value()) {
  70. auto word = node.data().code_points().substring_view(m_offset, *offset - m_offset);
  71. m_offset = *offset;
  72. if (should_continue_beyond_word(word))
  73. continue;
  74. }
  75. break;
  76. }
  77. return true;
  78. }
  79. bool Position::decrement_offset_to_previous_word()
  80. {
  81. if (!is<DOM::Text>(*m_node) || m_offset == 0)
  82. return false;
  83. auto& node = static_cast<DOM::Text&>(*m_node);
  84. while (true) {
  85. if (auto offset = node.word_segmenter().previous_boundary(m_offset); offset.has_value()) {
  86. auto word = node.data().code_points().substring_view(*offset, m_offset - *offset);
  87. m_offset = *offset;
  88. if (should_continue_beyond_word(word))
  89. continue;
  90. }
  91. break;
  92. }
  93. return true;
  94. }
  95. bool Position::offset_is_at_end_of_node() const
  96. {
  97. if (!is<DOM::Text>(*m_node))
  98. return false;
  99. auto& node = verify_cast<DOM::Text>(*m_node);
  100. auto text = node.data();
  101. return m_offset == text.bytes_as_string_view().length();
  102. }
  103. }