I had a similar difficulty, in that I wanted to use wrapping, but only where I specified it to be wrapped with a newline character.
My solution was to resize the column width based upon the longest string I didn't want wrapped, and then let the wrapping do the rest.
Feel free to try the following, or modify it to suit your purposes.
private void dataGridView_TextItems_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
    {
        if (sender is DataGridView dgv && dgv.Rows[e.RowIndex].Cells[e.ColumnIndex] is DataGridViewTextBoxCell cell)
        {
            SetDGVTextBoxColumnWidth(dgv.Columns[e.ColumnIndex] as DataGridViewTextBoxColumn);
        }
    }
    private void SetDGVTextBoxColumnWidth(DataGridViewTextBoxColumn column)
    {
        if (column != null)
        {
            DataGridView dgv = column.DataGridView;
            Graphics g = dgv.CreateGraphics();
            Font font = dgv.Font;
            // Acquire all the relevant cells - the whole column's collection of cells:
            List<DataGridViewTextBoxCell> cells = new List<DataGridViewTextBoxCell>();
            foreach (DataGridViewRow row in column.DataGridView.Rows)
            {
                cells.Add(row.Cells[column.Index] as DataGridViewTextBoxCell);
            }
            // Now find the widest cell:
            int widestCellWidth = g.MeasureString(column.HeaderText, font).ToSize().Width;  // Start with the header text, but for some reason this seems a bit short.
            bool foundNewline = false;
            foreach (DataGridViewTextBoxCell cell in cells)
            {
                font = ((cell.Style.Font != null) ? cell.Style.Font : dgv.Font);  // The font may change between cells.
                string cellText = cell.Value.ToString().Replace("\r","");  // Ignore any carriage return characters.  
                if (cellText.Contains('\n'))
                {
                    foundNewline = true;
                    cell.Style.WrapMode = DataGridViewTriState.True;  // This allows newlines in the cell's text to be recognised.
                    string[] lines = cellText.Split('\n');
                    foreach (string line in lines)
                    {
                        int textWidth = g.MeasureString(line + "_", font).ToSize().Width;  // A simple way to ensure that there is room for this text.
                        widestCellWidth = Math.Max(widestCellWidth, textWidth);
                    }
                }
                else
                {
                    int textWidth = g.MeasureString(cellText + "_", font).ToSize().Width;
                    widestCellWidth = Math.Max(widestCellWidth, textWidth);
                }
            }
            if (foundNewline)
            {
                column.AutoSizeMode = DataGridViewAutoSizeColumnMode.None;  // Allows us to programatically modify the column width.
                column.Width = widestCellWidth;  // Simply set the desired width.
            }
            else
            {
                column.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;  // Allow the system to do the work for us.  This does a better job with cell headers.
            }
            column.Resizable = DataGridViewTriState.False;  // We don't wish the User to modify the width of this column manually.
        }
    }