docs: def involved_tags(self):
          """Provides all HTML tags directly involved in this string."""
          if len(self._addresses) < 2:
              # there can't be a tag boundary if there's only 1 or 0 characters
              return frozenset()
  
          # creating 'parent_sets' mapping, where the first item in tuple
          # is the address of character and the second is set
          # of character's parent HTML elements
          parent_sets = []
  
          # meanwhile we are creatingalso a set of common parents so we can
          # put them away later on (we're not interested in them as
          # they're only some global wrappers)
          common_parents = set()
  
          for addr in self._addresses:
              parents = set()
              if addr.attr == 'text':
                  parents.add(addr.element)
              parents.update(addr.element.iterancestors())
  
              parent_sets.append((addr, parents))
              if not common_parents:
                  common_parents = parents
              else:
                  common_parents &= parents
  
          # constructing final set of involved tags
          involved_tags = set()
          prev_addr = None
  
          for addr, parents in parent_sets:
              parents = parents - common_parents
              involved_tags.update(p.tag for p in parents)
  
              # hidden tags - sometimes there are tags without text which
              # can hide between characters, but they actually break textflow
              is_tail_of_hidden = (
                  prev_addr and
                  addr.attr == 'tail' and
                  prev_addr.element != addr.element
              )
              if is_tail_of_hidden:
                  involved_tags.add(addr.element)
  
              prev_addr = addr
  
          return frozenset(involved_tags)