There is (as of Joomla 3.9...) an annoying bug in Joomla core with the JHtml Helper function TruncString()

This bug was reported in j2.5 and still hasn't been fixed.
If the truncate point is between a tag pair, then string.truncate closes the hanging tag but puts the ellipsis after the tag closure. 

<?php $text1='test without tags';
      $text2='<h4>test with tags</h4>';
      echo 'text1: ';
      echo JHtml::_('string.truncate', $text1, 15);
      echo '<p>--</p>';
      echo 'text2: ';
      echo JHtml::_('string.truncate', $text2, 15);    

Expected result

ellipsis should be within the tag that is closed

<p>Truncate test</p>
text1: test without...<p>--</p>text2: <h4>test with...</h4>

Actual result

text1: test without...<p>--</p>text2: <h4>test with</h4>...

The ellipsis is after the tag is closed which screws up the display if it is an h or p tag
The result is the same with truncateComplex (the difference is that truncateComplex doesn't count the chars inside the <> of a tag when working out where to cut the string).

Additional comments

there might be some cases where it makes sense to have the ellipsis outside the tag that is closed by truncate. eg if the truncate comes in the first part of an <a tag or inside an <img tag.
Currently this

      $text3='<p>test <a href="#">with</a> anchor</p>';
      echo 'text3: ';
      echo JHtml::_('string.truncate', $text3, 15);

gives this result

text3: <p>test <a< p="">...</a<></p>

which is clearly garbage.
For this particular case the truncateComplex method does correctly close the <a tag but it still puts the ellipsis after the final </p> which it also adds.

There are actually four interrelated problems

  1. If $noSplit is true (whether or not $allowHtml is true) then
    a. if the break point is just after the space after a word then the last word is cut - it should be left intact
    b. if the split point is just after the last character of a word then the last word is cut - it should be left intact.
    (In both these cases there is a question as to how punctuation should be handled, but this could be debated and dealt with in truncateComplex)

  2. If $allowHtml is true and the string starts with a tag (eg <p>...) then
    a. if the break occurs in the middle of a tag then the html output is fragmented
    b. the ellipsis is placed outside the closing tag (eg </p> which is correctly added on the end
    (NB both of these cases only occur when the start of the string is a tag )

1a. can be addressed by :
a. not trimming the left end of the truncated string at line 64. Use ltrim
1b. can be addressed by:
a. adding a test to see if the next char after the break point is a space and if it is including it
(NB the final string is left trimmed anyway at line 137 when it is copied back to return)

2a. can be addressed by:
a. move the code block to cut off any tag fragments from line 127-132 up to line 106 before we close any complete tags that are open
2b can be addressed thus:
a. add the ellipsis after any tag fragments are stripped and before tags are closed (just after the moved block above)
b. remove the addition of ellipsis at line 137 and add an else case to the if ($allowHtml) block lines 91-133 to add an ellipsis in the case that $allowHtml is false.

The good news is that so long as your string doesn't start with a tag it works correctly.
I think truncateComplex has similar problems.