// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkContext2D.h"

#include "vtkBrush.h"
#include "vtkContextDevice2D.h"
#include "vtkFloatArray.h"
#include "vtkPen.h"
#include "vtkPoints2D.h"
#include "vtkTextProperty.h"
#include "vtkTransform2D.h"
#include "vtkUnsignedCharArray.h"
#include "vtkVector.h"

#include "vtkObjectFactory.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"

#include <cassert>

//------------------------------------------------------------------------------
VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkContext2D);

//------------------------------------------------------------------------------
bool vtkContext2D::Begin(vtkContextDevice2D* device)
{
  if (this->Device == device)
  {
    // Handle the case where the same device is set multiple times
    return true;
  }
  else if (this->Device)
  {
    this->Device->Delete();
  }
  this->Device = device;
  this->Device->Register(this);
  this->Modified();
  return true;
}

//------------------------------------------------------------------------------
bool vtkContext2D::End()
{
  if (this->Device)
  {
    this->Device->End();
    this->Device->Delete();
    this->Device = nullptr;
    this->Modified();
    return true;
  }
  return true;
}

//------------------------------------------------------------------------------
bool vtkContext2D::GetBufferIdMode() const
{
  return this->BufferId != nullptr;
}

//------------------------------------------------------------------------------
void vtkContext2D::BufferIdModeBegin(vtkAbstractContextBufferId* bufferId)
{
  assert("pre: not_yet" && !this->GetBufferIdMode());
  assert("pre: bufferId_exists" && bufferId != nullptr);

  this->BufferId = bufferId;
  this->Device->BufferIdModeBegin(bufferId);

  assert("post: started" && this->GetBufferIdMode());
}

