private static function insertEntities(
        &$string,
        &$matches,
        $hole_start = -1,
        $hole_end = -1,
        $hole_length = 0
    ) {
        for ($i = 0; $i < count($matches[0]); $i++) {
            $entity = $matches[0][$i][0];
            $position = $matches[0][$i][1];

            // offsets are byte offsets, not character offsets
            $substr = mb_substr($string, 0, $position, '8bit');
            $byte_len = mb_strlen($substr, '8bit');
            $char_len = mb_strlen($substr);
            $position -= $byte_len - $char_len;

            if ($position < $hole_start) {
                // this entity falls before the hole
                $string =
                    mb_substr($string, 0, $position) .
                    $entity .
                    mb_substr($string, $position + 1);

                $hole_start += mb_strlen($entity);
                $hole_end += mb_strlen($entity);
            } elseif ($hole_end <= $hole_start) {
                // break here because all remaining entities fall in the hole
                // extending infinitely to the right
                break;
            } elseif ($position >= $hole_end) {
                // this entity falls after the hole
                $offset = -$hole_end + $hole_length + $hole_start + 1;
                $string =
                    mb_substr($string, 0, $position + $offset) .
                    $entity .
                    mb_substr($string, $position + $offset + 1);
            } else {
                // this entity falls in the hole but we must account
                // for its unused size
                $hole_end += mb_strlen($entity);
            }
        }
    }