// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
// SPDX-FileCopyrightText: Copyright 2008 Sandia Corporation
// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-Sandia-USGov

#include "vtkPVScalarBarActor.h"
#include "vtkScalarBarActorInternal.h"

#include "vtkAxis.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkContextScene.h"
#include "vtkDiscretizableColorTransferFunction.h"
#include "vtkDoubleArray.h"
#include "vtkFloatArray.h"
#include "vtkImageData.h"
#include "vtkMath.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper2D.h"
#include "vtkProperty2D.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkScalarsToColors.h"
#include "vtkSmartPointer.h"
#include "vtkStringArray.h"
#include "vtkStringFormatter.h"
#include "vtkTextActor.h"
#include "vtkTextProperty.h"
#include "vtkTexture.h"
#include "vtkUnsignedCharArray.h"
#include "vtkWindow.h"

#include <cmath>

#include <algorithm>
#include <sstream>

#define COLOR_TEXTURE_MAP_SIZE 256

#define MY_ABS(x) ((x) < 0 ? -(x) : (x))

//=============================================================================
vtkStandardNewMacro(vtkPVScalarBarActor);

//=============================================================================
vtkPVScalarBarActor::vtkPVScalarBarActor()
{
  this->AspectRatio = 20.0;
  this->AutomaticLabelFormat = 1;
  this->DrawTickMarks = 1;
  this->DrawSubTickMarks = 1;
  this->AddRangeLabels = 1;
  this->RangeLabelFormat = nullptr;
  this->SetRangeLabelFormat("{:4.3e}");
  this->TitleJustification = VTK_TEXT_CENTERED;
  this->AddRangeAnnotations = 1;
  this->AnnotationTextScaling = 1;
  this->SetVerticalTitleSeparation(4);
  this->AutomaticAnnotations = 0;

  this->ScalarBarTexture = vtkTexture::New();

  this->TickMarks = vtkPolyData::New();
  this->TickMarksMapper = vtkPolyDataMapper2D::New();
  this->TickMarksMapper->SetInputData(this->TickMarks);
  this->TickMarksActor = vtkActor2D::New();
  this->TickMarksActor->SetMapper(this->TickMarksMapper);
  this->TickMarksActor->GetPositionCoordinate()->SetReferenceCoordinate(this->PositionCoordinate);

  this->TickLayoutHelper->SetBehavior(vtkAxis::FIXED);
  this->TickLayoutHelper->SetScene(this->TickLayoutHelperScene.GetPointer());
}

//-----------------------------------------------------------------------------
vtkPVScalarBarActor::~vtkPVScalarBarActor()
{
  this->ScalarBarTexture->Delete();

  this->TickMarks->Delete();
  this->TickMarksMapper->Delete();
  this->TickMarksActor->Delete();

  if (this->ComponentTitle)
  {
    delete[] this->ComponentTitle;
    this->ComponentTitle = nullptr;
  }

  delete[] this->RangeLabelFormat;
  this->RangeLabelFormat = nullptr;
}

//-----------------------------------------------------------------------------
void vtkPVScalarBarActor::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "AspectRatio: " << this->AspectRatio << endl;
  os << indent << "AutomaticLabelFormat: " << this->AutomaticLabelFormat << endl;
  os << indent << "DrawTickMarks: " << this->DrawTickMarks << endl;
  os << indent << "DrawSubTickMarks: " << this->DrawSubTickMarks << endl;
  os << indent << "AddRangeLabels: " << this->AddRangeLabels << endl;
  os << indent
     << "RangeLabelFormat: " << (this->RangeLabelFormat ? this->RangeLabelFormat : "(null)")
     << endl;
  os << indent << "ScalarBarTexture: ";
  if (this->ScalarBarTexture)
  {
    this->ScalarBarTexture->PrintSelf(os << "\n", indent.GetNextIndent());
  }
  else
  {
    os << "(null)\n";
  }
  os << indent << "TickMarks: ";
  if (this->TickMarks)
  {
    this->TickMarks->PrintSelf(os << "\n", indent.GetNextIndent());
  }
  else
  {
    os << "(null)\n";
  }
  os << indent << "TickMarksMapper: ";
  if (this->TickMarksMapper)
  {
    this->TickMarksMapper->PrintSelf(os << "\n", indent.GetNextIndent());
  }
  else
  {
    os << "(null)\n";
  }
  os << indent << "TickMarksActor: ";
  if (this->TickMarksActor)
  {
    this->TickMarksActor->PrintSelf(os << "\n", indent.GetNextIndent());
  }
  else
  {
    os << "(null)\n";
  }
  os << indent << "LabelSpace: " << this->LabelSpace << endl;
  os << indent << "TitleJustification: " << this->TitleJustification << endl;
  os << indent << "AddRangeAnnotations: " << this->AddRangeAnnotations << endl;
  os << indent << "AutomaticAnnotations: " << this->AutomaticAnnotations << endl;
}

//-----------------------------------------------------------------------------
void vtkPVScalarBarActor::ReleaseGraphicsResources(vtkWindow* window)
{
  this->ScalarBarTexture->ReleaseGraphicsResources(window);

  for (unsigned int i = 0; i < this->P->TextActors.size(); i++)
  {
    this->P->TextActors[i]->ReleaseGraphicsResources(window);
  }

  this->TickMarksActor->ReleaseGraphicsResources(window);

  this->Superclass::ReleaseGraphicsResources(window);
}