//------------------------------------------------------------------------------
void vtkContext2D::BufferIdModeEnd()
{
  assert("pre: started" && this->GetBufferIdMode());

  this->Device->BufferIdModeEnd();
  this->BufferId = nullptr;

  assert("post: done" && !this->GetBufferIdMode());
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawLine(float x1, float y1, float x2, float y2)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  float x[] = { x1, y1, x2, y2 };
  this->Device->DrawPoly(&x[0], 2);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawLine(float p[4])
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  this->Device->DrawPoly(&p[0], 2);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawLine(vtkPoints2D* points)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  if (points->GetNumberOfPoints() < 2)
  {
    vtkErrorMacro(<< "Attempted to paint a line with <2 points.");
    return;
  }
  float* f = vtkArrayDownCast<vtkFloatArray>(points->GetData())->GetPointer(0);
  this->Device->DrawPoly(f, 2);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPoly(float* x, float* y, int n)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  float* p = new float[2 * n];
  for (int i = 0; i < n; ++i)
  {
    p[2 * i] = x[i];
    p[2 * i + 1] = y[i];
  }
  this->Device->DrawPoly(p, n);
  delete[] p;
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPoly(vtkPoints2D* points)
{
  // Construct an array with the correct coordinate packing for OpenGL.
  int n = static_cast<int>(points->GetNumberOfPoints());
  // If the points are of type float then call OpenGL directly
  float* f = vtkArrayDownCast<vtkFloatArray>(points->GetData())->GetPointer(0);
  this->DrawPoly(f, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPoly(float* points, int n)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  if (n < 2)
  {
    vtkErrorMacro(<< "Attempted to paint a line with <2 points.");
    return;
  }
  this->Device->DrawPoly(points, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPoly(float* points, int n, unsigned char* colors, int nc_comps)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  if (n < 2)
  {
    vtkErrorMacro(<< "Attempted to paint a line with <2 points.");
    return;
  }
  this->Device->DrawPoly(points, n, colors, nc_comps);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawLines(vtkPoints2D* points)
{
  // Construct an array with the correct coordinate packing for OpenGL.
  int n = static_cast<int>(points->GetNumberOfPoints());
  // If the points are of type float then call OpenGL directly
  float* f = vtkArrayDownCast<vtkFloatArray>(points->GetData())->GetPointer(0);
  this->DrawLines(f, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawLines(float* points, int n)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  if (n < 2)
  {
    vtkErrorMacro(<< "Attempted to paint a line with <2 points.");
    return;
  }
  this->Device->DrawLines(points, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPoint(float x, float y)
{
  float p[] = { x, y };
  this->DrawPoints(&p[0], 1);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPoints(float* x, float* y, int n)
{
  // Copy the points into an array and draw it.
  float* p = new float[2 * n];
  for (int i = 0; i < n; ++i)
  {
    p[2 * i] = x[i];
    p[2 * i + 1] = y[i];
  }
  this->DrawPoints(&p[0], n);
  delete[] p;
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPoints(vtkPoints2D* points)
{
  // Construct an array with the correct coordinate packing for OpenGL.
  int n = static_cast<int>(points->GetNumberOfPoints());
  // If the points are of type float then call OpenGL directly
  float* f = vtkArrayDownCast<vtkFloatArray>(points->GetData())->GetPointer(0);
  this->DrawPoints(f, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPoints(
  vtkDataArray* positions, vtkUnsignedCharArray* colors, std::uintptr_t cacheIdentifier)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  this->Device->DrawPoints(positions, colors, cacheIdentifier);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPoints(float* points, int n)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  this->Device->DrawPoints(points, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPointSprites(vtkImageData* sprite, vtkPoints2D* points)
{
  // Construct an array with the correct coordinate packing for OpenGL.
  int n = static_cast<int>(points->GetNumberOfPoints());
  // If the points are of type float then call OpenGL directly
  float* f = vtkArrayDownCast<vtkFloatArray>(points->GetData())->GetPointer(0);
  this->DrawPointSprites(sprite, f, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPointSprites(
  vtkImageData* sprite, vtkPoints2D* points, vtkUnsignedCharArray* colors)
{
  // Construct an array with the correct coordinate packing for OpenGL.
  int n = static_cast<int>(points->GetNumberOfPoints());
  int nc = static_cast<int>(colors->GetNumberOfTuples());
  if (n != nc)
  {
    vtkErrorMacro(<< "Attempted to color points with array of wrong length");
    return;
  }
  int nc_comps = colors->GetNumberOfComponents();
  // If the points are of type float then call OpenGL directly
  float* f = vtkArrayDownCast<vtkFloatArray>(points->GetData())->GetPointer(0);
  unsigned char* c = colors->GetPointer(0);
  this->DrawPointSprites(sprite, f, n, c, nc_comps);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPointSprites(vtkImageData* sprite, vtkDataArray* positions,
  vtkUnsignedCharArray* colors, std::uintptr_t cacheIdentifier)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  this->Device->DrawPointSprites(sprite, positions, colors, cacheIdentifier);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPointSprites(
  vtkImageData* sprite, float* points, int n, unsigned char* colors, int nc_comps)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  this->Device->DrawPointSprites(sprite, points, n, colors, nc_comps);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPointSprites(vtkImageData* sprite, float* points, int n)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  this->Device->DrawPointSprites(sprite, points, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMarkers(
  int shape, bool highlight, float* points, int n, unsigned char* colors, int nc_comps)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  this->Device->DrawMarkers(shape, highlight, points, n, colors, nc_comps);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMarkers(int shape, bool highlight, float* points, int n)
{
  this->DrawMarkers(shape, highlight, points, n, nullptr, 0);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMarkers(int shape, bool highlight, vtkPoints2D* points)
{
  // Construct an array with the correct coordinate packing for OpenGL.
  int n = static_cast<int>(points->GetNumberOfPoints());
  float* f = vtkArrayDownCast<vtkFloatArray>(points->GetData())->GetPointer(0);
  this->DrawMarkers(shape, highlight, f, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMarkers(
  int shape, bool highlight, vtkPoints2D* points, vtkUnsignedCharArray* colors)
{
  // Construct an array with the correct coordinate packing for OpenGL.
  int n = static_cast<int>(points->GetNumberOfPoints());
  int nc = static_cast<int>(colors->GetNumberOfTuples());
  if (n != nc)
  {
    vtkErrorMacro(<< "Attempted to color points with array of wrong length");
    return;
  }
  int nc_comps = colors->GetNumberOfComponents();
  float* f = vtkArrayDownCast<vtkFloatArray>(points->GetData())->GetPointer(0);
  unsigned char* c = colors->GetPointer(0);
  this->DrawMarkers(shape, highlight, f, n, c, nc_comps);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMarkers(int shape, bool highlight, vtkDataArray* positions,
  vtkUnsignedCharArray* colors, std::uintptr_t cacheIdentifier)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  this->Device->DrawMarkers(shape, highlight, positions, colors, cacheIdentifier);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawRect(float x, float y, float width, float height)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  float p[] = { x, y, x + width, y, x + width, y + height, x, y + height, x, y };

  // Draw the filled area of the rectangle.
  this->Device->DrawQuad(&p[0], 4);

  // Draw the outline now.
  this->Device->DrawPoly(&p[0], 5);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawQuad(
  float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4)
{
  float p[] = { x1, y1, x2, y2, x3, y3, x4, y4 };
  this->DrawQuad(&p[0]);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawQuad(float* p)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }

  // Draw the filled area of the quad.
  this->Device->DrawQuad(p, 4);

  // Draw the outline now.
  this->Device->DrawPoly(p, 4);
  float closeLine[] = { p[0], p[1], p[6], p[7] };
  this->Device->DrawPoly(&closeLine[0], 2);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawQuadStrip(vtkPoints2D* points)
{
  // Construct an array with the correct coordinate packing for OpenGL.
  int n = static_cast<int>(points->GetNumberOfPoints());
  // If the points are of type float then call OpenGL directly
  float* f = vtkArrayDownCast<vtkFloatArray>(points->GetData())->GetPointer(0);
  this->DrawQuadStrip(f, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawQuadStrip(float* points, int n)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  // Draw the filled area of the polygon.
  this->Device->DrawQuadStrip(points, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPolygon(float* x, float* y, int n)
{
  // Copy the points into an array and draw it.
  float* p = new float[2 * n];
  for (int i = 0; i < n; ++i)
  {
    p[2 * i] = x[i];
    p[2 * i + 1] = y[i];
  }
  this->DrawPolygon(&p[0], n);
  delete[] p;
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPolygon(vtkPoints2D* points)
{
  // Construct an array with the correct coordinate packing for OpenGL.
  int n = static_cast<int>(points->GetNumberOfPoints());
  // If the points are of type float then call OpenGL directly
  float* f = vtkArrayDownCast<vtkFloatArray>(points->GetData())->GetPointer(0);
  this->DrawPolygon(f, n);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPolygon(float* points, int n)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  // Draw the filled area of the polygon.
  this->Device->DrawPolygon(points, n);

  // Draw the outline now.
  this->Device->DrawPoly(points, n);
  float closeLine[] = { points[0], points[1], points[2 * n - 2], points[2 * n - 1] };
  this->Device->DrawPoly(&closeLine[0], 2);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPolygon(float* x, float* y, int n, unsigned char* color, int nc_comps)
{
  // Copy the points into an array and draw it.
  float* p = new float[2 * n];
  for (int i = 0; i < n; ++i)
  {
    p[2 * i] = x[i];
    p[2 * i + 1] = y[i];
  }
  this->DrawPolygon(&p[0], n, color, nc_comps);
  delete[] p;
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPolygon(vtkPoints2D* points, unsigned char* color, int nc_comps)
{

  // Construct an array with the correct coordinate packing for OpenGL.
  int n = static_cast<int>(points->GetNumberOfPoints());
  // If the points are of type float then call OpenGL directly
  float* f = vtkArrayDownCast<vtkFloatArray>(points->GetData())->GetPointer(0);
  this->DrawPolygon(f, n, color, nc_comps);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPolygon(float* points, int n, unsigned char* color, int nc_comps)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }

  // Draw the filled area of the polygon.
  this->Device->DrawColoredPolygon(points, n, color, nc_comps);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawEllipse(float x, float y, float rx, float ry)
{
  assert("pre: positive_rx" && rx >= 0);
  assert("pre: positive_ry" && ry >= 0);
  this->DrawEllipticArc(x, y, rx, ry, 0.0, 360.0);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawWedge(
  float x, float y, float outRadius, float inRadius, float startAngle, float stopAngle)

{
  assert("pre: positive_outRadius" && outRadius >= 0.0f);
  assert("pre: positive_inRadius" && inRadius >= 0.0f);
  assert("pre: ordered_radii" && inRadius <= outRadius);

  this->DrawEllipseWedge(x, y, outRadius, outRadius, inRadius, inRadius, startAngle, stopAngle);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawEllipseWedge(float x, float y, float outRx, float outRy, float inRx,
  float inRy, float startAngle, float stopAngle)

{
  assert("pre: positive_outRx" && outRx >= 0.0f);
  assert("pre: positive_outRy" && outRy >= 0.0f);
  assert("pre: positive_inRx" && inRx >= 0.0f);
  assert("pre: positive_inRy" && inRy >= 0.0f);
  assert("pre: ordered_rx" && inRx <= outRx);
  assert("pre: ordered_ry" && inRy <= outRy);

  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  // don't tessellate here. The device context knows what to do with an
  // arc. An OpenGL device context will tessellate but and SVG context with
  // just generate an arc.
  this->Device->DrawEllipseWedge(x, y, outRx, outRy, inRx, inRy, startAngle, stopAngle);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawArc(float x, float y, float r, float startAngle, float stopAngle)
{
  assert("pre: positive_radius" && r >= 0);
  this->DrawEllipticArc(x, y, r, r, startAngle, stopAngle);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawEllipticArc(
  float x, float y, float rX, float rY, float startAngle, float stopAngle)
{
  assert("pre: positive_rX" && rX >= 0);
  assert("pre: positive_rY" && rY >= 0);

  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  // don't tessellate here. The device context knows what to do with an
  // arc. An OpenGL device context will tessellate but and SVG context with
  // just generate an arc.
  this->Device->DrawEllipticArc(x, y, rX, rY, startAngle, stopAngle);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawStringRect(vtkPoints2D* rect, const vtkStdString& string)
{
  vtkVector2f p = this->CalculateTextPosition(rect);
  this->DrawString(p.GetX(), p.GetY(), string);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawStringRect(vtkPoints2D* rect, const char* string)
{
  this->DrawStringRect(rect, vtkStdString(string));
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawStringRect(const float rect[4], const vtkStdString& string)
{
  vtkVector2f p = this->CalculateTextPosition(rect);
  this->DrawString(p.GetX(), p.GetY(), string);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawStringRect(const float rect[4], const char* string)
{
  this->DrawStringRect(rect, vtkStdString(string));
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawString(vtkPoints2D* point, const vtkStdString& string)
{
  float* f = vtkArrayDownCast<vtkFloatArray>(point->GetData())->GetPointer(0);
  this->DrawString(f[0], f[1], string);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawString(float x, float y, const vtkStdString& string)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  if (string.empty())
  {
    return;
  }
  float f[] = { x, y };
  this->Device->DrawString(f, string);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawString(vtkPoints2D* point, const char* string)
{
  float* f = vtkArrayDownCast<vtkFloatArray>(point->GetData())->GetPointer(0);
  this->DrawString(f[0], f[1], vtkStdString(string));
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawString(float x, float y, const char* string)
{
  this->DrawString(x, y, vtkStdString(string));
}

//------------------------------------------------------------------------------
void vtkContext2D::ComputeStringBounds(const vtkStdString& string, vtkPoints2D* bounds)
{
  bounds->SetNumberOfPoints(2);
  float* f = vtkArrayDownCast<vtkFloatArray>(bounds->GetData())->GetPointer(0);
  this->ComputeStringBounds(string, f);
}

//------------------------------------------------------------------------------
void vtkContext2D::ComputeStringBounds(const vtkStdString& string, float bounds[4])
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  this->Device->ComputeStringBounds(string, bounds);
}

//------------------------------------------------------------------------------
void vtkContext2D::ComputeStringBounds(const char* string, vtkPoints2D* bounds)
{
  this->ComputeStringBounds(vtkStdString(string), bounds);
}

//------------------------------------------------------------------------------
void vtkContext2D::ComputeStringBounds(const char* string, float bounds[4])
{
  this->ComputeStringBounds(vtkStdString(string), bounds);
}

//------------------------------------------------------------------------------
void vtkContext2D::ComputeJustifiedStringBounds(const char* string, float bounds[4])
{
  this->Device->ComputeJustifiedStringBounds(string, bounds);
}

//------------------------------------------------------------------------------
int vtkContext2D::ComputeFontSizeForBoundedString(
  const vtkStdString& string, float width, float height)
{
  double orientation = this->GetTextProp()->GetOrientation();
  this->GetTextProp()->SetOrientation(0.0);

  float stringBounds[4];
  int currentFontSize = this->GetTextProp()->GetFontSize();
  this->ComputeStringBounds(string, stringBounds);

  // font size is too big
  if (stringBounds[2] > width || stringBounds[3] > height)
  {
    while (stringBounds[2] > width || stringBounds[3] > height)
    {
      --currentFontSize;
      this->GetTextProp()->SetFontSize(currentFontSize);
      this->ComputeStringBounds(string, stringBounds);
      if (currentFontSize < 0)
      {
        this->GetTextProp()->SetFontSize(0);
        return 0;
      }
    }
  }

  // font size is too small
  else
  {
    while (stringBounds[2] < width && stringBounds[3] < height)
    {
      ++currentFontSize;
      this->GetTextProp()->SetFontSize(currentFontSize);
      this->ComputeStringBounds(string, stringBounds);
    }
    --currentFontSize;
    this->GetTextProp()->SetFontSize(currentFontSize);
  }

  this->GetTextProp()->SetOrientation(orientation);
  return currentFontSize;
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMathTextString(vtkPoints2D* point, const vtkStdString& string)
{
  float* f = vtkArrayDownCast<vtkFloatArray>(point->GetData())->GetPointer(0);
  this->DrawMathTextString(f[0], f[1], string);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMathTextString(float x, float y, const vtkStdString& string)
{
  if (!this->Device)
  {
    vtkErrorMacro(<< "Attempted to paint with no active vtkContextDevice2D.");
    return;
  }
  if (string.empty())
  {
    return;
  }
  float f[] = { x, y };
  this->Device->DrawMathTextString(f, string);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMathTextString(vtkPoints2D* point, const char* string)
{
  float* f = vtkArrayDownCast<vtkFloatArray>(point->GetData())->GetPointer(0);
  this->DrawMathTextString(f[0], f[1], vtkStdString(string));
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMathTextString(float x, float y, const char* string)
{
  this->DrawMathTextString(x, y, vtkStdString(string));
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMathTextString(
  vtkPoints2D* point, const vtkStdString& string, const vtkStdString& fallback)
{
  if (this->Device->MathTextIsSupported())
  {
    this->DrawMathTextString(point, string);
  }
  else
  {
    this->DrawString(point, fallback);
  }
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMathTextString(
  float x, float y, const vtkStdString& string, const vtkStdString& fallback)
{
  if (this->Device->MathTextIsSupported())
  {
    this->DrawMathTextString(x, y, string);
  }
  else
  {
    this->DrawString(x, y, fallback);
  }
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMathTextString(vtkPoints2D* point, const char* string, const char* fallback)
{
  if (this->Device->MathTextIsSupported())
  {
    this->DrawMathTextString(point, string);
  }
  else
  {
    this->DrawString(point, fallback);
  }
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawMathTextString(float x, float y, const char* string, const char* fallback)
{
  if (this->Device->MathTextIsSupported())
  {
    this->DrawMathTextString(x, y, string);
  }
  else
  {
    this->DrawString(x, y, fallback);
  }
}

//------------------------------------------------------------------------------
bool vtkContext2D::MathTextIsSupported()
{
  return this->Device->MathTextIsSupported();
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawImage(float x, float y, vtkImageData* image)
{
  float p[] = { x, y };
  this->Device->DrawImage(&p[0], 1.0, image);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawImage(float x, float y, float scale, vtkImageData* image)
{
  float p[] = { x, y };
  this->Device->DrawImage(&p[0], scale, image);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawImage(const vtkRectf& pos, vtkImageData* image)
{
  this->Device->DrawImage(pos, image);
}

//------------------------------------------------------------------------------
void vtkContext2D::DrawPolyData(
  float x, float y, vtkPolyData* polyData, vtkUnsignedCharArray* colors, int scalarMode)
{
  float p[] = { x, y };
  this->Device->DrawPolyData(&p[0], 1.0, polyData, colors, scalarMode);
}

//------------------------------------------------------------------------------
void vtkContext2D::ApplyPen(vtkPen* pen)
{
  this->Device->ApplyPen(pen);
}

//------------------------------------------------------------------------------
vtkPen* vtkContext2D::GetPen()
{
  if (this->Device)
  {
    return this->Device->GetPen();
  }
  return nullptr;
}

//------------------------------------------------------------------------------
void vtkContext2D::ApplyBrush(vtkBrush* brush)
{
  this->Device->ApplyBrush(brush);
}

//------------------------------------------------------------------------------
vtkBrush* vtkContext2D::GetBrush()
{
  if (this->Device)
  {
    return this->Device->GetBrush();
  }
  return nullptr;
}

//------------------------------------------------------------------------------
void vtkContext2D::ApplyTextProp(vtkTextProperty* prop)
{
  this->Device->ApplyTextProp(prop);
}

//------------------------------------------------------------------------------
vtkTextProperty* vtkContext2D::GetTextProp()
{
  if (this->Device)
  {
    return this->Device->GetTextProp();
  }
  return nullptr;
}

//------------------------------------------------------------------------------
void vtkContext2D::SetTransform(vtkTransform2D* transform)
{
  if (transform)
  {
    this->Device->SetMatrix(transform->GetMatrix());
  }
}

//------------------------------------------------------------------------------
vtkTransform2D* vtkContext2D::GetTransform()
{
  if (this->Device && this->Transform)
  {
    this->Device->GetMatrix(this->Transform->GetMatrix());
    return this->Transform;
  }
  return nullptr;
}

//------------------------------------------------------------------------------
void vtkContext2D::AppendTransform(vtkTransform2D* transform)
{
  if (!transform)
  {
    return;
  }

  this->Device->MultiplyMatrix(transform->GetMatrix());
}

//------------------------------------------------------------------------------
void vtkContext2D::PushMatrix()
{
  this->Device->PushMatrix();
}

//------------------------------------------------------------------------------
void vtkContext2D::PopMatrix()
{
  this->Device->PopMatrix();
}

//------------------------------------------------------------------------------
void vtkContext2D::ApplyId(vtkIdType id)
{
  assert("pre: zero_reserved_for_background" && id > 0);
  assert("pre: 24bit_limited" && id < 16777216);
  unsigned char rgba[4];

  // r most significant bits (16-23).
  // g (8-15)
  // b less significant bits (0-7).

  rgba[0] = static_cast<unsigned char>((id & 0xff0000) >> 16);
  rgba[1] = static_cast<unsigned char>((id & 0xff00) >> 8);
  rgba[2] = static_cast<unsigned char>(id & 0xff);
  rgba[3] = 1; // not used (because the colorbuffer in the default framebuffer
  // may not have an alpha channel)

  assert("check: valid_conversion" &&
    static_cast<vtkIdType>((static_cast<int>(rgba[0]) << 16) | (static_cast<int>(rgba[1]) << 8) |
      static_cast<int>(rgba[2])) == id);

  this->Device->SetColor4(rgba);
}

void vtkContext2D::SetContext3D(vtkContext3D* context)
{
  this->Context3D = context;
}

//------------------------------------------------------------------------------
vtkVector2f vtkContext2D::CalculateTextPosition(vtkPoints2D* rect)
{
  if (rect->GetNumberOfPoints() < 2)
  {
    return vtkVector2f(0, 0);
  }

  float* f = vtkArrayDownCast<vtkFloatArray>(rect->GetData())->GetPointer(0);
  return this->CalculateTextPosition(f);
}

//------------------------------------------------------------------------------
vtkVector2f vtkContext2D::CalculateTextPosition(const float rect[4])
{
  // Draw the text at the appropriate point inside the rect for the alignment
  // specified. This is a convenience when an area of the screen should have
  // text drawn that is aligned to the entire area.
  vtkVector2f p(0, 0);

  if (this->Device->GetTextProp()->GetJustification() == VTK_TEXT_LEFT)
  {
    p.SetX(rect[0]);
  }
  else if (this->Device->GetTextProp()->GetJustification() == VTK_TEXT_CENTERED)
  {
    p.SetX(rect[0] + 0.5f * rect[2]);
  }
  else
  {
    p.SetX(rect[0] + rect[2]);
  }

  if (this->Device->GetTextProp()->GetVerticalJustification() == VTK_TEXT_BOTTOM)
  {
    p.SetY(rect[1]);
  }
  else if (this->Device->GetTextProp()->GetVerticalJustification() == VTK_TEXT_CENTERED)
  {
    p.SetY(rect[1] + 0.5f * rect[3]);
  }
  else
  {
    p.SetY(rect[1] + rect[3]);
  }
  return p;
}

//------------------------------------------------------------------------------
vtkContext2D::vtkContext2D()
  : Context3D(nullptr)
{
  this->Device = nullptr;
  this->Transform = vtkTransform2D::New();
  this->BufferId = nullptr;
}

//------------------------------------------------------------------------------
vtkContext2D::~vtkContext2D()
{
  if (this->Device)
  {
    this->Device->Delete();
  }
  if (this->Transform)
  {
    this->Transform->Delete();
  }
}

//------------------------------------------------------------------------------
void vtkContext2D::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "Context Device: ";
  if (this->Device)
  {
    os << endl;
    this->Device->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << "(none)" << endl;
  }
}
VTK_ABI_NAMESPACE_END
