--- modules/node/node.pages.inc.orig	2010-09-19 01:27:49.000000000 -0700
+++ modules/node/node.pages.inc	2010-09-19 01:36:19.000000000 -0700
@@ -263,7 +263,13 @@
 function node_body_field(&$node, $label, $word_count) {
 
   // Check if we need to restore the teaser at the beginning of the body.
-  $include = !isset($node->teaser) || ($node->teaser == substr($node->body, 0, strlen($node->teaser)));
+  $include = TRUE;
+  if (isset($node->teaser)) {
+    // remove all the tags at the end because if they are closing tags
+    // then they were likely added by node_teaser().
+    $teaser = preg_replace('/(<[^>]+>)+$/', '', $node->teaser);
+    $include = $teaser == substr($node->body, 0, strlen($teaser));
+  }
 
   $form = array(
     '#after_build' => array('node_teaser_js', 'node_teaser_include_verify'));
--- modules/node/node.module.orig	2010-09-19 01:28:14.000000000 -0700
+++ modules/node/node.module	2010-09-19 01:34:47.000000000 -0700
@@ -340,61 +340,141 @@
 
   // If the delimiter has not been specified, try to split at paragraph or
   // sentence boundaries.
+  $filter_newline = isset($filters['filter/1']);
+  $len = strlen($body);
 
-  // The teaser may not be longer than maximum length specified. Initial slice.
-  $teaser = truncate_utf8($body, $size);
+  $p = 0;
+  $l = 0;
+  $s = array(); // stack
+  while ($p < $len && $l < $size) {
+    $last_tag = FALSE;
+    $o = strpos($body, '<', $p);
+    if ($o === FALSE) {
+      // no more tags till the end
+      $a = drupal_strlen(substr($body, $p, $len - $p)); // UTF-8 length
+      $n = $len;
+    }
+    else {
+      // count characters between previous position and
+      // beginning of tag
+      $a = drupal_strlen(substr($body, $p, $o - $p)); // UTF-8 length
 
-  // Store the actual length of the UTF8 string -- which might not be the same
-  // as $size.
-  $max_rpos = strlen($teaser);
+      ++$o; // skip the '<'
+      $n = strpos($body, '>', $o);
 
-  // How much to cut off the end of the teaser so that it doesn't end in the
-  // middle of a paragraph, sentence, or word.
-  // Initialize it to maximum in order to find the minimum.
-  $min_rpos = $max_rpos;
+      if ($body[$o] == '/') {
+        // closing tag, pop the opening tag too
+        array_pop($s);
+      }
+      elseif ($body[$n - 1] != '/') { // skip empty tags
+        // opening tag, save its name on the stack so we can close it later
+        $end_name = strpos($body, ' ', $o);
+        if ($end_name === FALSE || $end_name > $n) {
+          $end_name = $n;
+        }
+        $tag_name = substr($body, $o, $end_name - $o);
+        switch ($tag_name) { // ignore empty tags that were not properly closed
+        case 'br':
+        case 'hr':
+        case 'img':
+        case 'input':
+          break;
 
-  // Store the reverse of the teaser.  We use strpos on the reversed needle and
-  // haystack for speed and convenience.
-  $reversed = strrev($teaser);
+        default:
+          $s[] = $tag_name;
+          $last_tag = TRUE;
+          break;
 
-  // Build an array of arrays of break points grouped by preference.
-  $break_points = array();
+        }
+      }
 
-  // A paragraph near the end of sliced teaser is most preferable.
-  $break_points[] = array('</p>' => 0);
+      // skip the tag now (we assume properly opening/closing tag boundaries!)
+      if ($n === FALSE) {
+        // last tag not closed or it wasn't a tag?!
+        $n = $len;
+      }
+      else {
+        ++$n;  // skip the '>' character
+      }
+    }
 
-  // If no complete paragraph then treat line breaks as paragraphs.
-  $line_breaks = array('<br />' => 6, '<br>' => 4);
-  // Newline only indicates a line break if line break converter
-  // filter is present.
-  if (isset($filters['filter/1'])) {
-    $line_breaks["\n"] = 1;
-  }
-  $break_points[] = $line_breaks;
+    // any characters to add the to result?
+    if ($a) {
+      if ($l + $a >= $size) {
+        // the last tag did not make it in
+        if ($last_tag) {
+          array_pop($s);
+        }
+        // we've got more than we want to, search for a break point
+        $o = $p + $size - $l;
+        if ($body[$o] != ' ') while ($o > $p) {
+          switch ($body[$o - 1]) {
+          case "\xD8": // "\xD8\x9F" == arabic '?' (right to left)
+            if (!isset($body[$o]) || $body[$o] != "\x9F") {
+              // no the right sequence
+              break;
+            }
+            if ($o + 1 == $len || $body[$o + 1] == ' ') {
+              // found a break-point
+              break 2;
+            }
+            if ($body[$o + 1] == '"') {
+              $o += 2;
+              break 2;
+            }
+            break;
 
-  // If the first paragraph is too long, split at the end of a sentence.
-  $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);
+          case '.':
+          case '!':
+          case '?':
+            if ($o == $len || $body[$o] == ' ') {
+              // found a break-point
+              break 2;
+            }
+            if ($body[$o] == '"') {
+              ++$o;
+              break 2;
+            }
+            break;
 
-  // Iterate over the groups of break points until a break point is found.
-  foreach ($break_points as $points) {
-    // Look for each break point, starting at the end of the teaser.
-    foreach ($points as $point => $offset) {
-      // The teaser is already reversed, but the break point isn't.
-      $rpos = strpos($reversed, strrev($point));
-      if ($rpos !== FALSE) {
-        $min_rpos = min($rpos + $offset, $min_rpos);
+          case "\n":
+            if (!$filter_newline) {
+              break;
+            }
+          case ' ':
+            // found and remove the space (not that we ignore no-break spaces since we're not supposed to break there)
+            --$o;
+            break 2;
+
+          //case ... add support for other UTF-8 spaces?
+
+          case "\xE3":
+            // found the CJK ideographic full stop?
+            if (isset($body[$o + 1]) && $body[$o] == "\x80" && $body[$o + 1] == "\x82") {
+              // keep this character in full
+              $o += 2;
+              break 2;
+            }
+            break;
+
+          }
+          --$o;
+        }
+        $p = $o;
+        break;
       }
+      $l += $a;
     }
 
-    // If a break point was found in this group, slice and return the teaser.
-    if ($min_rpos !== $max_rpos) {
-      // Don't slice with length 0.  Length must be <0 to slice from RHS.
-      return ($min_rpos === 0) ? $teaser : substr($teaser, 0, 0 - $min_rpos);
-    }
+    $p = $n;
   }
 
-  // If a break point was not found, still return a teaser.
-  return $teaser;
+  $result = substr($body, 0, $p);
+  while (!empty($s)) {
+    $result .= '</' . array_pop($s) . '>';
+  }
+
+  return $result;
 }
 
 /**