//----------------------------------------------------------------------------
int vtkPVScalarBarActor::RenderOpaqueGeometry(vtkViewport* viewport)
{
  // This ensures that tile scaling can be accounted for in the tick layout:
  if (vtkRenderer* renderer = vtkRenderer::SafeDownCast(viewport))
  {
    this->TickLayoutHelperScene->SetRenderer(renderer);
  }

  return this->Superclass::RenderOpaqueGeometry(viewport);
}

//----------------------------------------------------------------------------
int vtkPVScalarBarActor::RenderOverlay(vtkViewport* viewport)
{
  int renderedSomething = this->Superclass::RenderOverlay(viewport);
  if (this->LookupTable && this->LookupTable->GetIndexedLookup())
  {
    return renderedSomething;
  }

  if (this->DrawTickMarks)
  {
    renderedSomething += this->TickMarksActor->RenderOverlay(viewport);
  }

  return renderedSomething;
}

//-----------------------------------------------------------------------------
int vtkPVScalarBarActor::CreateLabel(
  double value, int minDigits, int targetWidth, int targetHeight, vtkViewport* viewport)
{
  char string[1024];

  vtkNew<vtkTextActor> textActor;
  textActor->GetProperty()->DeepCopy(this->GetProperty());
  textActor->GetPositionCoordinate()->SetReferenceCoordinate(this->PositionCoordinate);

  // Copy the text property here so that the size of the label prop is not
  // affected by the automatic adjustment of its text mapper's size.
  textActor->GetTextProperty()->ShallowCopy(this->LabelTextProperty);
  if (this->P->Viewport)
  {
    textActor->SetTextScaleModeToViewport();
    textActor->ComputeScaledFont(this->P->Viewport);
  }

// One Visual Studio prior 2015, formats with exponents have three digits by default
// whereas on other systems, exponents have two digits. Set to two
// digits on Windows for consistent behavior.
#if defined(_MSC_VER) && (_MSC_VER < 1900)
  unsigned int oldWin32ExponentFormat = _set_output_format(_TWO_DIGIT_EXPONENT);
#endif

  if (this->AutomaticLabelFormat)
  {
    // Iterate over all format lengths and find the highest precision that we
    // can represent without going over the target width.  If we cannot fit
    // within the target width, make the smallest possible text.
    int smallestFoundWidth = VTK_INT_MAX;
    bool foundValid = false;
    string[0] = '\0';
    for (int i = 1 + minDigits; i < 6; i++)
    {
      char format[512];
      char string2[1024];
      auto result = vtk::format_to_n(format, 511, "{{-0.{:d}g}}", i);
      *result.out = '\0';
      result = vtk::format_to_n(string2, 1023, std::string_view(format), value);
      *result.out = '\0';

      // we want the reduced size used so that we can get better fitting
      // Extra filter: Used to remove unwanted 0 after e+ or e-
      // i.e.: 1.23e+009 => 1.23e+9
      std::string strToFilter = string2;
      std::string ePlus = "e+0";
      std::string eMinus = "e-0";
      size_t pos = 0;
      while ((pos = strToFilter.find(ePlus)) != std::string::npos ||
        (pos = strToFilter.find(eMinus)) != std::string::npos)
      {
        strToFilter.erase(pos + 2, 1);
      }
      strcpy(string2, strToFilter.c_str());
      textActor->SetInput(string2);

      textActor->SetConstrainedFontSize(viewport, VTK_INT_MAX, targetHeight);
      double tsize[2];
      textActor->GetSize(viewport, tsize);
      if (tsize[0] < targetWidth)
      {
        // Found a string that fits.  Keep it unless we find something better.
        strcpy(string, string2);
        foundValid = true;
      }
      else if ((tsize[0] < smallestFoundWidth) && !foundValid)
      {
        // String does not fit, but it is the smallest so far.
        strcpy(string, string2);
        smallestFoundWidth = tsize[0];
      }
    }
  }
  else
  {
    // Potential of buffer overrun (onto the stack) here.
    VTK_FORMAT_IF_ERROR_RETURN(
      auto result = vtk::format_to_n(string, 1023, this->LabelFormat, value);
      *result.out = '\0', 0);
  }

  // Set the txt label
  textActor->SetInput(string);

#if defined(_MSC_VER) && (_MSC_VER < 1900)
  _set_output_format(oldWin32ExponentFormat);
#endif

  // Size the font to fit in the targetHeight, which we are using
  // to size the font because it is (relatively?) constant.
  int fontSize = textActor->SetConstrainedFontSize(viewport, VTK_INT_MAX, targetHeight);
  int maxFontSize = this->LabelTextProperty->GetFontSize();
  if (fontSize > maxFontSize)
  {
    textActor->GetTextProperty()->SetFontSize(maxFontSize);
  }

  // Make sure that the string fits in the allotted space.
  double tsize[2];
  textActor->GetSize(viewport, tsize);
  if (tsize[0] > targetWidth)
  {
    fontSize = textActor->SetConstrainedFontSize(viewport, targetWidth, targetHeight);
  }

  this->P->TextActors.push_back(textActor.GetPointer());
  return static_cast<int>(this->P->TextActors.size()) - 1;
}

//----------------------------------------------------------------------------
void vtkPVScalarBarActor::PrepareTitleText()
{
  // Let the superclass prepare the actor:
  this->Superclass::PrepareTitleText();

  // Set font scaling
  if (this->P->Viewport)
  {
    this->TitleActor->ComputeScaledFont(this->P->Viewport);
    this->TitleActor->SetTextScaleModeToViewport();
  }
}

