mirror of https://gitee.com/openkylin/vtk9.git
2400 lines
69 KiB
C++
2400 lines
69 KiB
C++
/*=========================================================================
|
|
|
|
Program: Visualization Toolkit
|
|
Module: vtkPDFContextDevice2D.cxx
|
|
|
|
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
|
|
All rights reserved.
|
|
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
|
|
|
|
This software is distributed WITHOUT ANY WARRANTY; without even
|
|
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
PURPOSE. See the above copyright notice for more information.
|
|
|
|
=========================================================================*/
|
|
|
|
#include "vtkPDFContextDevice2D.h"
|
|
|
|
#include "vtkAbstractMapper.h" // for VTK_SCALAR_MODE defines
|
|
#include "vtkBrush.h"
|
|
#include "vtkCellIterator.h"
|
|
#include "vtkCellTypes.h"
|
|
#include "vtkDataArray.h"
|
|
#include "vtkFloatArray.h"
|
|
#include "vtkImageBlend.h"
|
|
#include "vtkImageCast.h"
|
|
#include "vtkImageData.h"
|
|
#include "vtkImageExtractComponents.h"
|
|
#include "vtkImageFlip.h"
|
|
#include "vtkIntArray.h"
|
|
#include "vtkMatrix3x3.h"
|
|
#include "vtkMatrix4x4.h"
|
|
#include "vtkObjectFactory.h"
|
|
#include "vtkPath.h"
|
|
#include "vtkPen.h"
|
|
#include "vtkPointData.h"
|
|
#include "vtkPolyData.h"
|
|
#include "vtkRenderWindow.h"
|
|
#include "vtkRenderer.h"
|
|
#include "vtkStdString.h"
|
|
#include "vtkTextProperty.h"
|
|
#include "vtkTextRenderer.h"
|
|
#include "vtkTransform.h"
|
|
#include "vtkUnicodeString.h"
|
|
#include "vtkUnsignedCharArray.h"
|
|
#include "vtkVectorOperators.h"
|
|
|
|
#include <vtk_libharu.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
|
|
void GetPointBounds(float* points, int numPoints, HPDF_REAL bbox[4], const float radius = 0.f)
|
|
{
|
|
bbox[0] = static_cast<HPDF_REAL>(points[0]);
|
|
bbox[1] = static_cast<HPDF_REAL>(points[0]);
|
|
bbox[2] = static_cast<HPDF_REAL>(points[1]);
|
|
bbox[3] = static_cast<HPDF_REAL>(points[1]);
|
|
|
|
for (int i = 1; i < numPoints; ++i)
|
|
{
|
|
bbox[0] = std::min(bbox[0], static_cast<HPDF_REAL>(points[i * 2]));
|
|
bbox[1] = std::max(bbox[1], static_cast<HPDF_REAL>(points[i * 2]));
|
|
bbox[2] = std::min(bbox[2], static_cast<HPDF_REAL>(points[i * 2 + 1]));
|
|
bbox[3] = std::max(bbox[3], static_cast<HPDF_REAL>(points[i * 2 + 1]));
|
|
}
|
|
|
|
bbox[0] -= radius;
|
|
bbox[1] += radius;
|
|
bbox[2] -= radius;
|
|
bbox[3] += radius;
|
|
}
|
|
|
|
void PolygonToShading(
|
|
float* points, int numPoints, unsigned char* colors, int nc_comps, HPDF_Shading shading)
|
|
{
|
|
assert(numPoints >= 3);
|
|
|
|
// First triangle
|
|
for (int ptIdx = 0; ptIdx < 3; ++ptIdx)
|
|
{
|
|
const float* pt = points + ptIdx * 2;
|
|
const unsigned char* color = colors + ptIdx * nc_comps;
|
|
HPDF_Shading_AddVertexRGB(shading, HPDF_FREE_FORM_TRI_MESH_EDGEFLAG_NO_CONNECTION, pt[0], pt[1],
|
|
color[0], color[1], color[2]);
|
|
}
|
|
|
|
// Fan-out additional verts
|
|
for (int ptIdx = 3; ptIdx < numPoints; ++ptIdx)
|
|
{
|
|
const float* pt = points + ptIdx * 2;
|
|
const unsigned char* color = colors + ptIdx * nc_comps;
|
|
HPDF_Shading_AddVertexRGB(
|
|
shading, HPDF_FREE_FORM_TRI_MESH_EDGEFLAG_AC, pt[0], pt[1], color[0], color[1], color[2]);
|
|
}
|
|
}
|
|
|
|
void LineSegmentToShading(const float p1[2], const unsigned char rgb1[3], const float p2[2],
|
|
const unsigned char rgb2[3], float radius, HPDF_Shading shading)
|
|
{
|
|
float pDy = p2[1] - p1[1];
|
|
float pDx = p2[0] - p1[0];
|
|
float nDx = -pDy;
|
|
float nDy = pDx;
|
|
|
|
if (nDx == 0.f && nDy == 0.f)
|
|
{
|
|
return; // Points are coincident. Avoid division by zero below:
|
|
}
|
|
|
|
float tmpInvNorm = 1.f / std::sqrt(nDx * nDx + nDy * nDy);
|
|
nDx *= tmpInvNorm * radius;
|
|
nDy *= tmpInvNorm * radius;
|
|
|
|
float quad[8] = { p1[0] + nDx, p1[1] + nDy, p1[0] - nDx, p1[1] - nDy, p2[0] - nDx, p2[1] - nDy,
|
|
p2[0] + nDx, p2[1] + nDy };
|
|
unsigned char color[12] = { rgb1[0], rgb1[1], rgb1[2], rgb1[0], rgb1[1], rgb1[2], rgb2[0],
|
|
rgb2[1], rgb2[2], rgb2[0], rgb2[1], rgb2[2] };
|
|
PolygonToShading(quad, 4, color, 3, shading);
|
|
}
|
|
|
|
void PolyLineToShading(const float* points, int numPoints, const unsigned char* color, int nc_comps,
|
|
float radius, HPDF_Shading shading)
|
|
{
|
|
for (int i = 0; i < numPoints - 1; ++i)
|
|
{
|
|
const int n = i + 1;
|
|
LineSegmentToShading(
|
|
points + 2 * i, color + nc_comps * i, points + 2 * n, color + nc_comps * n, radius, shading);
|
|
}
|
|
}
|
|
|
|
std::pair<double, double> GetScaleFactor(vtkMatrix3x3* mat)
|
|
{
|
|
auto sign = [](double x) -> double { return x >= 0. ? 1. : -1; };
|
|
|
|
auto a = mat->GetData()[0];
|
|
auto b = mat->GetData()[1];
|
|
auto c = mat->GetData()[3];
|
|
auto d = mat->GetData()[4];
|
|
|
|
double sx = sign(a) * std::sqrt(a * a + b * b);
|
|
double sy = sign(d) * std::sqrt(c * c + d * d);
|
|
|
|
return std::make_pair(sx, sy);
|
|
}
|
|
|
|
} // end anon namespace
|
|
|
|
// Need to be able to use vtkColor3f in a std::map. Must be outside of the anon
|
|
// namespace to work.
|
|
static bool operator<(const vtkColor3f& a, const vtkColor3f& b)
|
|
{
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
if (a[i] < b[i])
|
|
{
|
|
return true;
|
|
}
|
|
else if (a[i] > b[i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct TextHelper
|
|
{
|
|
struct Line
|
|
{
|
|
Line(std::string&& str, HPDF_REAL width)
|
|
: String(std::move(str))
|
|
, Width(width)
|
|
{
|
|
}
|
|
|
|
Line(const std::string& str, HPDF_REAL width)
|
|
: String(str)
|
|
, Width(width)
|
|
{
|
|
}
|
|
|
|
std::string String;
|
|
HPDF_REAL Width;
|
|
};
|
|
|
|
HPDF_Doc Document;
|
|
HPDF_Page Page;
|
|
vtkTextProperty* TextProp;
|
|
HPDF_Font Font;
|
|
const std::string& String;
|
|
vtkMatrix3x3* Transform;
|
|
double ScaleX;
|
|
double ScaleY;
|
|
|
|
HPDF_REAL FontSize;
|
|
HPDF_Box FontBBox;
|
|
HPDF_REAL BBoxWidth;
|
|
HPDF_REAL BBoxHeight;
|
|
HPDF_REAL Theta;
|
|
HPDF_REAL SineTheta;
|
|
HPDF_REAL CosineTheta;
|
|
HPDF_REAL LineHeight;
|
|
HPDF_REAL Leading;
|
|
HPDF_REAL Ascent;
|
|
HPDF_REAL Descent;
|
|
std::vector<Line> Lines;
|
|
bool Valid;
|
|
|
|
TextHelper(
|
|
HPDF_Doc doc, HPDF_Page page, vtkTextProperty* tprop, const std::string& str, vtkMatrix3x3* mat)
|
|
: Document{ doc }
|
|
, Page{ page }
|
|
, TextProp{ tprop }
|
|
, Font{ nullptr }
|
|
, String{ str }
|
|
, Transform(mat)
|
|
, ScaleX{ 0 }
|
|
, ScaleY{ 0 }
|
|
, FontSize{ 0.f }
|
|
, BBoxWidth{ 0.f }
|
|
, BBoxHeight{ 0.f }
|
|
, Theta{ vtkMath::RadiansFromDegrees(static_cast<float>(tprop->GetOrientation())) }
|
|
, SineTheta(std::sin(this->Theta))
|
|
, CosineTheta(std::cos(this->Theta))
|
|
, LineHeight{ 0.f }
|
|
, Leading{ 0.f }
|
|
, Ascent{ 0.f }
|
|
, Descent{ 0.f }
|
|
, Valid{ false }
|
|
{
|
|
std::tie(this->ScaleX, this->ScaleY) = GetScaleFactor(this->Transform);
|
|
|
|
if (this->LoadFont() && this->SplitStrings() && this->ComputeBBox())
|
|
{
|
|
this->Valid = true;
|
|
}
|
|
}
|
|
|
|
void DrawText(const float ptIn[2]) const
|
|
{
|
|
assert(this->Valid);
|
|
|
|
// Copy point since we'll modify it:
|
|
std::array<float, 2> pt = { { ptIn[0], ptIn[1] } };
|
|
|
|
this->JustifyStartPoint(pt.data());
|
|
|
|
// Prepare text state
|
|
HPDF_Page_BeginText(this->Page);
|
|
HPDF_Page_SetFontAndSize(this->Page, this->Font, this->FontSize);
|
|
HPDF_Page_SetTextRenderingMode(this->Page, HPDF_FILL);
|
|
HPDF_Page_SetTextLeading(this->Page, this->Leading);
|
|
|
|
// Initialize text matrix
|
|
HPDF_Page_SetTextMatrix(this->Page, this->CosineTheta, this->SineTheta, -this->SineTheta,
|
|
this->CosineTheta, pt[0], pt[1]);
|
|
|
|
// Draw lines:
|
|
switch (this->TextProp->GetJustification())
|
|
{
|
|
default:
|
|
case VTK_TEXT_LEFT:
|
|
this->PrintLeftJustifiedText();
|
|
break;
|
|
|
|
case VTK_TEXT_CENTERED:
|
|
this->PrintCenterJustifiedText();
|
|
break;
|
|
|
|
case VTK_TEXT_RIGHT:
|
|
this->PrintRightJustifiedText();
|
|
break;
|
|
}
|
|
|
|
HPDF_Page_EndText(this->Page);
|
|
}
|
|
|
|
private:
|
|
bool LoadFont()
|
|
{
|
|
int family = this->TextProp->GetFontFamily();
|
|
if (family == VTK_FONT_FILE)
|
|
{
|
|
const char* fontName =
|
|
HPDF_LoadTTFontFromFile(this->Document, this->TextProp->GetFontFile(), true);
|
|
this->Font = HPDF_GetFont(this->Document, fontName, "StandardEncoding");
|
|
}
|
|
else
|
|
{
|
|
std::ostringstream fontStr;
|
|
bool isBold = this->TextProp->GetBold() != 0;
|
|
bool isItalic = this->TextProp->GetItalic() != 0;
|
|
|
|
switch (family)
|
|
{
|
|
case VTK_ARIAL:
|
|
fontStr << "Helvetica";
|
|
if (isBold || isItalic)
|
|
{
|
|
fontStr << "-";
|
|
}
|
|
if (isBold)
|
|
{
|
|
fontStr << "Bold";
|
|
}
|
|
if (isItalic)
|
|
{
|
|
fontStr << "Oblique";
|
|
}
|
|
break;
|
|
|
|
case VTK_COURIER:
|
|
fontStr << "Courier";
|
|
if (isBold || isItalic)
|
|
{
|
|
fontStr << "-";
|
|
}
|
|
if (isBold)
|
|
{
|
|
fontStr << "Bold";
|
|
}
|
|
if (isItalic)
|
|
{
|
|
fontStr << "Oblique";
|
|
}
|
|
break;
|
|
|
|
case VTK_TIMES:
|
|
fontStr << "Times-";
|
|
if (isBold && isItalic)
|
|
{
|
|
fontStr << "BoldItalic";
|
|
}
|
|
else if (isBold)
|
|
{
|
|
fontStr << "Bold";
|
|
}
|
|
else if (isItalic)
|
|
{
|
|
fontStr << "Italic";
|
|
}
|
|
else
|
|
{
|
|
fontStr << "Roman";
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Garbage in, garbage out:
|
|
vtkGenericWarningMacro("Unknown font code (" << family << ")");
|
|
return false;
|
|
}
|
|
|
|
this->Font = HPDF_GetFont(this->Document, fontStr.str().c_str(), "StandardEncoding");
|
|
}
|
|
|
|
if (!this->Font)
|
|
{
|
|
vtkGenericWarningMacro("Error preparing libharu font object.");
|
|
return false;
|
|
}
|
|
|
|
// Reduce the font size by the current y scale factor:
|
|
this->FontSize = static_cast<HPDF_REAL>(this->TextProp->GetFontSize());
|
|
this->FontSize /= this->ScaleY;
|
|
|
|
// Had to dig to find this info, so commenting it here:
|
|
// The font's bbox is the box containing "all glyphs if placed with their
|
|
// origins coincident. It is independent of fontsize.
|
|
// In libharu, the textHeight is computed as:
|
|
// (bbox.top - bbox.bottom) / 1000 * fontSize
|
|
// In VTK, the default leading is:
|
|
// (textHeight) * tprop->LineSpacing
|
|
// From this, we can compute the leading needed for libharu:
|
|
HPDF_REAL fontScale = this->FontSize / 1000.f;
|
|
this->FontBBox = HPDF_Font_GetBBox(this->Font);
|
|
this->LineHeight = (this->FontBBox.top - this->FontBBox.bottom) * fontScale;
|
|
this->Leading = this->LineHeight * this->TextProp->GetLineSpacing();
|
|
|
|
this->Ascent = HPDF_Font_GetAscent(this->Font) * fontScale;
|
|
this->Descent = HPDF_Font_GetDescent(this->Font) * fontScale;
|
|
|
|
return true;
|
|
}
|
|
|
|
HPDF_REAL ComputeLineWidth(const std::string& str)
|
|
{
|
|
HPDF_TextWidth widthAttr = HPDF_Font_TextWidth(this->Font,
|
|
reinterpret_cast<const HPDF_BYTE*>(str.c_str()), static_cast<HPDF_UINT>(str.size()));
|
|
|
|
HPDF_REAL wordSpace = HPDF_Page_GetWordSpace(this->Page);
|
|
HPDF_REAL charSpace = HPDF_Page_GetCharSpace(this->Page);
|
|
|
|
return (wordSpace * widthAttr.numwords + charSpace * widthAttr.numchars +
|
|
widthAttr.width * this->FontSize / 1000);
|
|
}
|
|
|
|
bool SplitStrings()
|
|
{
|
|
this->BBoxWidth = 0;
|
|
|
|
std::string::const_iterator it = this->String.begin();
|
|
std::string::const_iterator end = this->String.end();
|
|
std::string::const_iterator itEnd = std::find(it, end, '\n');
|
|
|
|
while (itEnd != end)
|
|
{
|
|
std::string tmp{ it, itEnd };
|
|
HPDF_REAL width = this->ComputeLineWidth(tmp);
|
|
this->BBoxWidth = std::max(this->BBoxWidth, width);
|
|
this->Lines.emplace_back(std::move(tmp), width);
|
|
|
|
it = itEnd;
|
|
std::advance(it, 1);
|
|
itEnd = std::find(it, end, '\n');
|
|
}
|
|
|
|
// Last line:
|
|
{
|
|
std::string tmp{ it, itEnd };
|
|
HPDF_REAL width = this->ComputeLineWidth(tmp);
|
|
if (width > 0) // Skip empty trailing lines:
|
|
{
|
|
this->BBoxWidth = std::max(this->BBoxWidth, width);
|
|
this->Lines.emplace_back(std::move(tmp), width);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ComputeBBox()
|
|
{
|
|
std::size_t nLines = this->Lines.size();
|
|
switch (nLines)
|
|
{
|
|
case 0:
|
|
this->BBoxHeight = 0;
|
|
break;
|
|
|
|
case 1:
|
|
this->BBoxHeight = this->Ascent;
|
|
break;
|
|
|
|
default:
|
|
this->BBoxHeight = this->LineHeight + this->Leading * (nLines - 1);
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Move the baseline of the first line to the appropriate location given
|
|
// the justification parameters
|
|
void JustifyStartPoint(float pt[2]) const
|
|
{
|
|
std::array<float, 2> offset = { { 0.f, -this->Ascent } };
|
|
|
|
switch (this->TextProp->GetJustification())
|
|
{
|
|
default:
|
|
case VTK_TEXT_LEFT:
|
|
break;
|
|
|
|
case VTK_TEXT_CENTERED:
|
|
offset[0] -= this->BBoxWidth * 0.5f;
|
|
break;
|
|
|
|
case VTK_TEXT_RIGHT:
|
|
offset[0] -= this->BBoxWidth;
|
|
break;
|
|
}
|
|
|
|
switch (this->TextProp->GetVerticalJustification())
|
|
{
|
|
case VTK_TEXT_BOTTOM:
|
|
offset[1] += this->BBoxHeight;
|
|
break;
|
|
|
|
case VTK_TEXT_CENTERED:
|
|
offset[1] += this->BBoxHeight * 0.5f;
|
|
break;
|
|
|
|
default:
|
|
case VTK_TEXT_TOP:
|
|
break;
|
|
}
|
|
|
|
// Account for tprop rotation:
|
|
std::array<float, 2> tmp = { {
|
|
offset[0] * this->CosineTheta - offset[1] * this->SineTheta,
|
|
offset[0] * this->SineTheta + offset[1] * this->CosineTheta,
|
|
} };
|
|
|
|
pt[0] += tmp[0];
|
|
pt[1] += tmp[1];
|
|
}
|
|
|
|
void PrintLeftJustifiedText() const
|
|
{
|
|
for (const auto& line : this->Lines)
|
|
{
|
|
HPDF_Page_ShowText(this->Page, line.String.c_str());
|
|
HPDF_Page_MoveToNextLine(this->Page);
|
|
}
|
|
}
|
|
|
|
void PrintCenterJustifiedText() const
|
|
{
|
|
HPDF_REAL currentOffset = 0; // for centering
|
|
for (size_t i = 0; i < this->Lines.size(); ++i)
|
|
{
|
|
const Line& line = this->Lines[i];
|
|
|
|
if (i == 0)
|
|
{ // Center the first line:
|
|
currentOffset = (this->BBoxWidth - line.Width) * 0.5f;
|
|
HPDF_Page_MoveTextPos(this->Page, currentOffset, 0);
|
|
}
|
|
else
|
|
{
|
|
// This line's offset:
|
|
HPDF_REAL lineOffset = (this->BBoxWidth - line.Width) * 0.5;
|
|
|
|
// The incremental change to effect this lines offset relative to the
|
|
// current offset:
|
|
HPDF_REAL incrOffset = lineOffset - currentOffset;
|
|
|
|
// Center current line and advance to new line:
|
|
HPDF_Page_MoveTextPos(this->Page, incrOffset, -this->Leading);
|
|
|
|
// Update for next iteration:
|
|
currentOffset = lineOffset;
|
|
}
|
|
|
|
HPDF_Page_ShowText(this->Page, line.String.c_str());
|
|
}
|
|
}
|
|
|
|
void PrintRightJustifiedText() const
|
|
{
|
|
HPDF_REAL currentOffset = 0; // for centering
|
|
for (size_t i = 0; i < this->Lines.size(); ++i)
|
|
{
|
|
const Line& line = this->Lines[i];
|
|
|
|
if (i == 0)
|
|
{ // Right justify the first line:
|
|
currentOffset = this->BBoxWidth - line.Width;
|
|
HPDF_Page_MoveTextPos(this->Page, currentOffset, 0);
|
|
}
|
|
else
|
|
{
|
|
// This line's offset:
|
|
HPDF_REAL lineOffset = this->BBoxWidth - line.Width;
|
|
|
|
// The incremental change to effect this lines offset relative to the
|
|
// current offset:
|
|
HPDF_REAL incrOffset = lineOffset - currentOffset;
|
|
|
|
// Center current line and advance to new line:
|
|
HPDF_Page_MoveTextPos(this->Page, incrOffset, -this->Leading);
|
|
|
|
// Update for next iteration:
|
|
currentOffset = lineOffset;
|
|
}
|
|
|
|
HPDF_Page_ShowText(this->Page, line.String.c_str());
|
|
}
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
// vtkPDFContextDevice2D::Details
|
|
//------------------------------------------------------------------------------
|
|
|
|
struct vtkPDFContextDevice2D::Details
|
|
{
|
|
Details()
|
|
: Page(nullptr)
|
|
{
|
|
}
|
|
|
|
HPDF_Doc Document;
|
|
HPDF_Page Page;
|
|
|
|
std::map<unsigned char, HPDF_ExtGState> AlphaGStateMap;
|
|
|
|
float computeWorldRadius(const float x1, const float y1, const float x2, const float y2,
|
|
vtkMatrix4x4* matrix, const float penWidth)
|
|
{
|
|
// Attempt to compute the radius in world space. For this computation
|
|
// we make the assumption that the following expression holds true:
|
|
//
|
|
// worldDiagonalLength worldRadius
|
|
// --------------------- = -------------
|
|
// screenDiagonalLength screenRadius
|
|
//
|
|
// Then we further assume that this->Matrix can take the points of our
|
|
// polydata bounding box and transform them into top-level pixel coords
|
|
// of our render window.
|
|
double transMat[9];
|
|
double* mat = matrix->GetData();
|
|
vtkPDFContextDevice2D::Matrix4ToMatrix3(mat, transMat);
|
|
|
|
const float boundPt1[3] = { static_cast<float>(x1), static_cast<float>(y1), 1.0f };
|
|
|
|
const float boundPt2[3] = { static_cast<float>(x2), static_cast<float>(y2), 1.0f };
|
|
|
|
float boundPt1Px[3];
|
|
float boundPt2Px[3];
|
|
|
|
vtkMatrix3x3::MultiplyPoint(transMat, boundPt1, boundPt1Px);
|
|
vtkMatrix3x3::MultiplyPoint(transMat, boundPt2, boundPt2Px);
|
|
|
|
// Compute the length of the bounding box diagonal in world space
|
|
const float wdx = boundPt1[0] - boundPt2[0];
|
|
const float wdy = boundPt1[1] - boundPt2[1];
|
|
const float worldDiag = std::sqrt(wdx * wdx + wdy * wdy);
|
|
|
|
// Do the same as above, but in screen space
|
|
const float sdx = boundPt1Px[0] - boundPt2Px[0];
|
|
const float sdy = boundPt1Px[1] - boundPt2Px[1];
|
|
const float screenDiag = std::sqrt(sdx * sdx + sdy * sdy);
|
|
|
|
// Pen width is twice the radius in screen space, so get that in world space
|
|
const float worldPenWidth = penWidth * (worldDiag / screenDiag);
|
|
|
|
return worldPenWidth * 0.5f;
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
// vtkPDFContextDevice2D
|
|
//------------------------------------------------------------------------------
|
|
|
|
vtkStandardNewMacro(vtkPDFContextDevice2D);
|
|
vtkCxxSetObjectMacro(vtkPDFContextDevice2D, Renderer, vtkRenderer);
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::PrintSelf(std::ostream& os, vtkIndent indent)
|
|
{
|
|
this->Superclass::PrintSelf(os, indent);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::SetHaruObjects(void* doc, void* page)
|
|
{
|
|
if (page && doc)
|
|
{
|
|
this->Impl->Document = *static_cast<HPDF_Doc*>(doc);
|
|
this->Impl->Page = *static_cast<HPDF_Page*>(page);
|
|
}
|
|
else
|
|
{
|
|
this->Impl->Document = nullptr;
|
|
this->Impl->Page = nullptr;
|
|
}
|
|
this->Impl->AlphaGStateMap.clear();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawPoly(float* points, int n, unsigned char* colors, int nc_comps)
|
|
{
|
|
assert(nc_comps == 0 || colors != nullptr);
|
|
assert(n > 0);
|
|
assert(points != nullptr);
|
|
|
|
if (this->Pen->GetLineType() == vtkPen::NO_PEN)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!colors && this->Pen->GetColorObject().GetAlpha() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this->PushGraphicsState();
|
|
this->ApplyPenState();
|
|
|
|
if (colors == nullptr)
|
|
{
|
|
HPDF_Page_MoveTo(this->Impl->Page, points[0], points[1]);
|
|
for (int i = 1; i < n; ++i)
|
|
{
|
|
HPDF_Page_LineTo(this->Impl->Page, points[i * 2], points[i * 2 + 1]);
|
|
}
|
|
this->Stroke();
|
|
}
|
|
else
|
|
{
|
|
vtkVector2f width = this->GetUnscaledPenWidth() * 0.5f;
|
|
const float radius = std::max(width[0], width[1]) * 0.5f;
|
|
HPDF_REAL bbox[4];
|
|
GetPointBounds(points, n, bbox, radius);
|
|
|
|
HPDF_Shading shading = HPDF_Shading_New(this->Impl->Document,
|
|
HPDF_SHADING_FREE_FORM_TRIANGLE_MESH, HPDF_CS_DEVICE_RGB, bbox[0], bbox[1], bbox[2], bbox[3]);
|
|
PolyLineToShading(points, n, colors, nc_comps, radius, shading);
|
|
HPDF_Page_SetShading(this->Impl->Page, shading);
|
|
}
|
|
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawLines(float* f, int n, unsigned char* colors, int nc_comps)
|
|
{
|
|
assert(nc_comps == 0 || colors != nullptr);
|
|
assert(n > 0);
|
|
assert(f != nullptr);
|
|
|
|
if (this->Pen->GetLineType() == vtkPen::NO_PEN)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!colors && this->Pen->GetColorObject().GetAlpha() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this->PushGraphicsState();
|
|
|
|
if (colors == nullptr)
|
|
{
|
|
this->ApplyPenState();
|
|
|
|
for (int i = 0; i < n / 2; ++i)
|
|
{
|
|
HPDF_Page_MoveTo(this->Impl->Page, f[i * 4], f[i * 4 + 1]);
|
|
HPDF_Page_LineTo(this->Impl->Page, f[i * 4 + 2], f[i * 4 + 3]);
|
|
}
|
|
this->Stroke();
|
|
}
|
|
else
|
|
{
|
|
vtkVector2f width = this->GetUnscaledPenWidth();
|
|
const float radius = std::max(width[0], width[1]) * 0.5f;
|
|
HPDF_REAL bbox[4];
|
|
GetPointBounds(f, n, bbox, radius);
|
|
|
|
HPDF_Shading shading = HPDF_Shading_New(this->Impl->Document,
|
|
HPDF_SHADING_FREE_FORM_TRIANGLE_MESH, HPDF_CS_DEVICE_RGB, bbox[0], bbox[1], bbox[2], bbox[3]);
|
|
|
|
for (int i = 0; i < n / 2; ++i)
|
|
{
|
|
const float* p1 = f + i * 4;
|
|
const unsigned char* rgb1 = colors + i * 2 * nc_comps;
|
|
const float* p2 = f + i * 6;
|
|
const unsigned char* rgb2 = colors + (i * 2 + 1) * nc_comps;
|
|
LineSegmentToShading(p1, rgb1, p2, rgb2, radius, shading);
|
|
}
|
|
|
|
HPDF_Page_SetShading(this->Impl->Page, shading);
|
|
}
|
|
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawPoints(float* points, int n, unsigned char* colors, int nc_comps)
|
|
{
|
|
assert(nc_comps == 0 || colors != nullptr);
|
|
assert(n > 0);
|
|
assert(points != nullptr);
|
|
|
|
if (!colors && this->Pen->GetColorObject().GetAlpha() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this->PushGraphicsState();
|
|
this->ApplyPenStateAsFill();
|
|
|
|
const vtkVector2f width = this->GetUnscaledPenWidth();
|
|
const vtkVector2f halfWidth = width * 0.5f;
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
if (nc_comps > 0)
|
|
{
|
|
this->ApplyFillColor(colors + i * nc_comps, nc_comps);
|
|
}
|
|
float originX = points[i * 2] - halfWidth[0];
|
|
float originY = points[i * 2 + 1] - halfWidth[1];
|
|
HPDF_Page_Rectangle(this->Impl->Page, originX, originY, width[0], width[1]);
|
|
this->Fill();
|
|
}
|
|
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawPointSprites(
|
|
vtkImageData* spriteIn, float* points, int n, unsigned char* colors, int nc_comps)
|
|
{
|
|
assert(points);
|
|
assert(n > 0);
|
|
assert(nc_comps == 0 || colors);
|
|
assert(spriteIn);
|
|
|
|
vtkImageData* rgb = this->PrepareImageData(spriteIn);
|
|
if (!rgb)
|
|
{
|
|
vtkErrorMacro("Unsupported point sprite format.");
|
|
return;
|
|
}
|
|
|
|
assert(rgb->GetScalarType() == VTK_UNSIGNED_CHAR);
|
|
assert(rgb->GetNumberOfScalarComponents() == 3);
|
|
|
|
int dims[3];
|
|
rgb->GetDimensions(dims);
|
|
vtkIdType numPoints = rgb->GetNumberOfPoints();
|
|
unsigned char* bufIn = static_cast<unsigned char*>(rgb->GetScalarPointer());
|
|
|
|
const float sizeFactor = this->Pen->GetWidth() / static_cast<float>(std::max(dims[0], dims[1]));
|
|
const float width = dims[0] * sizeFactor;
|
|
const float height = dims[1] * sizeFactor;
|
|
const float halfWidth = width * 0.5f;
|
|
const float halfHeight = height * 0.5f;
|
|
|
|
this->PushGraphicsState();
|
|
|
|
// The HPDF_Images are cleaned up by libharu when we finish writing the file.
|
|
typedef std::map<vtkColor3f, HPDF_Image> SpriteMap;
|
|
SpriteMap spriteMap;
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
const float* p = points + 2 * i;
|
|
|
|
vtkColor3f color;
|
|
unsigned char alpha = 255;
|
|
if (colors)
|
|
{
|
|
unsigned char* c = colors + nc_comps * i;
|
|
switch (nc_comps)
|
|
{
|
|
case 3:
|
|
color.Set(c[0] / 255.f, c[1] / 255.f, c[2] / 255.f);
|
|
break;
|
|
|
|
case 4:
|
|
color.Set(c[0] / 255.f, c[1] / 255.f, c[2] / 255.f);
|
|
alpha = c[3];
|
|
break;
|
|
|
|
default:
|
|
vtkErrorMacro("Unsupported number of color components: " << nc_comps);
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vtkColor4ub penColor = this->Pen->GetColorObject();
|
|
color.Set(penColor[0] / 255.f, penColor[1] / 255.f, penColor[2] / 255.f);
|
|
alpha = penColor[3];
|
|
}
|
|
|
|
HPDF_Image sprite;
|
|
|
|
SpriteMap::iterator it = spriteMap.find(color);
|
|
if (it != spriteMap.end())
|
|
{
|
|
sprite = it->second;
|
|
}
|
|
else
|
|
{
|
|
std::vector<unsigned char> coloredBuf;
|
|
coloredBuf.reserve(numPoints * 3);
|
|
// Using int since we're iterating to j < 0 (and vtkIdType is unsigned).
|
|
// It's very unlikely that numPoints will be larger than INT_MAX, but
|
|
// we'll check anyway. This throws a warning (taut-compare) when
|
|
// vtkIdType is a 32-bit integer, so we disable it in that case.
|
|
#ifdef VTK_USE_64BIT_IDS
|
|
if (numPoints > static_cast<vtkIdType>(VTK_INT_MAX))
|
|
{
|
|
vtkErrorMacro("FIXME: Image data too large for indexing with int.");
|
|
this->PopGraphicsState();
|
|
rgb->UnRegister(this);
|
|
return;
|
|
}
|
|
#endif // VTK_USE_64BIT_IDS
|
|
for (int j = static_cast<int>(numPoints) - 1; j >= 0; --j)
|
|
{
|
|
unsigned char* pointColor = bufIn + 3 * j;
|
|
// This is what the OpenGL implementation does:
|
|
coloredBuf.push_back(pointColor[0] * color[0]);
|
|
coloredBuf.push_back(pointColor[1] * color[1]);
|
|
coloredBuf.push_back(pointColor[2] * color[2]);
|
|
}
|
|
|
|
sprite = HPDF_LoadRawImageFromMem(
|
|
this->Impl->Document, coloredBuf.data(), dims[0], dims[1], HPDF_CS_DEVICE_RGB, 8);
|
|
spriteMap.insert(std::make_pair(color, sprite));
|
|
}
|
|
|
|
this->ApplyFillAlpha(alpha);
|
|
HPDF_Page_DrawImage(
|
|
this->Impl->Page, sprite, p[0] - halfWidth, p[1] - halfHeight, width, height);
|
|
}
|
|
|
|
rgb->UnRegister(this);
|
|
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawMarkers(
|
|
int shape, bool highlight, float* points, int n, unsigned char* colors, int nc_comps)
|
|
{
|
|
assert(points);
|
|
assert(n > 0);
|
|
assert(nc_comps == 0 || colors);
|
|
|
|
this->PushGraphicsState();
|
|
|
|
switch (shape)
|
|
{
|
|
case VTK_MARKER_CROSS:
|
|
this->DrawCrossMarkers(highlight, points, n, colors, nc_comps);
|
|
break;
|
|
|
|
default:
|
|
// default is here for consistency with old impl -- defaults to plus for
|
|
// unrecognized shapes.
|
|
VTK_FALLTHROUGH;
|
|
case VTK_MARKER_PLUS:
|
|
this->DrawPlusMarkers(highlight, points, n, colors, nc_comps);
|
|
break;
|
|
|
|
case VTK_MARKER_SQUARE:
|
|
this->DrawSquareMarkers(highlight, points, n, colors, nc_comps);
|
|
break;
|
|
|
|
case VTK_MARKER_CIRCLE:
|
|
this->DrawCircleMarkers(highlight, points, n, colors, nc_comps);
|
|
break;
|
|
|
|
case VTK_MARKER_DIAMOND:
|
|
this->DrawDiamondMarkers(highlight, points, n, colors, nc_comps);
|
|
break;
|
|
}
|
|
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawQuad(float* p, int n)
|
|
{
|
|
assert(n > 0);
|
|
assert(p != nullptr);
|
|
|
|
if (this->Brush->GetColorObject().GetAlpha() == 0 && this->Brush->GetTexture() == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this->PushGraphicsState();
|
|
this->ApplyBrushState();
|
|
this->RegisterTexturePoints(p, n);
|
|
|
|
size_t numQuads = n / 4;
|
|
for (size_t quad = 0; quad < numQuads; ++quad)
|
|
{
|
|
const size_t i = quad * 8; // (4 verts / quad) * (2 floats / vert)
|
|
|
|
HPDF_Page_MoveTo(this->Impl->Page, p[i], p[i + 1]);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[i + 2], p[i + 3]);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[i + 4], p[i + 5]);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[i + 6], p[i + 7]);
|
|
HPDF_Page_ClosePath(this->Impl->Page);
|
|
}
|
|
|
|
this->Fill();
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawQuadStrip(float* p, int n)
|
|
{
|
|
assert(n > 0);
|
|
assert(p != nullptr);
|
|
|
|
if (this->Brush->GetColorObject().GetAlpha() == 0 && this->Brush->GetTexture() == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this->PushGraphicsState();
|
|
this->ApplyBrushState();
|
|
this->RegisterTexturePoints(p, n);
|
|
|
|
size_t numQuads = n / 2 - 1;
|
|
for (size_t quad = 0; quad < numQuads; ++quad)
|
|
{
|
|
const size_t i = quad * 4;
|
|
|
|
HPDF_Page_MoveTo(this->Impl->Page, p[i], p[i + 1]);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[i + 2], p[i + 3]);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[i + 4], p[i + 5]);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[i + 6], p[i + 7]);
|
|
HPDF_Page_ClosePath(this->Impl->Page);
|
|
}
|
|
|
|
this->Fill();
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawPolygon(float* f, int n)
|
|
{
|
|
assert(n > 0);
|
|
assert(f != nullptr);
|
|
|
|
if (this->Brush->GetColorObject().GetAlpha() == 0 && this->Brush->GetTexture() == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this->PushGraphicsState();
|
|
this->ApplyBrushState();
|
|
this->RegisterTexturePoints(f, n);
|
|
|
|
HPDF_Page_MoveTo(this->Impl->Page, f[0], f[1]);
|
|
|
|
for (int i = 1; i < n; ++i)
|
|
{
|
|
HPDF_Page_LineTo(this->Impl->Page, f[i * 2], f[i * 2 + 1]);
|
|
}
|
|
|
|
HPDF_Page_ClosePath(this->Impl->Page);
|
|
|
|
this->Fill();
|
|
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawColoredPolygon(
|
|
float* points, int numPoints, unsigned char* colors, int nc_comps)
|
|
{
|
|
assert(numPoints > 0);
|
|
assert(points != nullptr);
|
|
|
|
// Just use the standard draw method if there is a texture or colors are not
|
|
// specified:
|
|
if (this->Brush->GetTexture() != nullptr || nc_comps == 0)
|
|
{
|
|
this->DrawPolygon(points, numPoints);
|
|
return;
|
|
}
|
|
|
|
// If all of the points have the same color, use a more compact method to
|
|
// draw the poly:
|
|
bool sameColor = true;
|
|
for (int i = 1; i < numPoints && sameColor; ++i)
|
|
{
|
|
sameColor = std::equal(colors, colors + nc_comps, colors + (i * nc_comps));
|
|
}
|
|
if (sameColor)
|
|
{
|
|
const vtkColor4ub oldBrush = this->Brush->GetColorObject();
|
|
switch (nc_comps)
|
|
{
|
|
case 4:
|
|
this->Brush->SetOpacity(colors[3]);
|
|
VTK_FALLTHROUGH;
|
|
case 3:
|
|
this->Brush->SetColor(colors);
|
|
break;
|
|
|
|
default:
|
|
vtkWarningMacro("Unsupported number of color components: " << nc_comps);
|
|
return;
|
|
}
|
|
|
|
this->DrawPolygon(points, numPoints);
|
|
this->Brush->SetColor(oldBrush);
|
|
return;
|
|
}
|
|
|
|
this->PushGraphicsState();
|
|
|
|
HPDF_REAL bbox[4];
|
|
GetPointBounds(points, numPoints, bbox);
|
|
|
|
HPDF_Shading shading = HPDF_Shading_New(this->Impl->Document,
|
|
HPDF_SHADING_FREE_FORM_TRIANGLE_MESH, HPDF_CS_DEVICE_RGB, bbox[0], bbox[1], bbox[2], bbox[3]);
|
|
|
|
PolygonToShading(points, numPoints, colors, nc_comps, shading);
|
|
|
|
HPDF_Page_SetShading(this->Impl->Page, shading);
|
|
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::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);
|
|
|
|
this->PushGraphicsState();
|
|
this->ApplyBrushState();
|
|
|
|
// Register the bounds of the outer ellipse:
|
|
float bounds[8] = { x - outRx, y - outRy, x - outRx, y + outRy, x + outRx, y + outRy, x + outRx,
|
|
y - outRy };
|
|
this->RegisterTexturePoints(bounds, 4);
|
|
|
|
// If we're drawing a complete ellipse, just use the built-in ellipse call:
|
|
if (std::fabs(stopAngle - startAngle) >= 360.f)
|
|
{
|
|
HPDF_Page_Ellipse(this->Impl->Page, x, y, outRx, outRy);
|
|
if (inRx > 0.f || inRy > 0.f)
|
|
{
|
|
HPDF_Page_Ellipse(this->Impl->Page, x, y, inRx, inRy);
|
|
this->FillEvenOdd();
|
|
}
|
|
else
|
|
{
|
|
this->Fill();
|
|
}
|
|
}
|
|
// If we're drawing circles, use the built-in arc calls:
|
|
else if (inRx == inRy && outRx == outRy)
|
|
{
|
|
// VTK uses 0 degrees = East with CCW rotation, but
|
|
// Haru uses 0 degrees = North with CW rotation. Adjust for this:
|
|
float hStart = -(stopAngle - 90.f);
|
|
float hStop = -(startAngle - 90.f);
|
|
|
|
HPDF_Page_Arc(this->Impl->Page, x, y, outRx, hStart, hStop);
|
|
if (inRx > 0.f)
|
|
{
|
|
HPDF_Page_Arc(this->Impl->Page, x, y, inRx, hStart, hStop);
|
|
this->FillEvenOdd();
|
|
}
|
|
else
|
|
{
|
|
this->Fill();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Haru doesn't support drawing ellipses that have start/stop angles.
|
|
// You can either do an ellipse or a circle with start/stop, but not both.
|
|
// We if have to do both, we'll need to rasterize the path.
|
|
this->DrawEllipticArcSegments(x, y, outRx, outRy, startAngle, stopAngle, true);
|
|
if (inRx > 0 || inRy > 0.f)
|
|
{
|
|
this->DrawEllipticArcSegments(x, y, inRx, inRy, stopAngle, startAngle, false);
|
|
HPDF_Page_ClosePath(this->Impl->Page);
|
|
this->FillEvenOdd();
|
|
}
|
|
else
|
|
{
|
|
HPDF_Page_ClosePath(this->Impl->Page);
|
|
this->Fill();
|
|
}
|
|
}
|
|
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::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);
|
|
|
|
this->PushGraphicsState();
|
|
this->ApplyPenState();
|
|
this->ApplyBrushState();
|
|
|
|
// If we're drawing a complete ellipse, just use the built-in ellipse call:
|
|
if (std::fabs(stopAngle - startAngle) >= 360.f)
|
|
{
|
|
HPDF_Page_Ellipse(this->Impl->Page, x, y, rX, rY);
|
|
this->Fill(true);
|
|
}
|
|
// If we're drawing circles, use the built-in arc calls:
|
|
else if (rX == rY)
|
|
{
|
|
// VTK uses 0 degrees = East with CCW rotation, but
|
|
// Haru uses 0 degrees = North with CW rotation. Adjust for this:
|
|
float hStart = -(stopAngle - 90.f);
|
|
float hStop = -(startAngle - 90.f);
|
|
|
|
HPDF_Page_Arc(this->Impl->Page, x, y, rX, hStart, hStop);
|
|
HPDF_Page_ClosePath(this->Impl->Page);
|
|
this->Fill();
|
|
HPDF_Page_Arc(this->Impl->Page, x, y, rX, hStart, hStop);
|
|
this->Stroke();
|
|
}
|
|
else
|
|
{
|
|
// Haru doesn't support drawing ellipses that have start/stop angles.
|
|
// You can either do an ellipse or a circle with start/stop, but not both.
|
|
// We if have to do both, we'll need to rasterize the path.
|
|
this->DrawEllipticArcSegments(x, y, rX, rY, startAngle, stopAngle, true);
|
|
HPDF_Page_ClosePath(this->Impl->Page);
|
|
this->Fill();
|
|
this->DrawEllipticArcSegments(x, y, rX, rY, startAngle, stopAngle, true);
|
|
this->Stroke();
|
|
}
|
|
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawString(float* point, const vtkStdString& string)
|
|
{
|
|
vtkTextRenderer* tren = vtkTextRenderer::GetInstance();
|
|
if (!tren)
|
|
{
|
|
vtkGenericWarningMacro("vtkTextRenderer unavailable. Link to "
|
|
"vtkRenderingFreeType to get the default "
|
|
"implementation.");
|
|
return;
|
|
}
|
|
|
|
int backend = tren->DetectBackend(string);
|
|
|
|
this->PushGraphicsState();
|
|
|
|
if (backend != vtkTextRenderer::MathText)
|
|
{
|
|
vtkNew<vtkMatrix3x3> mat;
|
|
this->GetMatrix(mat);
|
|
TextHelper helper(this->Impl->Document, this->Impl->Page, this->TextProp, string, mat);
|
|
if (!helper.Valid)
|
|
{
|
|
vtkErrorMacro("Error preparing to draw string: " << string);
|
|
this->PopGraphicsState();
|
|
return;
|
|
}
|
|
|
|
this->ApplyTextPropertyState();
|
|
|
|
helper.DrawText(point);
|
|
}
|
|
else
|
|
{
|
|
vtkNew<vtkPath> path;
|
|
int dpi = this->Renderer->GetRenderWindow()->GetDPI();
|
|
if (!tren->StringToPath(this->TextProp, string, path, dpi, backend))
|
|
{
|
|
vtkErrorMacro("Error generating path for MathText string '" << string << "'.");
|
|
this->PopGraphicsState();
|
|
return;
|
|
}
|
|
|
|
this->ApplyTextPropertyState();
|
|
this->DrawPath(path, point[0], point[1]);
|
|
this->FillEvenOdd();
|
|
|
|
float bbox[4];
|
|
this->ComputeStringBounds(string, bbox);
|
|
HPDF_Page_SetRGBStroke(this->Impl->Page, 1, 0, 0);
|
|
HPDF_Page_Rectangle(this->Impl->Page, bbox[0], bbox[1] - bbox[3], bbox[2], bbox[3]);
|
|
HPDF_Page_Stroke(this->Impl->Page);
|
|
}
|
|
|
|
this->PopGraphicsState();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ComputeStringBounds(const vtkStdString& string, float bounds[4])
|
|
{
|
|
vtkNew<vtkMatrix3x3> mat;
|
|
this->GetMatrix(mat);
|
|
TextHelper helper(this->Impl->Document, this->Impl->Page, this->TextProp, string, mat);
|
|
if (!helper.Valid)
|
|
{
|
|
vtkErrorMacro("Error determining bounding box for string '" << string << "'.");
|
|
std::fill(bounds, bounds + 4, 0.f);
|
|
return;
|
|
}
|
|
|
|
bounds[0] = 0.f;
|
|
bounds[1] = 0.f;
|
|
bounds[2] = helper.BBoxWidth;
|
|
bounds[3] = helper.BBoxHeight;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawString(float* point, const vtkUnicodeString& string)
|
|
{
|
|
this->DrawString(point, std::string(string.utf8_str()));
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ComputeStringBounds(const vtkUnicodeString& string, float bounds[4])
|
|
{
|
|
this->ComputeStringBounds(string.utf8_str(), bounds);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ComputeJustifiedStringBounds(const char* string, float bounds[4])
|
|
{
|
|
this->ComputeStringBounds(string, bounds);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawMathTextString(float* point, const vtkStdString& str)
|
|
{
|
|
this->DrawString(point, str);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawImage(float p[2], float scale, vtkImageData* image)
|
|
{
|
|
assert(p);
|
|
assert(image);
|
|
|
|
int dims[3];
|
|
image->GetDimensions(dims);
|
|
dims[0] *= scale;
|
|
dims[1] *= scale;
|
|
this->DrawImage(vtkRectf(p[0], p[1], dims[0], dims[1]), image);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawImage(const vtkRectf& pos, vtkImageData* image)
|
|
{
|
|
assert(image);
|
|
|
|
vtkImageData* rgb = this->PrepareImageData(image);
|
|
if (!rgb)
|
|
{
|
|
return;
|
|
}
|
|
|
|
assert(rgb->GetScalarType() == VTK_UNSIGNED_CHAR);
|
|
assert(rgb->GetNumberOfScalarComponents() == 3);
|
|
|
|
int dims[3];
|
|
rgb->GetDimensions(dims);
|
|
const HPDF_BYTE* buf = static_cast<HPDF_BYTE*>(rgb->GetScalarPointer());
|
|
|
|
HPDF_Image pdfImage =
|
|
HPDF_LoadRawImageFromMem(this->Impl->Document, buf, dims[0], dims[1], HPDF_CS_DEVICE_RGB, 8);
|
|
|
|
HPDF_Page_DrawImage(this->Impl->Page, pdfImage, pos[0], pos[1], pos[2], pos[3]);
|
|
|
|
rgb->UnRegister(this);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::SetColor4(unsigned char[4])
|
|
{
|
|
// This is how the OpenGL2 impl handles this...
|
|
vtkErrorMacro("color cannot be set this way.");
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::SetTexture(vtkImageData* image, int properties)
|
|
{
|
|
this->Brush->SetTexture(image);
|
|
this->Brush->SetTextureProperties(properties);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::SetPointSize(float size)
|
|
{
|
|
this->Pen->SetWidth(size);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::SetLineWidth(float width)
|
|
{
|
|
this->Pen->SetWidth(width);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawPolyData(
|
|
float p[2], float scale, vtkPolyData* polyData, vtkUnsignedCharArray* colors, int scalarMode)
|
|
{
|
|
// Do nothing if the supported cell types do not exist in the dataset:
|
|
vtkNew<vtkCellTypes> types;
|
|
polyData->GetCellTypes(types);
|
|
if (!types->IsType(VTK_LINE) && !types->IsType(VTK_POLY_LINE) && !types->IsType(VTK_TRIANGLE) &&
|
|
!types->IsType(VTK_QUAD) && !types->IsType(VTK_POLYGON))
|
|
{
|
|
return;
|
|
}
|
|
|
|
double bounds[6];
|
|
polyData->GetBounds(bounds);
|
|
|
|
float radius = this->Impl->computeWorldRadius(
|
|
bounds[0], bounds[2], bounds[1], bounds[3], this->Matrix->GetMatrix(), this->Pen->GetWidth());
|
|
|
|
// Adjust bounds for transform, account for pen width:
|
|
bounds[0] = (bounds[0] + p[0]) * scale - radius;
|
|
bounds[1] = (bounds[1] + p[0]) * scale + radius;
|
|
bounds[2] = (bounds[2] + p[1]) * scale - radius;
|
|
bounds[3] = (bounds[3] + p[1]) * scale + radius;
|
|
|
|
// Accumulate all triangles in a shading object:
|
|
HPDF_Shading shading =
|
|
HPDF_Shading_New(this->Impl->Document, HPDF_SHADING_FREE_FORM_TRIANGLE_MESH, HPDF_CS_DEVICE_RGB,
|
|
bounds[0], bounds[1], bounds[2], bounds[3]);
|
|
|
|
// Temporary buffers:
|
|
std::vector<float> verts;
|
|
std::vector<unsigned char> vertColors;
|
|
|
|
vtkCellIterator* cell = polyData->NewCellIterator();
|
|
cell->InitTraversal();
|
|
for (; !cell->IsDoneWithTraversal(); cell->GoToNextCell())
|
|
{
|
|
// To match the original implementation on the OpenGL2 backend, we only
|
|
// handle polygons and lines:
|
|
int cellType = cell->GetCellType();
|
|
switch (cellType)
|
|
{
|
|
case VTK_LINE:
|
|
case VTK_POLY_LINE:
|
|
case VTK_TRIANGLE:
|
|
case VTK_QUAD:
|
|
case VTK_POLYGON:
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
// Allocate temporary arrays:
|
|
vtkIdType numPoints = cell->GetNumberOfPoints();
|
|
if (numPoints == 0)
|
|
{
|
|
continue;
|
|
}
|
|
verts.resize(static_cast<size_t>(numPoints) * 2);
|
|
vertColors.resize(static_cast<size_t>(numPoints) * 4);
|
|
|
|
vtkIdType cellId = cell->GetCellId();
|
|
vtkIdList* pointIds = cell->GetPointIds();
|
|
vtkPoints* points = cell->GetPoints();
|
|
|
|
for (vtkIdType i = 0; i < numPoints; ++i)
|
|
{
|
|
const size_t vertsIdx = 2 * static_cast<size_t>(i);
|
|
const size_t colorIdx = 4 * static_cast<size_t>(i);
|
|
|
|
const double* point = points->GetPoint(i);
|
|
verts[vertsIdx] = (static_cast<float>(point[0]) + p[0]) * scale;
|
|
verts[vertsIdx + 1] = (static_cast<float>(point[1]) + p[1]) * scale;
|
|
|
|
if (scalarMode == VTK_SCALAR_MODE_USE_POINT_DATA)
|
|
{
|
|
colors->GetTypedTuple(pointIds->GetId(i), vertColors.data() + colorIdx);
|
|
}
|
|
else
|
|
{
|
|
colors->GetTypedTuple(cellId, vertColors.data() + colorIdx);
|
|
}
|
|
}
|
|
|
|
if (cellType == VTK_LINE || cellType == VTK_POLY_LINE)
|
|
{
|
|
PolyLineToShading(verts.data(), numPoints, vertColors.data(), 4, radius, shading);
|
|
}
|
|
else
|
|
{
|
|
PolygonToShading(verts.data(), numPoints, vertColors.data(), 4, shading);
|
|
}
|
|
}
|
|
cell->Delete();
|
|
|
|
HPDF_Page_SetShading(this->Impl->Page, shading);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::SetLineType(int type)
|
|
{
|
|
this->Pen->SetLineType(type);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::SetMatrix(vtkMatrix3x3* mat3)
|
|
{
|
|
double mat4[16];
|
|
vtkPDFContextDevice2D::Matrix3ToMatrix4(mat3, mat4);
|
|
this->Matrix->SetMatrix(mat4);
|
|
this->ApplyTransform();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::GetMatrix(vtkMatrix3x3* mat3)
|
|
{
|
|
vtkPDFContextDevice2D::Matrix4ToMatrix3(this->Matrix->GetMatrix()->GetData(), mat3);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::MultiplyMatrix(vtkMatrix3x3* mat3)
|
|
{
|
|
double mat4[16];
|
|
vtkPDFContextDevice2D::Matrix3ToMatrix4(mat3, mat4);
|
|
this->Matrix->Concatenate(mat4);
|
|
this->ApplyTransform();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::PushMatrix()
|
|
{
|
|
this->Matrix->Push();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::PopMatrix()
|
|
{
|
|
this->Matrix->Pop();
|
|
this->ApplyTransform();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::SetClipping(int* x)
|
|
{
|
|
this->ClipBox[0] = static_cast<HPDF_REAL>(x[0]);
|
|
this->ClipBox[1] = static_cast<HPDF_REAL>(x[1]);
|
|
this->ClipBox[2] = static_cast<HPDF_REAL>(x[2]);
|
|
this->ClipBox[3] = static_cast<HPDF_REAL>(x[3]);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::EnableClipping(bool enable)
|
|
{
|
|
if (enable)
|
|
{
|
|
this->PushGraphicsState();
|
|
HPDF_Page_Rectangle(
|
|
this->Impl->Page, this->ClipBox[0], this->ClipBox[1], this->ClipBox[2], this->ClipBox[3]);
|
|
HPDF_Page_Clip(this->Impl->Page);
|
|
// Prevent the clip path from being drawn:
|
|
HPDF_Page_EndPath(this->Impl->Page);
|
|
}
|
|
else
|
|
{
|
|
this->PopGraphicsState();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
vtkPDFContextDevice2D::vtkPDFContextDevice2D()
|
|
: Impl(new Details)
|
|
, Renderer(nullptr)
|
|
, IsInTexturedFill(false)
|
|
{
|
|
std::fill(this->ClipBox, this->ClipBox + 4, 0.f);
|
|
std::fill(this->TextureBounds, this->TextureBounds + 4, 0.f);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
vtkPDFContextDevice2D::~vtkPDFContextDevice2D()
|
|
{
|
|
this->SetRenderer(nullptr);
|
|
delete this->Impl;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::PushGraphicsState()
|
|
{
|
|
HPDF_Page_GSave(this->Impl->Page);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::PopGraphicsState()
|
|
{
|
|
HPDF_Page_GRestore(this->Impl->Page);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ApplyPenState()
|
|
{
|
|
const vtkVector2f width = this->GetUnscaledPenWidth();
|
|
this->ApplyStrokeColor(this->Pen->GetColorObject().GetData(), 4);
|
|
this->ApplyLineWidth(std::max(width[0], width[1]));
|
|
this->ApplyLineType(this->Pen->GetLineType());
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ApplyStrokeColor(unsigned char* color, int numComps)
|
|
{
|
|
HPDF_Page_SetRGBStroke(this->Impl->Page, static_cast<HPDF_REAL>(color[0] / 255.0),
|
|
static_cast<HPDF_REAL>(color[1] / 255.0), static_cast<HPDF_REAL>(color[2] / 255.0));
|
|
|
|
unsigned char alpha = numComps > 3 ? color[3] : 255;
|
|
auto gstateIter = this->Impl->AlphaGStateMap.find(alpha);
|
|
if (gstateIter == this->Impl->AlphaGStateMap.end())
|
|
{
|
|
HPDF_ExtGState gstate = HPDF_CreateExtGState(this->Impl->Document);
|
|
HPDF_ExtGState_SetAlphaFill(gstate, alpha / 255.f);
|
|
auto tmp = this->Impl->AlphaGStateMap.insert(std::make_pair(alpha, gstate));
|
|
gstateIter = tmp.first;
|
|
}
|
|
HPDF_ExtGState alphaState = gstateIter->second;
|
|
HPDF_Page_SetExtGState(this->Impl->Page, alphaState);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ApplyLineWidth(float width)
|
|
{
|
|
HPDF_Page_SetLineWidth(this->Impl->Page, width);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ApplyLineType(int type)
|
|
{
|
|
// These match the OpenGL2 implementation:
|
|
static const HPDF_UINT16 noPen[] = { 0, 10 };
|
|
static const HPDF_UINT noPenLen = 2;
|
|
|
|
static const HPDF_UINT16 dash[] = { 8 };
|
|
static const HPDF_UINT dashLen = 1;
|
|
|
|
static const HPDF_UINT16 dot[] = { 1, 7 };
|
|
static const HPDF_UINT16 denseDot[] = { 1, 3 };
|
|
static const HPDF_UINT dotLen = 2;
|
|
|
|
static const HPDF_UINT16 dashDot[] = { 4, 6, 2, 4 };
|
|
static const HPDF_UINT dashDotLen = 4;
|
|
|
|
// This is dash-dot-dash, but eh. It matches the OpenGL2 0x1C47 pattern.
|
|
static const HPDF_UINT16 dashDotDot[] = { 3, 3, 1, 3, 3, 3 };
|
|
static const HPDF_UINT dashDotDotLen = 6;
|
|
|
|
switch (type)
|
|
{
|
|
default:
|
|
vtkErrorMacro("Unknown line type: " << type);
|
|
VTK_FALLTHROUGH;
|
|
|
|
case vtkPen::NO_PEN:
|
|
HPDF_Page_SetDash(this->Impl->Page, noPen, noPenLen, 0);
|
|
break;
|
|
|
|
case vtkPen::SOLID_LINE:
|
|
HPDF_Page_SetDash(this->Impl->Page, nullptr, 0, 0);
|
|
break;
|
|
|
|
case vtkPen::DASH_LINE:
|
|
HPDF_Page_SetDash(this->Impl->Page, dash, dashLen, 0);
|
|
break;
|
|
|
|
case vtkPen::DOT_LINE:
|
|
HPDF_Page_SetDash(this->Impl->Page, dot, dotLen, 0);
|
|
break;
|
|
|
|
case vtkPen::DASH_DOT_LINE:
|
|
HPDF_Page_SetDash(this->Impl->Page, dashDot, dashDotLen, 0);
|
|
break;
|
|
|
|
case vtkPen::DASH_DOT_DOT_LINE:
|
|
HPDF_Page_SetDash(this->Impl->Page, dashDotDot, dashDotDotLen, 0);
|
|
break;
|
|
|
|
case vtkPen::DENSE_DOT_LINE:
|
|
HPDF_Page_SetDash(this->Impl->Page, denseDot, dotLen, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::Stroke()
|
|
{
|
|
HPDF_Page_Stroke(this->Impl->Page);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ApplyPenStateAsFill()
|
|
{
|
|
this->ApplyFillColor(this->Pen->GetColorObject().GetData(), 4);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ApplyBrushState()
|
|
{
|
|
this->ApplyFillColor(this->Brush->GetColorObject().GetData(), 4);
|
|
|
|
if (this->Brush->GetTexture())
|
|
{
|
|
this->BeginClipPathForTexture();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ApplyTextPropertyState()
|
|
{
|
|
unsigned char rgba[4] = { static_cast<unsigned char>(this->TextProp->GetColor()[0] * 255.),
|
|
static_cast<unsigned char>(this->TextProp->GetColor()[1] * 255.),
|
|
static_cast<unsigned char>(this->TextProp->GetColor()[2] * 255.),
|
|
static_cast<unsigned char>(this->TextProp->GetOpacity() * 255.) };
|
|
|
|
this->ApplyFillColor(rgba, 4);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ApplyFillColor(unsigned char* color, int numComps)
|
|
{
|
|
HPDF_Page_SetRGBFill(this->Impl->Page, static_cast<HPDF_REAL>(color[0] / 255.0),
|
|
static_cast<HPDF_REAL>(color[1] / 255.0), static_cast<HPDF_REAL>(color[2] / 255.0));
|
|
if (numComps > 3)
|
|
{
|
|
this->ApplyFillAlpha(color[3]);
|
|
}
|
|
else
|
|
{
|
|
this->ApplyFillAlpha(255);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ApplyFillAlpha(unsigned char alpha)
|
|
{
|
|
auto gstateIter = this->Impl->AlphaGStateMap.find(alpha);
|
|
if (gstateIter == this->Impl->AlphaGStateMap.end())
|
|
{
|
|
HPDF_ExtGState gstate = HPDF_CreateExtGState(this->Impl->Document);
|
|
HPDF_ExtGState_SetAlphaFill(gstate, alpha / 255.f);
|
|
auto tmp = this->Impl->AlphaGStateMap.insert(std::make_pair(alpha, gstate));
|
|
gstateIter = tmp.first;
|
|
}
|
|
HPDF_ExtGState alphaState = gstateIter->second;
|
|
HPDF_Page_SetExtGState(this->Impl->Page, alphaState);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::Fill(bool stroke)
|
|
{
|
|
if (this->IsInTexturedFill)
|
|
{
|
|
this->FillTexture();
|
|
return;
|
|
}
|
|
|
|
if (stroke)
|
|
{
|
|
HPDF_Page_FillStroke(this->Impl->Page);
|
|
}
|
|
else
|
|
{
|
|
HPDF_Page_Fill(this->Impl->Page);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::FillEvenOdd(bool stroke)
|
|
{
|
|
if (this->IsInTexturedFill)
|
|
{
|
|
this->FillTexture();
|
|
return;
|
|
}
|
|
|
|
if (stroke)
|
|
{
|
|
HPDF_Page_EofillStroke(this->Impl->Page);
|
|
}
|
|
else
|
|
{
|
|
HPDF_Page_Eofill(this->Impl->Page);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::BeginClipPathForTexture()
|
|
{
|
|
assert(!this->IsInTexturedFill);
|
|
this->IsInTexturedFill = true;
|
|
this->TextureBounds[0] = this->TextureBounds[2] = VTK_FLOAT_MAX;
|
|
this->TextureBounds[1] = this->TextureBounds[3] = VTK_FLOAT_MIN;
|
|
this->PushGraphicsState(); // so we can pop the clip path
|
|
this->ApplyFillAlpha(255); // Match the OpenGL implementation
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::RegisterTexturePoints(float* data, int numPoints)
|
|
{
|
|
if (this->IsInTexturedFill)
|
|
{
|
|
for (int i = 0; i < numPoints; ++i)
|
|
{
|
|
this->TextureBounds[0] = std::min(this->TextureBounds[0], data[2 * i]);
|
|
this->TextureBounds[1] = std::max(this->TextureBounds[1], data[2 * i]);
|
|
this->TextureBounds[2] = std::min(this->TextureBounds[2], data[2 * i + 1]);
|
|
this->TextureBounds[3] = std::max(this->TextureBounds[3], data[2 * i + 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::FillTexture()
|
|
{
|
|
assert(this->IsInTexturedFill);
|
|
|
|
this->IsInTexturedFill = false;
|
|
|
|
if (this->TextureBounds[0] == VTK_FLOAT_MAX || this->TextureBounds[1] == VTK_FLOAT_MIN ||
|
|
this->TextureBounds[2] == VTK_FLOAT_MAX || this->TextureBounds[3] == VTK_FLOAT_MIN)
|
|
{ // No geometry to texture:
|
|
this->PopGraphicsState();
|
|
return;
|
|
}
|
|
|
|
// Use current path for clipping
|
|
HPDF_Page_Clip(this->Impl->Page);
|
|
HPDF_Page_EndPath(this->Impl->Page);
|
|
|
|
// Prepare texture image
|
|
vtkImageData* image = this->Brush->GetTexture();
|
|
assert(image);
|
|
|
|
vtkImageData* rgb = this->PrepareImageData(image);
|
|
if (!rgb)
|
|
{
|
|
return;
|
|
}
|
|
|
|
assert(rgb->GetScalarType() == VTK_UNSIGNED_CHAR);
|
|
assert(rgb->GetNumberOfScalarComponents() == 3);
|
|
|
|
int dims[3];
|
|
rgb->GetDimensions(dims);
|
|
const HPDF_BYTE* buf = static_cast<HPDF_BYTE*>(rgb->GetScalarPointer());
|
|
|
|
HPDF_Image pdfImage =
|
|
HPDF_LoadRawImageFromMem(this->Impl->Document, buf, dims[0], dims[1], HPDF_CS_DEVICE_RGB, 8);
|
|
|
|
const bool isTiled = ((this->Brush->GetTextureProperties() & vtkBrush::Repeat) != 0);
|
|
|
|
// tile across TextureBounds if repeating
|
|
if (isTiled)
|
|
{
|
|
float x = this->TextureBounds[0];
|
|
while (x < this->TextureBounds[1])
|
|
{
|
|
float y = this->TextureBounds[2];
|
|
while (y < this->TextureBounds[3])
|
|
{
|
|
HPDF_Page_DrawImage(this->Impl->Page, pdfImage, x, y, dims[0], dims[1]);
|
|
y += dims[1];
|
|
}
|
|
x += dims[0];
|
|
}
|
|
}
|
|
else
|
|
{ // stretch across texture bounds if stretched
|
|
HPDF_Page_DrawImage(this->Impl->Page, pdfImage, this->TextureBounds[0], this->TextureBounds[2],
|
|
this->TextureBounds[1] - this->TextureBounds[0],
|
|
this->TextureBounds[3] - this->TextureBounds[2]);
|
|
}
|
|
|
|
rgb->UnRegister(this);
|
|
this->PopGraphicsState(); // unset clip path
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
vtkImageData* vtkPDFContextDevice2D::PrepareImageData(vtkImageData* in)
|
|
{
|
|
int numComps = in->GetNumberOfScalarComponents();
|
|
|
|
// We'll only handle RGB / RGBA:
|
|
if (numComps != 3 && numComps != 4)
|
|
{
|
|
vtkWarningMacro("Images with " << numComps << " components not supported.");
|
|
return nullptr;
|
|
}
|
|
|
|
// Need to convert scalar type?
|
|
if (in->GetScalarType() != VTK_UNSIGNED_CHAR)
|
|
{
|
|
vtkNew<vtkImageCast> cast;
|
|
cast->SetInputData(in);
|
|
cast->SetOutputScalarTypeToUnsignedChar();
|
|
cast->Update();
|
|
in = cast->GetOutput();
|
|
in->Register(this);
|
|
}
|
|
else
|
|
{
|
|
in->Register(this); // Keep refcounts consistent
|
|
}
|
|
|
|
if (in->GetNumberOfScalarComponents() == 4)
|
|
{ // If RGBA, blend into brush color -- Haru doesn't support RGBA.
|
|
|
|
vtkNew<vtkImageData> background;
|
|
{ // Fill the background image with brush color, saturate alpha
|
|
std::array<unsigned char, 4> bgColor;
|
|
this->Brush->GetColor(bgColor.data());
|
|
bgColor[3] = 255; // Saturate alpha
|
|
background->SetExtent(in->GetExtent());
|
|
background->AllocateScalars(VTK_UNSIGNED_CHAR, 4);
|
|
auto* scalars = vtkUnsignedCharArray::SafeDownCast(background->GetPointData()->GetScalars());
|
|
for (int comp = 0; comp < 4; ++comp)
|
|
{
|
|
scalars->FillComponent(comp, bgColor[comp]);
|
|
}
|
|
}
|
|
|
|
// Blend the input image over the background color:
|
|
vtkNew<vtkImageBlend> blender;
|
|
blender->AddInputData(0, background);
|
|
blender->AddInputData(0, in);
|
|
in->UnRegister(this); // Remove ref++ from above
|
|
blender->SetBlendModeToNormal();
|
|
|
|
vtkNew<vtkImageExtractComponents> extract;
|
|
extract->SetInputConnection(blender->GetOutputPort(0));
|
|
extract->SetComponents(0, 1, 2);
|
|
extract->Update();
|
|
in = extract->GetOutput();
|
|
in->Register(this);
|
|
}
|
|
|
|
// Finally, flip the image (Haru expects them this way)
|
|
vtkNew<vtkImageFlip> flip;
|
|
flip->SetInputData(in);
|
|
in->UnRegister(this);
|
|
flip->SetFilteredAxis(1); // y axis
|
|
flip->Update();
|
|
in = flip->GetOutput();
|
|
in->Register(this);
|
|
|
|
return in;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawEllipticArcSegments(
|
|
float x, float y, float rX, float rY, float startAngle, float stopAngle, bool startPath)
|
|
{
|
|
// Adapted from OpenGL implementation:
|
|
int numSegments = this->GetNumberOfArcIterations(rX, rY, startAngle, stopAngle);
|
|
|
|
// step in radians:
|
|
float step =
|
|
vtkMath::RadiansFromDegrees(stopAngle - startAngle) / static_cast<float>(numSegments);
|
|
|
|
float rstart = vtkMath::RadiansFromDegrees(startAngle);
|
|
|
|
if (startPath)
|
|
{
|
|
HPDF_Page_MoveTo(this->Impl->Page, rX * std::cos(rstart) + x, rY * std::sin(rstart) + y);
|
|
}
|
|
else
|
|
{
|
|
HPDF_Page_LineTo(this->Impl->Page, rX * std::cos(rstart) + x, rY * std::sin(rstart) + y);
|
|
}
|
|
|
|
for (int i = 1; i <= numSegments; ++i)
|
|
{
|
|
float angle = rstart + i * step;
|
|
HPDF_Page_LineTo(this->Impl->Page, rX * std::cos(angle) + x, rY * std::sin(angle) + y);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
int vtkPDFContextDevice2D::GetNumberOfArcIterations(
|
|
float rX, float rY, float startAngle, float stopAngle)
|
|
{
|
|
// Copied from original OpenGL implementation:
|
|
assert("pre: positive_rX" && rX >= 0.0f);
|
|
assert("pre: positive_rY" && rY >= 0.0f);
|
|
assert("pre: not_both_null" && (rX > 0.0 || rY > 0.0));
|
|
|
|
// 1.0: pixel precision. 0.5 (subpixel precision, useful with multisampling)
|
|
double error = 4.0; // experience shows 4.0 is visually enough.
|
|
|
|
// The tessellation is the most visible on the biggest radius.
|
|
double maxRadius;
|
|
if (rX >= rY)
|
|
{
|
|
maxRadius = rX;
|
|
}
|
|
else
|
|
{
|
|
maxRadius = rY;
|
|
}
|
|
|
|
if (error > maxRadius)
|
|
{
|
|
// to make sure the argument of asin() is in a valid range.
|
|
error = maxRadius;
|
|
}
|
|
|
|
// Angle of a sector so that its chord is `error' pixels.
|
|
// This is will be our maximum angle step.
|
|
double maxStep = 2.0 * asin(error / (2.0 * maxRadius));
|
|
|
|
// ceil because we want to make sure we don't underestimate the number of
|
|
// iterations by 1.
|
|
return static_cast<int>(
|
|
ceil(vtkMath::RadiansFromDegrees(std::fabs(stopAngle - startAngle)) / maxStep));
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawCrossMarkers(
|
|
bool highlight, float* points, int n, unsigned char* colors, int nc_comps)
|
|
{
|
|
const float markerSize = this->Pen->GetWidth();
|
|
const float delta = markerSize * 0.5f;
|
|
|
|
this->ApplyLineWidth(highlight ? 1.5f : 0.5f);
|
|
this->ApplyLineType(vtkPen::SOLID_LINE);
|
|
if (!colors)
|
|
{
|
|
this->ApplyStrokeColor(this->Pen->GetColorObject().GetData(), 4);
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
float* p = points + i * 2;
|
|
if (colors)
|
|
{
|
|
if (i != 0)
|
|
{
|
|
this->Stroke();
|
|
}
|
|
this->ApplyStrokeColor(colors + i * nc_comps, nc_comps);
|
|
}
|
|
HPDF_Page_MoveTo(this->Impl->Page, p[0] + delta, p[1] + delta);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[0] - delta, p[1] - delta);
|
|
HPDF_Page_MoveTo(this->Impl->Page, p[0] + delta, p[1] - delta);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[0] - delta, p[1] + delta);
|
|
}
|
|
this->Stroke();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawPlusMarkers(
|
|
bool highlight, float* points, int n, unsigned char* colors, int nc_comps)
|
|
{
|
|
const float markerSize = this->Pen->GetWidth();
|
|
const float delta = markerSize * 0.5f;
|
|
|
|
this->ApplyLineWidth(highlight ? 1.5f : 0.5f);
|
|
this->ApplyLineType(vtkPen::SOLID_LINE);
|
|
if (!colors)
|
|
{
|
|
this->ApplyStrokeColor(this->Pen->GetColorObject().GetData(), 4);
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
float* p = points + i * 2;
|
|
if (colors)
|
|
{
|
|
if (i != 0)
|
|
{
|
|
this->Stroke();
|
|
}
|
|
this->ApplyStrokeColor(colors + i * nc_comps, nc_comps);
|
|
}
|
|
HPDF_Page_MoveTo(this->Impl->Page, p[0], p[1] + delta);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[0], p[1] - delta);
|
|
HPDF_Page_MoveTo(this->Impl->Page, p[0] + delta, p[1]);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[0] - delta, p[1]);
|
|
}
|
|
this->Stroke();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawSquareMarkers(
|
|
bool, float* points, int n, unsigned char* colors, int nc_comps)
|
|
{
|
|
const float markerSize = this->Pen->GetWidth();
|
|
const float delta = markerSize * 0.5f;
|
|
|
|
if (!colors)
|
|
{
|
|
this->ApplyFillColor(this->Pen->GetColorObject().GetData(), 4);
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
float* p = points + i * 2;
|
|
if (colors)
|
|
{
|
|
if (i != 0)
|
|
{
|
|
this->Fill();
|
|
}
|
|
this->ApplyFillColor(colors + i * nc_comps, nc_comps);
|
|
}
|
|
HPDF_Page_Rectangle(this->Impl->Page, p[0] - delta, p[1] - delta, markerSize, markerSize);
|
|
}
|
|
this->Fill();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawCircleMarkers(
|
|
bool, float* points, int n, unsigned char* colors, int nc_comps)
|
|
{
|
|
const float markerSize = this->Pen->GetWidth();
|
|
const float radius = markerSize * 0.5f;
|
|
|
|
if (!colors)
|
|
{
|
|
this->ApplyFillColor(this->Pen->GetColorObject().GetData(), 4);
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
float* p = points + i * 2;
|
|
if (colors)
|
|
{
|
|
if (i != 0)
|
|
{
|
|
this->Fill();
|
|
}
|
|
this->ApplyFillColor(colors + i * nc_comps, nc_comps);
|
|
}
|
|
HPDF_Page_Ellipse(this->Impl->Page, p[0], p[1], radius, radius);
|
|
}
|
|
this->Fill();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawDiamondMarkers(
|
|
bool, float* points, int n, unsigned char* colors, int nc_comps)
|
|
{
|
|
const float markerSize = this->Pen->GetWidth();
|
|
const float radius = markerSize * 0.5f;
|
|
|
|
if (!colors)
|
|
{
|
|
this->ApplyFillColor(this->Pen->GetColorObject().GetData(), 4);
|
|
}
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
float* p = points + i * 2;
|
|
if (colors)
|
|
{
|
|
if (i != 0)
|
|
{
|
|
this->Fill();
|
|
}
|
|
this->ApplyFillColor(colors + i * nc_comps, nc_comps);
|
|
}
|
|
HPDF_Page_MoveTo(this->Impl->Page, p[0] + radius, p[1]);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[0], p[1] + radius);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[0] - radius, p[1]);
|
|
HPDF_Page_LineTo(this->Impl->Page, p[0], p[1] - radius);
|
|
HPDF_Page_ClosePath(this->Impl->Page);
|
|
}
|
|
this->Fill();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::DrawPath(vtkPath* path, float originX, float originY)
|
|
{
|
|
// The text renderer always uses floats to generate paths, so we'll optimize
|
|
// a bit here:
|
|
vtkFloatArray* points = vtkArrayDownCast<vtkFloatArray>(path->GetPoints()->GetData());
|
|
vtkIntArray* codes = path->GetCodes();
|
|
|
|
if (!points)
|
|
{
|
|
vtkErrorMacro("This method expects the path point precision to be floats.");
|
|
return;
|
|
}
|
|
|
|
vtkIdType numTuples = points->GetNumberOfTuples();
|
|
if (numTuples != codes->GetNumberOfTuples() || codes->GetNumberOfComponents() != 1 ||
|
|
points->GetNumberOfComponents() != 3)
|
|
{
|
|
vtkErrorMacro("Invalid path data.");
|
|
return;
|
|
}
|
|
|
|
if (numTuples == 0)
|
|
{ // Nothing to do.
|
|
return;
|
|
}
|
|
|
|
typedef vtkPath::ControlPointType CodeEnum;
|
|
typedef vtkIntArray::ValueType CodeType;
|
|
CodeType* code = codes->GetPointer(0);
|
|
CodeType* codeEnd = code + numTuples;
|
|
|
|
typedef vtkFloatArray::ValueType PointType;
|
|
PointType* point = points->GetPointer(0);
|
|
|
|
// These are only used in an assertion, ifdef silences warning on non-debug
|
|
// builds
|
|
#ifndef NDEBUG
|
|
PointType* pointBegin = point;
|
|
CodeType* codeBegin = code;
|
|
#endif
|
|
|
|
HPDF_Page page = this->Impl->Page;
|
|
|
|
// Translate to origin:
|
|
HPDF_Page_Concat(page, 1.f, 0.f, 0.f, 1.f, originX, originY);
|
|
|
|
while (code < codeEnd)
|
|
{
|
|
assert("Sanity check" && (code - codeBegin) * 3 == point - pointBegin);
|
|
|
|
switch (static_cast<CodeEnum>(*code))
|
|
{
|
|
case vtkPath::MOVE_TO:
|
|
HPDF_Page_MoveTo(page, point[0], point[1]);
|
|
point += 3;
|
|
++code;
|
|
break;
|
|
|
|
case vtkPath::LINE_TO:
|
|
HPDF_Page_LineTo(page, point[0], point[1]);
|
|
point += 3;
|
|
++code;
|
|
break;
|
|
|
|
case vtkPath::CONIC_CURVE:
|
|
HPDF_Page_CurveTo3(page, point[0], point[1], point[3], point[4]);
|
|
point += 6;
|
|
assert(CodeEnum(code[1]) == vtkPath::CONIC_CURVE);
|
|
code += 2;
|
|
break;
|
|
|
|
case vtkPath::CUBIC_CURVE:
|
|
HPDF_Page_CurveTo(page, point[0], point[1], point[3], point[4], point[6], point[7]);
|
|
point += 9;
|
|
assert(CodeEnum(code[1]) == vtkPath::CUBIC_CURVE);
|
|
assert(CodeEnum(code[2]) == vtkPath::CUBIC_CURVE);
|
|
code += 3;
|
|
break;
|
|
|
|
default:
|
|
throw std::runtime_error("Unknown control code.");
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::ApplyTransform()
|
|
{
|
|
// The HPDF API for transform management is lacking. There's no clear way
|
|
// to simply *set* the transform, we can only concatenate multiple transforms
|
|
// together. Nor is there a way to push/pop a matrix stack. So we'll just
|
|
// invert the current transform to unapply it before applying the new one.
|
|
HPDF_TransMatrix oldTrans = HPDF_Page_GetTransMatrix(this->Impl->Page);
|
|
double oldInvTransMat3[9];
|
|
vtkPDFContextDevice2D::HPDFTransformToMatrix3(
|
|
oldTrans.a, oldTrans.b, oldTrans.c, oldTrans.d, oldTrans.x, oldTrans.y, oldInvTransMat3);
|
|
vtkMatrix3x3::Invert(oldInvTransMat3, oldInvTransMat3);
|
|
|
|
// Multiply the inverse current transform with the new one:
|
|
double newTransMat3[9];
|
|
double* mat = this->Matrix->GetMatrix()->GetData();
|
|
vtkPDFContextDevice2D::Matrix4ToMatrix3(mat, newTransMat3);
|
|
|
|
vtkMatrix3x3::Multiply3x3(oldInvTransMat3, newTransMat3, newTransMat3);
|
|
|
|
// Test if the new transform is identity, within tolerance:
|
|
bool isIdentity = true;
|
|
const double tol = 1e-6;
|
|
for (size_t i = 0; i < 3 && isIdentity; ++i)
|
|
{
|
|
for (size_t j = 0; j < 3 && isIdentity; ++j)
|
|
{
|
|
const double val = newTransMat3[i * 3 + j];
|
|
const double exp = (i == j) ? 1. : 0.;
|
|
if (std::fabs(val - exp) > tol)
|
|
{
|
|
isIdentity = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do nothing if the transform would have no effect.
|
|
if (isIdentity)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Otherwise, write the new transform out.
|
|
float hpdfMat[6];
|
|
vtkPDFContextDevice2D::Matrix3ToHPDFTransform(newTransMat3, hpdfMat);
|
|
HPDF_Page_Concat(
|
|
this->Impl->Page, hpdfMat[0], hpdfMat[1], hpdfMat[2], hpdfMat[3], hpdfMat[4], hpdfMat[5]);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
vtkVector2f vtkPDFContextDevice2D::GetUnscaledPenWidth()
|
|
{
|
|
float width = this->GetPen()->GetWidth();
|
|
vtkNew<vtkMatrix3x3> mat;
|
|
this->GetMatrix(mat);
|
|
double sx;
|
|
double sy;
|
|
std::tie(sx, sy) = GetScaleFactor(mat);
|
|
|
|
return vtkVector2f{ static_cast<float>(width / sx), static_cast<float>(width / sy) };
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::Matrix3ToMatrix4(vtkMatrix3x3* mat3, double mat4[16])
|
|
{
|
|
const double* mat3Data = mat3->GetData();
|
|
mat4[0] = mat3Data[0];
|
|
mat4[1] = mat3Data[1];
|
|
mat4[2] = 0.;
|
|
mat4[3] = mat3Data[2];
|
|
mat4[4] = mat3Data[3];
|
|
mat4[5] = mat3Data[4];
|
|
mat4[6] = 0.;
|
|
mat4[7] = mat3Data[5];
|
|
mat4[8] = 0.;
|
|
mat4[9] = 0.;
|
|
mat4[10] = 1.;
|
|
mat4[11] = 0.;
|
|
mat4[12] = 0.;
|
|
mat4[13] = 0.;
|
|
mat4[14] = 0.;
|
|
mat4[15] = 1.;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::Matrix4ToMatrix3(double mat4[16], vtkMatrix3x3* mat3)
|
|
{
|
|
double* mat3Data = mat3->GetData();
|
|
vtkPDFContextDevice2D::Matrix4ToMatrix3(mat4, mat3Data);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::Matrix4ToMatrix3(double mat4[16], double mat3[9])
|
|
{
|
|
mat3[0] = mat4[0];
|
|
mat3[1] = mat4[1];
|
|
mat3[2] = mat4[3];
|
|
mat3[3] = mat4[4];
|
|
mat3[4] = mat4[5];
|
|
mat3[5] = mat4[7];
|
|
mat3[6] = 0.;
|
|
mat3[7] = 0.;
|
|
mat3[8] = 1.;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::Matrix4ToHPDFTransform(const double mat4[16], float hpdfMat[6])
|
|
{
|
|
hpdfMat[0] = static_cast<float>(mat4[0]);
|
|
hpdfMat[1] = static_cast<float>(mat4[4]);
|
|
hpdfMat[2] = static_cast<float>(mat4[1]);
|
|
hpdfMat[3] = static_cast<float>(mat4[5]);
|
|
hpdfMat[4] = static_cast<float>(mat4[3]);
|
|
hpdfMat[5] = static_cast<float>(mat4[7]);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::Matrix3ToHPDFTransform(const double mat3[9], float hpdfMat[6])
|
|
{
|
|
hpdfMat[0] = static_cast<float>(mat3[0]);
|
|
hpdfMat[1] = static_cast<float>(mat3[3]);
|
|
hpdfMat[2] = static_cast<float>(mat3[1]);
|
|
hpdfMat[3] = static_cast<float>(mat3[4]);
|
|
hpdfMat[4] = static_cast<float>(mat3[2]);
|
|
hpdfMat[5] = static_cast<float>(mat3[5]);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void vtkPDFContextDevice2D::HPDFTransformToMatrix3(
|
|
float a, float b, float c, float d, float x, float y, double mat3[9])
|
|
{
|
|
mat3[0] = static_cast<double>(a);
|
|
mat3[1] = static_cast<double>(c);
|
|
mat3[2] = static_cast<double>(x);
|
|
mat3[3] = static_cast<double>(b);
|
|
mat3[4] = static_cast<double>(d);
|
|
mat3[5] = static_cast<double>(y);
|
|
mat3[6] = 0.;
|
|
mat3[7] = 0.;
|
|
mat3[8] = 1.;
|
|
}
|