+// telegramCodeFence returns the language of a line that opens a fenced code
+// block, and the fence. The fence is "" when line does not open a block.
+func telegramCodeFence(line string) (string, string) {
+ trimmed := strings.TrimLeft(line, " ")
+ if len(line)-len(trimmed) > 3 || !strings.HasPrefix(trimmed, "```") {
+ return "", ""
+ }
+
+ run := len(trimmed) - len(strings.TrimLeft(trimmed, "`"))
+ info := strings.TrimSpace(trimmed[run:])
+ if strings.Contains(info, "`") {
+ return "", ""
+ }
+
+ language, _, _ := strings.Cut(info, " ")
+
+ return language, trimmed[:run]
+}
+
+// telegramClosesFence tells if line closes the code block of fence.
+func telegramClosesFence(line, fence string) bool {
+ trimmed := strings.TrimSpace(line)
+ return strings.HasPrefix(trimmed, fence) && strings.Trim(trimmed, "`") == ""
+}
+
+// telegramUTF16Units returns the number of UTF-16 code units of text.
+func telegramUTF16Units(text string) int {
+ units := 0
+ for _, char := range text {
+ units++
+ if char > 0xFFFF {
+ units++
+ }
+ }
+
+ return units
+}
+
+// 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
+}
+