//----------------------------------------------------------------------------
void vtkPVScalarBarActor::ComputeScalarBarThickness()
{
  double aspectRatio = this->AspectRatio;
  if (aspectRatio <= 0.)
  {
    aspectRatio = 20.;
  }

  // Make the bar's thickness a fraction of its length.
  this->P->ScalarBarBox.Size[0] = static_cast<int>(ceil(this->P->Frame.Size[1] / aspectRatio));
  // Make tick marks half the thickness of the scalar bar (+1 to ensure non-zero size).
  this->LabelSpace = this->P->ScalarBarBox.Size[0] / 2 + 1;

  this->P->ScalarBarBox.Posn = this->P->Frame.Posn;

  // Force opacity to match lookup table
  vtkDiscretizableColorTransferFunction* lut =
    vtkDiscretizableColorTransferFunction::SafeDownCast(this->LookupTable);
  if (lut)
  {
    this->SetUseOpacity(lut->IsOpaque() ? 0 : 1);
  }
}

//----------------------------------------------------------------------------
void vtkPVScalarBarActor::LayoutTitle()
{
  if ((this->Title == nullptr || !strlen(this->Title)) &&
    (this->ComponentTitle == nullptr || !strlen(this->ComponentTitle)))
  {
    this->P->TitleBox.Size[0] = this->P->TitleBox.Size[1] = 0;
    return;
  }

  // Reset the text size and justification
  this->TitleActor->GetTextProperty()->ShallowCopy(this->TitleTextProperty);
  this->TitleActor->GetTextProperty()->SetJustification(this->TitleJustification);
  this->TitleActor->GetTextProperty()->SetVerticalJustification(
    this->Orientation == VTK_ORIENT_VERTICAL
      ? VTK_TEXT_BOTTOM
      : (this->TextPosition == vtkScalarBarActor::PrecedeScalarBar ? VTK_TEXT_BOTTOM
                                                                   : VTK_TEXT_TOP));

  double titleSize[2];
  // Get the actual size of the text.
  this->TitleActor->GetSize(this->P->Viewport, titleSize);
  // Now, determine how much space should be reserved for the title and its padding.
  // For the horizontal orientation, the font size is exactly as specified by the user.
  // In the vertical case, we limit the font size so that the remaining box has
  // at least some space (25%) for the scalar bar.
  if (this->Orientation == VTK_ORIENT_VERTICAL &&
    (1.5 * titleSize[1] + 3 * this->TextPad > 0.75 * this->P->Frame.Size[1]))
  { // title takes up 3/4 or more of the frame... better reduce font size
    this->TitleActor->SetConstrainedFontSize(
      this->P->Viewport, VTK_INT_MAX, 0.5 * this->P->Frame.Size[1] - 3 * this->TextPad);
    this->TitleActor->GetSize(this->P->Viewport, titleSize);
  }
  this->P->TitleBox.Size[this->P->TL[0]] = titleSize[0];
  this->P->TitleBox.Size[this->P->TL[1]] = 1.5 * (titleSize[1] + this->TextPad);

  // Position the title.
  switch (this->TitleActor->GetTextProperty()->GetJustification())
  {
    case VTK_TEXT_LEFT:
      this->P->TitleBox.Posn[0] = this->P->Frame.Posn[0] + this->TextPad;
      break;
    case VTK_TEXT_RIGHT:
      this->P->TitleBox.Posn[0] =
        this->P->Frame.Posn[0] + this->P->Frame.Size[this->P->TL[0]] - this->TextPad - titleSize[0];
      break;
    case VTK_TEXT_CENTERED:
    default:
      this->P->TitleBox.Posn[0] =
        this->P->Frame.Posn[0] + (this->P->Frame.Size[this->P->TL[0]] - titleSize[0]) / 2;
      break;
  }
  if (this->Orientation == VTK_ORIENT_VERTICAL)
  { // The title is stacked above the scalar bar.
    this->P->TitleBox.Posn[1] = this->P->Frame.Posn[1] + this->P->Frame.Size[this->P->TL[1]] -
      this->P->TitleBox.Size[this->P->TL[1]] / 1.5;
    this->P->ScalarBarBox.Size[this->P->TL[1]] -= 2 * this->TextPad;
  }
  else // VTK_ORIENT_HORIZONTAL
  {
    // The title is above or below the ticks,
    // which either precede or succeed the scalar bar.
    // We don't know the tick size yet, but we can push the title or scalar
    // bar away from the frame's origin as required. Then LayoutTicks can
    // further adjust positions.
    if (this->TextPosition == vtkScalarBarActor::PrecedeScalarBar)
    {
      this->P->TitleBox.Posn[1] = this->P->Frame.Posn[1];
      // push the scalar bar up by the title height.
      this->P->ScalarBarBox.Posn[1] += this->P->TitleBox.Size[0] + this->TextPad;
      this->P->NanBox.Posn[1] += this->TextPad + this->P->TitleBox.Size[0];
    }
    else
    {
      this->P->TitleBox.Posn[1] =
        this->P->Frame.Posn[1] + this->P->ScalarBarBox.Size[0] + this->TextPad;
    }
  }
}

//----------------------------------------------------------------------------
void vtkPVScalarBarActor::ComputeScalarBarLength()
{
  this->Superclass::ComputeScalarBarLength();
}

