The preemptive test to see if $string is "too long" shouldn't add strlen($replacement) to $max. $max should represent the absolute maximum length of string returned. The size of the $replacement is irrelevant in that determination.
The rest of the function (unchanged below) operates as defined above. Meaning, the size of the $replacement is subtracted from the $max, so that the returned string is exactly the length of $max.
<?php
function truncate($string, $max = 20, $replacement = '')
{
if (strlen($string) <= $max)
{
return $string;
}
$leave = $max - strlen ($replacement);
return substr_replace($string, $replacement, $leave);
}
?>