From 26c52556c40be1c7e62342eb7c7a127d2a3d6617 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Fri, 21 Aug 2026 04:15:16 +0200 Subject: [PATCH] perf: skip inactive inline transforms on borrowed HTML --- src/Performance/BorrowedHtmlLayout.php | 62 +++++++++++++++++--------- tests/BorrowedHtmlLayoutTest.php | 1 + 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/Performance/BorrowedHtmlLayout.php b/src/Performance/BorrowedHtmlLayout.php index 8995073..1fb3d56 100644 --- a/src/Performance/BorrowedHtmlLayout.php +++ b/src/Performance/BorrowedHtmlLayout.php @@ -423,9 +423,22 @@ private function renderInline(string $text, array $definitions): ?string if (preg_match('/^\[[^\]]+\]: +\S+ +"[^"]*"$/', $text) === 1) { return $this->escapeText($text); } + if ( + strpbrk($text, "*_`[{}^\\<>~!@$=#'\":") === false + && !str_contains($text, '--') + && !str_contains($text, '...') + && !str_contains($text, '(c)') + && !str_contains($text, '(r)') + && !str_contains($text, '(tm)') + ) { + return $this->escape($text); + } if ($this->inlineComplex($text)) { return null; } + if (strpbrk($text, '*_`[') === false) { + return $this->escapeText($text); + } $flat = $this->renderFlatInline($text, $definitions); if ($flat !== false) { return $flat; @@ -745,12 +758,15 @@ private function decimalListItem(string $line): ?array private function inlineComplex(string $text): bool { - $withoutContractions = preg_replace('/(?<=[A-Za-z0-9])\'(?=[A-Za-z0-9])/', '', $text); - if ($withoutContractions === null) { - return true; + $withoutContractions = $text; + if (str_contains($text, "'")) { + $withoutContractions = preg_replace('/(?<=[A-Za-z0-9])\'(?=[A-Za-z0-9])/', '', $text); + if ($withoutContractions === null) { + return true; + } } - if (substr_count($withoutContractions, '"') % 2 !== 0) { + if (str_contains($withoutContractions, '"') && substr_count($withoutContractions, '"') % 2 !== 0) { return true; } @@ -857,22 +873,28 @@ private function escape(string $text): string private function escapeText(string $text): string { - $text = (string)preg_replace('/(?<=[A-Za-z0-9])\'(?=[A-Za-z0-9])/', '’', $text); - $text = (string)preg_replace('/"([^"]*)"/', '“$1”', $text); - $text = (string)preg_replace_callback('/-{2,}/', static function (array $match): string { - $length = strlen($match[0]); - if ($length % 2 === 0 && $length % 3 !== 0) { - return str_repeat('–', intdiv($length, 2)); - } - $triples = intdiv($length, 3); - $remainder = $length % 3; - if ($remainder === 1) { - $triples--; - $remainder = 4; - } - - return str_repeat('—', $triples) . str_repeat('–', intdiv($remainder, 2)); - }, $text); + if (str_contains($text, "'")) { + $text = (string)preg_replace('/(?<=[A-Za-z0-9])\'(?=[A-Za-z0-9])/', '’', $text); + } + if (str_contains($text, '"')) { + $text = (string)preg_replace('/"([^"]*)"/', '“$1”', $text); + } + if (str_contains($text, '--')) { + $text = (string)preg_replace_callback('/-{2,}/', static function (array $match): string { + $length = strlen($match[0]); + if ($length % 2 === 0 && $length % 3 !== 0) { + return str_repeat('–', intdiv($length, 2)); + } + $triples = intdiv($length, 3); + $remainder = $length % 3; + if ($remainder === 1) { + $triples--; + $remainder = 4; + } + + return str_repeat('—', $triples) . str_repeat('–', intdiv($remainder, 2)); + }, $text); + } return $this->escape($text); } diff --git a/tests/BorrowedHtmlLayoutTest.php b/tests/BorrowedHtmlLayoutTest.php index 1cb33a8..26a1f5c 100644 --- a/tests/BorrowedHtmlLayoutTest.php +++ b/tests/BorrowedHtmlLayoutTest.php @@ -28,6 +28,7 @@ public function testAcceptedDocumentsAreByteIdenticalToTheAstPipeline(string $so public static function acceptedDocuments(): iterable { yield 'plain paragraphs' => ["First paragraph.\ncontinues here.\n\nSecond paragraph.\n"]; + yield 'smart punctuation' => ["A \"quote\", don't stop--now.\n"]; yield 'core inline' => ["A *strong*, _emphasized_, and `coded` [link](https://example.com).\n"]; yield 'sections' => ["# First heading\n\nBody.\n\n## Child heading\n\nMore body.\n"]; yield 'code fence' => ["# Code\n\n```php\necho '';\n```\n"];