//----------------------------------------------------------------------------
void vtkPVScalarBarActor::LayoutTicks()
{
  if (this->LookupTable->GetIndexedLookup())
  { // no tick marks in indexed lookup mode.
    this->NumberOfLabelsBuilt = 0;
    return;
  }

  // Figure out the precision to use based on the width of the scalar bar.
  vtkNew<vtkTextActor> dummyActor;
  dummyActor->GetTextProperty()->ShallowCopy(this->LabelTextProperty);
  dummyActor->SetInput("()"); // parentheses are taller than numbers for all useful fonts.
  if (this->P->Viewport)
  {
    dummyActor->SetTextScaleModeToViewport();
    dummyActor->ComputeScaledFont(this->P->Viewport);
  }

  double tsize[2];
  dummyActor->GetSize(this->P->Viewport, tsize);
  // Now constrain the width of the text with our target height:
  int targetHeight = static_cast<int>(ceil(tsize[1]));

  if (this->Orientation == VTK_ORIENT_VERTICAL)
  {
    this->P->TickBox.Size[0] = std::max(0, this->P->Frame.Size[0] - this->P->ScalarBarBox.Size[0]);
    this->P->TickBox.Size[1] = this->P->ScalarBarBox.Size[1];
    this->P->TickBox.Posn = this->P->ScalarBarBox.Posn;
    if (this->TextPosition == vtkScalarBarActor::PrecedeScalarBar)
    {
      this->P->ScalarBarBox.Posn[0] += this->P->TickBox.Size[0];
      this->P->NanBox.Posn[0] += this->P->TickBox.Size[0];
    }
    else
    {
      this->P->TickBox.Posn[0] += this->P->ScalarBarBox.Size[0];
    }

    /* FYI, this is how height is used in ConfigureTicks:
    int maxHeight = static_cast<int>(
      this->P->ScalarBarBox.Size[1] / this->NumberOfLabels);
    targetHeight = std::min(targetHeight, maxHeight);
    */
  }
  else
  {
    this->P->TickBox.Size[0] = this->LabelSpace + 2 + targetHeight;
    this->P->TickBox.Size[1] = this->P->ScalarBarBox.Size[1];
    if (this->TextPosition == PrecedeScalarBar)
    { // Push scalar bar and NaN swatch up to make room for ticks.
      this->P->TickBox.Posn = this->P->ScalarBarBox.Posn;
      this->P->ScalarBarBox.Posn[1] += this->P->TickBox.Size[0];
      this->P->NanBox.Posn[1] += this->P->TickBox.Size[0];
    }
    else
    {
      this->P->TickBox.Posn[0] = this->P->ScalarBarBox.Posn[0];
      this->P->TickBox.Posn[1] = this->P->TitleBox.Posn[1];
      this->P->TitleBox.Posn[1] += this->P->TickBox.Size[0];
    }
  }
}

void vtkPVScalarBarActor::ConfigureAnnotations()
{
  // Work around an issue in VTK.
  if (this->P->Labels.empty())
  {
    this->P->AnnotationBoxes->Initialize();
  }
  this->Superclass::ConfigureAnnotations();
}

void vtkPVScalarBarActor::ConfigureTitle()
{
  // This adjustment is necessary because this->TitleActor adjusts
  // where the text is rendered relative to the position of the
  // TitleActor based on the justification set on the TitleActor's
  // TextProperty.
  double texturePolyDataBounds[6];
  this->TexturePolyData->GetBounds(texturePolyDataBounds);

  double scalarBarMinX = texturePolyDataBounds[0];
  double scalarBarMaxX = texturePolyDataBounds[1];
  double x;
  switch (this->TitleActor->GetTextProperty()->GetJustification())
  {
    case VTK_TEXT_LEFT:
      if (this->Orientation == VTK_ORIENT_VERTICAL)
      {
        x = scalarBarMaxX;
        x -= this->P->TitleBox.Size[this->P->TL[0]];
      }
      else
      {
        x = scalarBarMinX;
      }
      break;
    case VTK_TEXT_RIGHT:
      if (this->Orientation == VTK_ORIENT_VERTICAL)
      {
        x = scalarBarMinX;
        x += this->P->TitleBox.Size[this->P->TL[0]];
      }
      else
      {
        x = scalarBarMaxX;
      }
      break;
    case VTK_TEXT_CENTERED:
      x = 0.5 * (scalarBarMinX + scalarBarMaxX);
      break;
    default:
      x = 0.;
      vtkErrorMacro(<< "Invalid text justification ("
                    << this->TitleActor->GetTextProperty()->GetJustification()
                    << " for scalar bar title.");
      break;
  }

  double y = this->TitleActor->GetTextProperty()->GetVerticalJustification() == VTK_TEXT_BOTTOM
    ? this->P->TitleBox.Posn[1]
    : this->P->TitleBox.Posn[1] + this->P->TitleBox.Size[this->P->TL[1]];

  this->TitleActor->SetPosition(x, y);
}

