diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php
index d97975b..60ba570 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php
@@ -19,7 +19,7 @@ class SearchExcerptTest extends WebTestBase {
    *
    * @var array
    */
-  public static $modules = array('search');
+  public static $modules = array('search', 'search_langcode_test');
 
   public static function getInfo() {
     return array(
@@ -50,6 +50,10 @@ function testSearchExcerpt() {
     $result = preg_replace('| +|', ' ', search_excerpt('fox', $text));
     $this->assertEqual($result, 'The quick brown <strong>fox</strong> &amp; jumps over the lazy dog ...', 'Found keyword is highlighted');
 
+    $expected = '<strong>The</strong> quick brown fox &amp; jumps over the lazy dog';
+    $result = preg_replace('| +|', ' ', search_excerpt('The', $text));
+    $this->assertEqual(preg_replace('| +|', ' ', $result), $expected, 'Keyword is highlighted at beginning of short string');
+
     $longtext = str_repeat($text . ' ', 10);
     $result = preg_replace('| +|', ' ', search_excerpt('nothing', $text));
     $this->assertTrue(strpos($result, $expected) === 0, 'When keyword is not found in long string, return value starts as expected');
@@ -101,10 +105,10 @@ function testSearchExcerptSimplified() {
 
     // Test phrases with characters which are being truncated.
     $result = preg_replace('| +|', ' ', search_excerpt('"ipsum _"', $text));
-    $this->assertTrue(strpos($result, '<strong>ipsum </strong>') !== FALSE, 'Only valid part of the phrase is highlighted and invalid part containing "_" is ignored.');
+    $this->assertTrue(strpos($result, '<strong>ipsum</strong>') !== FALSE, 'Only valid part of the phrase is highlighted and invalid part containing "_" is ignored.');
 
     $result = preg_replace('| +|', ' ', search_excerpt('"ipsum 0000"', $text));
-    $this->assertTrue(strpos($result, '<strong>ipsum </strong>') !== FALSE, 'Only valid part of the phrase is highlighted and invalid part "0000" is ignored.');
+    $this->assertTrue(strpos($result, '<strong>ipsum</strong>') !== FALSE, 'Only valid part of the phrase is highlighted and invalid part "0000" is ignored.');
 
     // Test combination of the valid keyword and keyword containing only
     // characters which are being truncated during simplification.
@@ -113,5 +117,24 @@ function testSearchExcerptSimplified() {
 
     $result = preg_replace('| +|', ' ', search_excerpt('ipsum 0000', $text));
     $this->assertTrue(strpos($result, '<strong>ipsum</strong>') !== FALSE, 'Only valid keyword is highlighted and invalid keyword "0000" is ignored.');
+
+    // Test using the hook_search_preprocess() from the test module.
+    // The hook replaces "finding" or "finds" with "find".
+    // So, if we search for "find" or "finds" or "finding", we should
+    // highlight "finding".
+    $text = "this tests finding a string";
+    $result = preg_replace('| +|', ' ', search_excerpt('finds', $text, 'ex'));
+    $this->assertTrue(strpos($result, '<strong>finding</strong>') !== FALSE, 'Search excerpt works with preprocess hook, search for finds');
+    $result = preg_replace('| +|', ' ', search_excerpt('find', $text, 'ex'));
+    $this->assertTrue(strpos($result, '<strong>finding</strong>') !== FALSE, 'Search excerpt works with preprocess hook, search for find');
+
+    // Just to be sure, test with the replacement at the beginning and end.
+    $text = "finding at the beginning";
+    $result = preg_replace('| +|', ' ', search_excerpt('finds', $text, 'ex'));
+    $this->assertTrue(strpos($result, '<strong>finding</strong>') !== FALSE, 'Search excerpt works with preprocess hook, text at start');
+
+    $text = "at the end finding";
+    $result = preg_replace('| +|', ' ', search_excerpt('finds', $text, 'ex'));
+    $this->assertTrue(strpos($result, '<strong>finding</strong>') !== FALSE, 'Search excerpt works with preprocess hook, text at end');
   }
 }
diff --git a/core/modules/search/search.module b/core/modules/search/search.module
index 375c43f..45c547f 100644
--- a/core/modules/search/search.module
+++ b/core/modules/search/search.module
@@ -1096,9 +1096,10 @@ function search_data($keys, $module, $conditions = NULL) {
  *
  * @param $keys
  *   A string containing a search query.
- *
  * @param $text
  *   The text to extract fragments from.
+ * @param $langcode
+ *   The language code to use.
  *
  * @return
  *   A string containing HTML for the excerpt.
@@ -1114,6 +1115,8 @@ function search_excerpt($keys, $text, $langcode = NULL) {
   // Prepare text by stripping HTML tags and decoding HTML entities.
   $text = strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text));
   $text = decode_entities($text);
+  // And make sure it starts with a space, because the code below assumes it.
+  $text = ' ' . $text;
 
   // Slash-escape quotes in the search keyword string.
   array_walk($keys, '_search_excerpt_replace');
@@ -1189,8 +1192,8 @@ function search_excerpt($keys, $text, $langcode = NULL) {
   if (count($ranges) == 0) {
     // We didn't find any keyword matches, so just return the first part of the
     // text. We also need to re-encode any HTML special characters that we
-    // entity-decoded above.
-    return check_plain(truncate_utf8($text, 256, TRUE, TRUE));
+    // entity-decoded above, and remove the space we added.
+    return check_plain(truncate_utf8(substr($text, 1), 256, TRUE, TRUE));
   }
 
   // Sort the text ranges by starting position.
@@ -1218,6 +1221,9 @@ function search_excerpt($keys, $text, $langcode = NULL) {
   // Fetch text
   $out = array();
   foreach ($newranges as $from => $to) {
+    // Make sure not to start at the first character, since this was a space
+    // we added.
+    $from = ($from == 0) ? 1 : $from;
     $out[] = substr($text, $from, $to - $from);
   }
 
@@ -1269,8 +1275,8 @@ function _search_excerpt_replace(&$text) {
  */
 function search_simplify_excerpt_match($key, $text, $offset, $boundary, $langcode = NULL) {
   $pos = NULL;
-  $simplified_key = search_simplify($key, $langcode);
-  $simplified_text = search_simplify($text, $langcode);
+  $simplified_key = trim(search_simplify($key, $langcode));
+  $simplified_text = ' ' . search_simplify($text, $langcode) . ' ';
 
   // Return immediately if simplified key or text are empty.
   if (!$simplified_key || !$simplified_text) {
@@ -1285,34 +1291,36 @@ function search_simplify_excerpt_match($key, $text, $offset, $boundary, $langcod
   // If we get here, we have a match. Now find the exact location of the match
   // and the original text that matched. Start by splitting up the text by all
   // potential starting points of the matching text and iterating through them.
-  $split = array_filter(preg_split('/' . $boundary . '/iu', $text, -1, PREG_SPLIT_OFFSET_CAPTURE), '_search_excerpt_match_filter');
-  foreach ($split as $value) {
+  $split = array_values(array_filter(preg_split('/' . $boundary . '/iu', $text, -1, PREG_SPLIT_OFFSET_CAPTURE), '_search_excerpt_match_filter'));
+  // Add a fake entry to the end, for purposes of our for loops below.
+  $split[] = array('', strlen($text) + 1);
+  $numsplits = count($split);
+
+  for ($split_index = 0; $split_index < $numsplits; $split_index++) {
+    $value = $split[$split_index];
+
     // Skip starting points before the offset.
     if ($value[1] < $offset) {
       continue;
     }
 
-    // Check a window of 80 characters after the starting point for a match,
-    // based on the size of the excerpt window.
+    // See if there is a match starting here, using the next 80 characters.
     $window = substr($text, $value[1], 80);
-    $simplified_window = search_simplify($window);
+    $simplified_window = search_simplify($window, $langcode);
     if (strpos($simplified_window, $simplified_key) === 0) {
-      // We have a match in this window. Store the position of the match.
-      $pos = $value[1];
-      // Iterate through the text in the window until we find the full original
-      // matching text.
-      $length = strlen($window);
-      for ($i = 1; $i <= $length; $i++) {
-        $keyfound = substr($text, $value[1], $i);
-        if ($simplified_key == search_simplify($keyfound)) {
+      // We have a match. See if we can find a substring of complete words that
+      // simplifies down to our text.
+      for ($end_index = $split_index + 1; $end_index < $numsplits; $end_index++) {
+        $keyfound = substr($text, $value[1], $split[$end_index][1] - $value[1] - 1);
+        if ($simplified_key == search_simplify($keyfound, $langcode)) {
+          $pos = $value[1];
           break;
         }
       }
-      break;
     }
   }
 
-  return $pos ? array('where' => $pos, 'keyword' => $keyfound) : FALSE;
+  return isset($pos) ? array('where' => $pos, 'keyword' => $keyfound) : FALSE;
 }
 
 /**
diff --git a/core/modules/search/tests/modules/search_langcode_test/search_langcode_test.module b/core/modules/search/tests/modules/search_langcode_test/search_langcode_test.module
index 3b52753..5ab44ba 100644
--- a/core/modules/search/tests/modules/search_langcode_test/search_langcode_test.module
+++ b/core/modules/search/tests/modules/search_langcode_test/search_langcode_test.module
@@ -23,6 +23,12 @@ function search_langcode_test_search_preprocess($text, $langcode = NULL) {
   // Prints the langcode for testPreprocessLangcode().
   elseif (isset($langcode)) {
     drupal_set_message('Langcode Preprocess Test: ' . $langcode);
+
+    // Preprocessing for the excerpt test.
+    if ($langcode == 'ex') {
+      $text = str_replace('finding', 'find', $text);
+      $text = str_replace('finds', 'find', $text);
+    }
   }
   return $text;
 }
