diff --git a/src/Converter/EmphasisConverter.php b/src/Converter/EmphasisConverter.php
new file mode 100644
index 0000000..359b5ee
--- /dev/null
+++ b/src/Converter/EmphasisConverter.php
@@ -0,0 +1,73 @@
+isText()) {
+ $tag = $element->getTagName();
+ if ($tag === 'i' || $tag === 'em') {
+ return 'em';
+ }
+
+ if ($tag === 'b' || $tag === 'strong') {
+ return 'strong';
+ }
+ }
+
+ return '';
+ }
+
+ public function setConfig(Configuration $config): void
+ {
+ $this->config = $config;
+ }
+
+ public function convert(ElementInterface $element): string
+ {
+ $tag = $this->getNormTag($element);
+ $value = $element->getValue();
+
+ if (! \trim($value)) {
+ return $value;
+ }
+
+ if ($tag === 'em') {
+ $style = Coerce::toString($this->config->getOption('italic_style'));
+ } else {
+ $style = Coerce::toString($this->config->getOption('bold_style'));
+ }
+
+ $prefix = \ltrim($value) !== $value ? ' ' : '';
+ $suffix = \rtrim($value) !== $value ? ' ' : '';
+
+ /* If this node is immediately preceded or followed by one of the same type don't emit
+ * the start or end $style, respectively. This prevents foobar from
+ * being converted to *foo**bar* which is incorrect. We want *foobar* instead.
+ */
+ $preStyle = $this->getNormTag($element->getPreviousSibling()) === $tag ? '' : $style;
+ $postStyle = $this->getNormTag($element->getNextSibling()) === $tag ? '' : $style;
+
+ return $prefix . $preStyle . \trim($value) . $postStyle . $suffix;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getSupportedTags(): array
+ {
+ return ['em', 'i', 'strong', 'b'];
+ }
+}
diff --git a/src/Converter/TextConverter.php b/src/Converter/TextConverter.php
new file mode 100644
index 0000000..465dd48
--- /dev/null
+++ b/src/Converter/TextConverter.php
@@ -0,0 +1,48 @@
+getValue();
+
+ // Remove leftover \n at the beginning of the line
+ $markdown = \ltrim($markdown, "\n");
+
+ // Replace sequences of invisible characters with spaces
+ $markdown = \preg_replace('~\s+~u', ' ', $markdown);
+ \assert(\is_string($markdown));
+
+ // Escape the following characters: '*', '_', '[', ']' and '\'
+ if (($parent = $element->getParent()) && $parent->getTagName() !== 'div') {
+ $markdown = \preg_replace('~([*_\\[\\]\\\\])~u', '\\\\$1', $markdown);
+ \assert(\is_string($markdown));
+ }
+
+ $markdown = \preg_replace('~^#~u', '\\\\#', $markdown);
+ \assert(\is_string($markdown));
+
+ if ($markdown === ' ') {
+ $next = $element->getNext();
+ if (! $next || $next->isBlock()) {
+ $markdown = '';
+ }
+ }
+
+ return \htmlspecialchars($markdown, ENT_NOQUOTES, 'UTF-8');
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getSupportedTags(): array
+ {
+ return ['#text'];
+ }
+}
diff --git a/tests/HtmlConverterTest.php b/tests/HtmlConverterTest.php
index dd9bd6d..e62247e 100644
--- a/tests/HtmlConverterTest.php
+++ b/tests/HtmlConverterTest.php
@@ -163,8 +163,6 @@ class HtmlConverterTest extends TestCase
$this->assertHtmlGivesMarkdown('google.com', '[google.com](google.com)');
$this->assertHtmlGivesMarkdown('https://www.google.com', '');
$this->assertHtmlGivesMarkdown('ftp://files.example.com', '');
- $this->assertHtmlGivesMarkdown('https://example.com/?a=1&b=2', '');
- $this->assertHtmlGivesMarkdown('https://example.com/<b>', '[https://example.com/<b>](https://example.com/)');
$this->assertHtmlGivesMarkdown('test@example.com', '');
$this->assertHtmlGivesMarkdown('test+foo@example.bar-baz.com', '');