//----------------------------------------------------------------------------
void vtkPVScalarBarActor::ConfigureTicks()
{
  bool isLogTable = (this->LookupTable->UsingLogScale() != 0);
  double* range = this->LookupTable->GetRange();

  if (this->LookupTable->GetIndexedLookup())
  { // no tick marks in indexed lookup mode.
    return;
  }

  // Use vtkAxis to compute tick locations:
  vtkAxis::Location loc;
  vtkVector2f p1(this->P->TickBox.Posn.Cast<float>().GetData());
  vtkVector2f p2 = p1;
  if (this->Orientation == VTK_ORIENT_HORIZONTAL)
  {
    p2[0] += static_cast<float>(this->P->TickBox.Size[1]);
    loc = vtkAxis::BOTTOM;
  }
  else
  {
    p2[1] += static_cast<float>(this->P->TickBox.Size[1]);
    loc = vtkAxis::LEFT;
  }

  this->TickLayoutHelper->SetPosition(static_cast<int>(loc));
  this->TickLayoutHelper->SetPoint1(p1);
  this->TickLayoutHelper->SetPoint2(p2);
  this->TickLayoutHelper->SetLogScale(isLogTable);
  this->TickLayoutHelper->SetUnscaledRange(range);
  this->TickLayoutHelper->SetNumberOfTicks(this->NumberOfLabels);
  this->TickLayoutHelper->Update();

  vtkDoubleArray* tickArray = this->TickLayoutHelper->GetTickPositions();
  vtkStringArray* labelArray = this->TickLayoutHelper->GetTickLabels();

  // Process and filter the ticks:
  std::vector<double> ticks;
  ticks.reserve(tickArray->GetNumberOfTuples());
  for (vtkIdType i = 0; i < tickArray->GetNumberOfTuples(); ++i)
  {
    // The label will be an empty string if it's not supposed to be labeled.
    // Filter these out.
    if (labelArray->GetValue(i).empty())
    {
      continue;
    }

    ticks.push_back(isLogTable ? std::pow(10.0, tickArray->GetValue(i)) : tickArray->GetValue(i));
  }

  // minimum number of digits (base 10) to differentiate between tick marks.
  int minDigits = 1;

  // Compute the difference between min and max of scalar bar values.
  double delta = range[1] - range[0];
  if (delta != 0)
  {
    // See what digit of the decimal number the difference is contained in.
    double dmag = log10(delta);
    double emag = floor(dmag) - 1;
    double mag = pow(10.0, emag);
    if (1.1 * mag > delta)
    {
      mag /= 10.0;
    }
    int leastDig = floor(log10(fabs(mag)));

    // Find the the maximum number of digits in the range:
    double absMax = std::max(std::fabs(range[0]), std::fabs(range[1]));
    int leadDig = floor(log10(absMax));

    minDigits = leadDig - leastDig;
  }

  // Map from tick to label ID for tick
  std::vector<int> tickToLabelId(ticks.size(), -1);

  this->P->TextActors.reserve(ticks.size());

  vtkNew<vtkCellArray> tickCells;
  tickCells->AllocateEstimate(ticks.size(), 20);

  vtkNew<vtkPoints> tickPoints;
  tickPoints->Allocate(ticks.size() * 20);

  double targetWidth = this->P->TickBox.Size[this->P->TL[0]];
  double targetHeight = this->P->TickBox.Size[this->P->TL[1]];
  if (this->Orientation == VTK_ORIENT_HORIZONTAL)
  {
    targetWidth = (targetWidth - (ticks.size() - 1) * this->TextPad) / (ticks.size() + 1.);
  }
  else // VTK_ORIENT_VERTICAL
  {
    targetHeight = (targetHeight - (ticks.size() - 1) * this->TextPad) / (ticks.size() + 1.);
    // Get the target height based on the selected font size. Set this
    // as the target height
    // int desiredFontSize = this->LabelTextProperty->GetFontSize();
    vtkNew<vtkTextActor> dummyActor;
    dummyActor->GetTextProperty()->ShallowCopy(this->LabelTextProperty);
    dummyActor->SetInput("()"); // parentheses are taller than numbers for all useful fonts.
    if (this->P->Viewport)
    {
      dummyActor->SetTextScaleModeToViewport();
      dummyActor->ComputeScaledFont(this->P->Viewport);
    }

    double tsize[2];
    dummyActor->GetSize(this->P->Viewport, tsize);

    targetHeight = std::min(targetHeight, tsize[1]);
  }

  bool precede = this->TextPosition == vtkScalarBarActor::PrecedeScalarBar;
  int minimumFontSize = VTK_INT_MAX;
  for (int i = 0; i < static_cast<int>(ticks.size()); i++)
  {
    double val = ticks[i];

    // Do not create the label if it is already represented in the min or max
    // label.
    if (!((val - 1e-6 * MY_ABS(val + range[0]) > range[0]) &&
          (val + 1e-6 * MY_ABS(val + range[1]) < range[1])))
    {
      continue;
    }

    int labelIdx = this->CreateLabel(val, minDigits, targetWidth, targetHeight, this->P->Viewport);
    tickToLabelId[i] = labelIdx;
    vtkTextActor* textActor = this->P->TextActors[labelIdx];

    int labelFontSize = textActor->GetTextProperty()->GetFontSize();
    minimumFontSize = std::min(minimumFontSize, labelFontSize);
  }

  // Now place the label actors
  for (size_t i = 0; i < ticks.size(); i++)
  {
    int labelIdx = tickToLabelId[i];
    if (labelIdx == -1)
    {
      // No label
      continue;
    }
    vtkTextActor* textActor = this->P->TextActors[labelIdx];

    // Make sure every text actor gets the smallest text size to fit
    // the constraints.
    textActor->GetTextProperty()->SetFontSize(minimumFontSize);

    double val = ticks[i];

    double normVal;
    if (isLogTable)
    {
      normVal = ((log10(val) - log10(range[0])) / (log10(range[1]) - log10(range[0])));
    }
    else
    {
      normVal = (val - range[0]) / (range[1] - range[0]);
    }

    if (this->Orientation == VTK_ORIENT_VERTICAL)
    {
      double x =
        precede ? this->P->TickBox.Posn[0] + this->P->TickBox.Size[0] : this->P->TickBox.Posn[0];
      double y = normVal * this->P->TickBox.Size[1] + this->P->TickBox.Posn[1];
      double textSize[2];
      textActor->GetSize(this->P->Viewport, textSize);
      y -= textSize[1] / 2; // Adjust to center text.
      textActor->GetTextProperty()->SetJustification(precede ? VTK_TEXT_RIGHT : VTK_TEXT_LEFT);
      textActor->SetPosition(precede ? x - this->LabelSpace : x + this->LabelSpace, y);
    }
    else // this->Orientation == VTK_ORIENT_HORIZONTAL
    {
      double x = this->P->TickBox.Posn[0] + normVal * this->P->TickBox.Size[1];
      double y =
        precede ? this->P->TickBox.Posn[1] + this->P->TickBox.Size[0] : this->P->TickBox.Posn[1];
      textActor->GetTextProperty()->SetJustificationToCentered();
      textActor->GetTextProperty()->SetVerticalJustification(
        precede ? VTK_TEXT_TOP : VTK_TEXT_BOTTOM);
      textActor->SetPosition(x, precede ? y - this->LabelSpace : y + this->LabelSpace);
    }
  }

  // Create minor tick marks.
  int numTicks = static_cast<int>(ticks.size());
  if (numTicks > 1)
  {
    // Decide how many (maximum) minor ticks we want based on how many pixels
    // are available.
    double fractionOfRange;
    if (isLogTable)
    {
      double tickDelta;
      tickDelta = log10(ticks[numTicks - 1]) - log10(ticks[0]);
      fractionOfRange = (tickDelta) / (log10(range[1]) - log10(range[0]));
    }
    else
    {
      double tickDelta;
      tickDelta = ticks[numTicks - 1] - ticks[0];
      fractionOfRange = (tickDelta) / (range[1] - range[0]);
    }

    double pixelsAvailable = fractionOfRange * this->P->TickBox.Size[1];
    int maxNumMinorTicks = vtkMath::Floor(pixelsAvailable / 5);

    // This array lists valid minor to major tick ratios.
    const int minorRatios[] = { 10, 5, 2, 1 };
    const int numMinorRatios = static_cast<int>(sizeof(minorRatios) / sizeof(int));
    int minorRatio = 0;
    for (int r = 0; r < numMinorRatios; r++)
    {
      minorRatio = minorRatios[r];
      int numMinorTicks = (numTicks - 1) * minorRatio;
      if (numMinorTicks <= maxNumMinorTicks)
        break;
    }

    // Add "fake" major ticks so that the minor ticks extend to bar ranges.
    double fakeMin, fakeMax;
    if (!isLogTable)
    {
      fakeMin = 2 * ticks[0] - ticks[1];
      fakeMax = 2 * ticks[numTicks - 1] - ticks[numTicks - 2];
    }
    else
    {
      fakeMin = pow(10.0, 2 * log10(ticks[0]) - log10(ticks[1]));
      fakeMax = pow(10.0, 2 * log10(ticks[numTicks - 1]) - log10(ticks[numTicks - 2]));
    }
    ticks.insert(ticks.begin(), fakeMin);
    ticks.insert(ticks.end(), fakeMax);
    numTicks = static_cast<int>(ticks.size());

    for (int i = 0; this->DrawSubTickMarks && i < numTicks - 1; i++)
    {
      double minorTickRange[2];
      minorTickRange[0] = ticks[i];
      minorTickRange[1] = ticks[i + 1];
      for (int j = 0; j < minorRatio; j++)
      {
        double val =
          (((minorTickRange[1] - minorTickRange[0]) * j) / minorRatio + minorTickRange[0]);
        double normVal;
        if (isLogTable)
        {
          normVal = ((log10(val) - log10(range[0])) / (log10(range[1]) - log10(range[0])));
        }
        else
        {
          normVal = (val - range[0]) / (range[1] - range[0]);
        }

        // Do not draw ticks out of range.
        if ((normVal < 0.0) || (normVal > 1.0))
          continue;

        if (this->Orientation == VTK_ORIENT_VERTICAL)
        {
          double x = precede ? this->P->TickBox.Posn[0] + this->P->TickBox.Size[0]
                             : this->P->TickBox.Posn[0];
          double y = normVal * this->P->TickBox.Size[1] + this->P->TickBox.Posn[1];
          vtkIdType ids[2];
          ids[0] = tickPoints->InsertNextPoint(precede ? x - this->LabelSpace + 2 : x - 2, y, 0.0);
          ids[1] = tickPoints->InsertNextPoint(precede ? x + 2 : x + this->LabelSpace - 2, y, 0.0);
          tickCells->InsertNextCell(2, ids);
        }
        else // ths->Orientation == VTK_ORIENT_HORIZONTAL
        {
          double x = this->P->TickBox.Posn[0] + normVal * this->P->TickBox.Size[1];
          double y = precede ? this->P->TickBox.Posn[1] + this->P->TickBox.Size[0]
                             : this->P->TickBox.Posn[1];
          vtkIdType ids[2];
          ids[0] = tickPoints->InsertNextPoint(x, precede ? y + 2 : y + this->LabelSpace - 2, 0.0);
          ids[1] = tickPoints->InsertNextPoint(x, precede ? y - this->LabelSpace + 2 : y - 2, 0.0);
          tickCells->InsertNextCell(2, ids);
        }
      }
    }
  }

  this->TickMarks->SetLines(tickCells.GetPointer());
  this->TickMarks->SetPoints(tickPoints.GetPointer());

  // "Mute" the color of the tick marks.
  double color[3];
  // this->TickMarksActor->GetProperty()->GetColor(color);
  this->LabelTextProperty->GetColor(color);
  vtkMath::RGBToHSV(color, color);
  if (color[2] > 0.5)
  {
    color[2] -= 0.2;
  }
  else
  {
    color[2] += 0.2;
  }
  vtkMath::HSVToRGB(color, color);
  this->TickMarksActor->GetProperty()->SetColor(color);

  if (this->AddRangeLabels)
  {

    // Save state and set preferred parameters
    int previousAutomaticLabelFormat = this->GetAutomaticLabelFormat();
    this->SetAutomaticLabelFormat(0);

    std::string previousLabelFormat(this->GetLabelFormat());
    this->SetLabelFormat(this->RangeLabelFormat);

    this->CreateLabel(range[0], minDigits, targetWidth, targetHeight, this->P->Viewport);
    this->CreateLabel(range[1], minDigits, targetWidth, targetHeight, this->P->Viewport);

    // Restore state
    this->AutomaticLabelFormat = previousAutomaticLabelFormat;
    this->SetLabelFormat(previousLabelFormat.c_str());

    // Now change the font size of the min/max text actors to the minimum
    // font size of all the text actors.
    for (size_t i = this->P->TextActors.size() - 2; i < this->P->TextActors.size(); ++i)
    {
      vtkTextActor* textActor = this->P->TextActors[i];

      // Keep min/max labels the same size as the rest of the labels
      textActor->GetTextProperty()->SetFontSize(minimumFontSize);

      double val = range[i - (this->P->TextActors.size() - 2)];

      double normVal;
      if (isLogTable)
      {
        normVal = ((log10(val) - log10(range[0])) / (log10(range[1]) - log10(range[0])));
      }
      else
      {
        normVal = (val - range[0]) / (range[1] - range[0]);
      }

      if (this->Orientation == VTK_ORIENT_VERTICAL)
      {
        double x =
          precede ? this->P->TickBox.Posn[0] + this->P->TickBox.Size[0] : this->P->TickBox.Posn[0];
        double y = normVal * this->P->TickBox.Size[1] + this->P->TickBox.Posn[1];
        double textSize[2];
        textActor->GetSize(this->P->Viewport, textSize);
        y -= textSize[1] / 2; // Adjust to center text.
        textActor->GetTextProperty()->SetJustification(precede ? VTK_TEXT_RIGHT : VTK_TEXT_LEFT);
        textActor->SetPosition(precede ? x - this->LabelSpace : x + this->LabelSpace, y);
      }
      else // this->Orientation == VTK_ORIENT_HORIZONTAL
      {
        double x = this->P->TickBox.Posn[0] + normVal * this->P->TickBox.Size[1];
        double y =
          precede ? this->P->TickBox.Posn[1] + this->P->TickBox.Size[0] : this->P->TickBox.Posn[1];
        textActor->GetTextProperty()->SetJustificationToCentered();
        textActor->GetTextProperty()->SetVerticalJustification(
          precede ? VTK_TEXT_TOP : VTK_TEXT_BOTTOM);
        textActor->SetPosition(x, precede ? y - this->LabelSpace : y + this->LabelSpace);
      }

      // Turn off visibility of any labels that overlap the min/max labels
      double bbox[4];
      textActor->GetBoundingBox(this->P->Viewport, bbox);
      double* pos = textActor->GetPosition();
      bbox[0] += pos[0];
      bbox[1] += pos[0];
      bbox[2] += pos[1];
      bbox[3] += pos[1];

      for (size_t j = 0; j < tickToLabelId.size(); ++j)
      {
        int labelIdx = tickToLabelId[j];
        if (labelIdx == -1)
        {
          // No label
          continue;
        }
        vtkTextActor* labelActor = this->P->TextActors[labelIdx];

        double labelbbox[4];
        double* labelpos;
        labelActor->GetBoundingBox(this->P->Viewport, labelbbox);
        labelpos = labelActor->GetPosition();
        labelbbox[0] += labelpos[0];
        labelbbox[1] += labelpos[0];
        labelbbox[2] += labelpos[1];
        labelbbox[3] += labelpos[1];

        // Does label bounding box intersect min/max label bounding box?
        bool xoverlap = !((labelbbox[0] < bbox[0] && labelbbox[1] < bbox[0]) ||
          (labelbbox[0] > bbox[1] && labelbbox[1] > bbox[1]));
        bool yoverlap = !((labelbbox[2] < bbox[2] && labelbbox[3] < bbox[2]) ||
          (labelbbox[2] > bbox[3] && labelbbox[3] > bbox[3]));
        if (xoverlap && yoverlap)
        {
          labelActor->SetVisibility(0);
        }
      }
    }
  }

  // Loop range accounts for "fake" min max ticks
  for (size_t i = 1; !ticks.empty() && i < ticks.size() - 1; i++)
  {
    int labelIdx = tickToLabelId[i - 1];
    if (labelIdx == -1)
    {
      // No label
      continue;
    }
    vtkTextActor* textActor = this->P->TextActors[labelIdx];
    if (textActor->GetVisibility() == 0)
    {
      continue;
    }

    double val = ticks[i];

    double normVal;
    if (isLogTable)
    {
      normVal = ((log10(val) - log10(range[0])) / (log10(range[1]) - log10(range[0])));
    }
    else
    {
      normVal = (val - range[0]) / (range[1] - range[0]);
    }

    if (this->Orientation == VTK_ORIENT_VERTICAL)
    {
      double x =
        precede ? this->P->TickBox.Posn[0] + this->P->TickBox.Size[0] : this->P->TickBox.Posn[0];
      double y = normVal * this->P->TickBox.Size[1] + this->P->TickBox.Posn[1];
      vtkIdType ids[2];
      ids[0] = tickPoints->InsertNextPoint(x - this->LabelSpace + 2, y, 0.0);
      ids[1] = tickPoints->InsertNextPoint(x + this->LabelSpace - 2, y, 0.0);
      tickCells->InsertNextCell(2, ids);
    }
    else // this->Orientation == VTK_ORIENT_HORIZONTAL
    {
      double x = this->P->TickBox.Posn[0] + normVal * this->P->TickBox.Size[1];
      double y =
        precede ? this->P->TickBox.Posn[1] + this->P->TickBox.Size[0] : this->P->TickBox.Posn[1];
      vtkIdType ids[2];
      ids[0] = tickPoints->InsertNextPoint(x, y - this->LabelSpace + 2, 0.0);
      ids[1] = tickPoints->InsertNextPoint(x, y + this->LabelSpace - 2, 0.0);
      tickCells->InsertNextCell(2, ids);
    }
  }
}

