+// TelegramMarkdownText is the plain text of a Markdown reply and its Telegram
+// entities, while the conversion builds it. Units counts the UTF-16 code units
+// of text, the unit of the entity offsets and lengths.
+type TelegramMarkdownText struct {
+ text strings.Builder
+ units int
+ entities []TelegramEntity
+}
+
+// add appends text.
+func (m *TelegramMarkdownText) add(text string) {
+ m.text.WriteString(text)
+
+ m.units += telegramUTF16Units(text)
+}
+
+// entity keeps entity, unless it is empty, because Telegram refuses an entity
+// of length 0.
+func (m *TelegramMarkdownText) entity(entity TelegramEntity) {
+ if entity.Length > 0 {
+ m.entities = append(m.entities, entity)
+ }
+}
+
+// inline appends one line of Markdown with its inline marks as entities. A
+// backslash before an ASCII punctuation character makes the character plain
+// text.
+func (m *TelegramMarkdownText) inline(line string) {
+ plain := 0
+ index := 0
+ for index < len(line) {
+ if line[index] == '\\' && index+1 < len(line) && strings.IndexByte(markdownPunctuation, line[index+1]) >= 0 {
+ m.add(line[plain:index])
+
+ plain = index + 1
+ index += 2
+
+ continue
+ }
+
+ span, found := markdownMarkSpan(line, index)
+ if !found {
+ index++
+ continue
+ }
+
+ m.add(line[plain:index])
+
+ start := m.units
+ if span.kind == "code" {
+ m.add(span.content)
+ } else {
+ m.inline(span.content)
+ }
+
+ m.entity(TelegramEntity{
+ Type: span.kind,
+ Offset: start,
+ Length: m.units - start,
+ URL: span.url,
+ })
+
+ index = span.end
+ plain = span.end
+ }
+
+ m.add(line[plain:])
+}
+