//----------------------------------------------------------------------------
namespace
{
void AddLabelIfUnoccluded(double x, const vtkColor3ub& color, const std::string& label,
  vtkScalarBarActorInternal* scalarBar)
{
  // Don't place the label if the nearest existing labels are within 1% of
  // the screen-space size of the scalar bar.
  bool okLo = true, okHi = true;
  if (!scalarBar->Labels.empty())
  {
    double delta = scalarBar->ScalarBarBox.Size[1] / 100.;
    std::map<double, vtkStdString>::iterator clo = scalarBar->Labels.lower_bound(x);

    if (clo == scalarBar->Labels.end())
    { // nothing bounds above, just check below:
      okLo = (x - scalarBar->Labels.rbegin()->first) > delta;
    }
    else
    {
      okHi = (clo->first - x) > delta;
      if (clo != scalarBar->Labels.begin())
      { // something bounds below
        --clo;
        okLo = (x - clo->first) > delta;
      }
    }
  }
  if (okLo && okHi)
  {
    scalarBar->Labels[x] = label;
    scalarBar->LabelColors[x] = color;
  }
}
}

//-----------------------------------------------------------------------------
void vtkPVScalarBarActor::EditAnnotations()
{
  vtkScalarsToColors* lut = this->LookupTable;
  if (lut && !lut->GetIndexedLookup())
  {
    const double* range = lut->GetRange();
    double dr = range[1] - range[0];
    double minX = this->P->ScalarBarBox.Posn[this->P->TL[1]];
    double maxX = this->P->ScalarBarBox.Size[1] + minX;

    // Add annotations with min and max values
    if (this->AddRangeAnnotations)
    {
      this->AddValueLabelIfUnoccluded(range[0], minX, dr);
      this->AddValueLabelIfUnoccluded(range[1], maxX, dr);
    }

    // Add annotations with corresponding values between discretized colors
    vtkDiscretizableColorTransferFunction* transferFunc =
      vtkDiscretizableColorTransferFunction::SafeDownCast(this->LookupTable);
    vtkIdType nbValues = lut->GetNumberOfAvailableColors();
    if (transferFunc && transferFunc->GetDiscretize() && this->AutomaticAnnotations && nbValues)
    {
      double step = dr / nbValues;
      double stepPos = (maxX - minX) / nbValues;
      for (vtkIdType i = 0; i <= nbValues; i++)
      {
        double value = range[0] + step * i;
        double pos = minX + stepPos * i;
        this->AddValueLabelIfUnoccluded(value, pos, value - range[0]);
      }
    }
  }
}

//-----------------------------------------------------------------------------
void vtkPVScalarBarActor::AddValueLabelIfUnoccluded(double value, double pos, double /*diff*/)
{
  double lVal = log10(value);
  // The least significant digit
  int least = (lVal > 0 ? +1 : -1) * static_cast<int>(floor(fabs(lVal)));
  // The most significant digit
  int most = static_cast<int>(ceil(log10(fabs(value))));
  // How many digits of precision are required
  int dig = (most == VTK_INT_MIN) ? 3 : (most - least);
  // Label
  char label[64], format[64];
  auto result = vtk::format_to_n(format, 63, "{{:.{:d}g}}", dig);
  *result.out = '\0';
  result = vtk::format_to_n(label, 63, std::string_view(format), value);
  *result.out = '\0';
  vtkColor4d fltCol;
  vtkColor3ub col;
  this->LookupTable->GetColor(value, fltCol.GetData());
  for (int j = 0; j < 3; ++j)
  {
    col.GetData()[j] = static_cast<unsigned char>(fltCol.GetData()[j] * 255.);
  }
  AddLabelIfUnoccluded(pos, col, label, this->P);
}

//-----------------------------------------------------------------------------
void vtkPVScalarBarActor::BuildScalarBarTexture()
{
  vtkNew<vtkFloatArray> tmp;
  tmp->SetNumberOfTuples(COLOR_TEXTURE_MAP_SIZE);
  double* range = this->LookupTable->GetRange();
  int isLogTable = this->LookupTable->UsingLogScale();
  for (int i = 0; i < COLOR_TEXTURE_MAP_SIZE; i++)
  {
    double normVal = (double)i / (COLOR_TEXTURE_MAP_SIZE - 1);
    double val;
    if (isLogTable)
    {
      double lval = log10(range[0]) + normVal * (log10(range[1]) - log10(range[0]));
      val = pow(10.0, lval);
    }
    else
    {
      val = (range[1] - range[0]) * normVal + range[0];
    }
    tmp->SetValue(i, val);
  }
  vtkNew<vtkImageData> colorMapImage;
  colorMapImage->SetExtent(0, COLOR_TEXTURE_MAP_SIZE - 1, 0, 0, 0, 0);
  colorMapImage->AllocateScalars(VTK_UNSIGNED_CHAR, 4);
  vtkDataArray* colors =
    this->LookupTable->MapScalars(tmp.GetPointer(), VTK_COLOR_MODE_MAP_SCALARS, 0);
  colorMapImage->GetPointData()->SetScalars(colors);
  colors->Delete();

  this->ScalarBarTexture->SetInputData(colorMapImage.GetPointer());
}
