Commit 125149a5 authored by David C. Lonie's avatar David C. Lonie
Browse files

Improve aligned and rotated text.

This patch fixes aligned rotated text by anchoring the rendered image
or path to the location described by the text property's justification
properties.

As a result, alignment options are no longer ignored by the vtkTextActor3D.

Change-Id: I6640b6dc8d579b408ac85670b732b7c94763a5d7
parent 6c7cdbf9
......@@ -722,14 +722,10 @@ void vtkGL2PSExporter::DrawTextActor3D(vtkTextActor3D *textAct,
// Get path
const char *string = textAct->GetInput();
vtkNew<vtkPath> path;
vtkNew<vtkTextProperty> tprop;
tprop->ShallowCopy(textAct->GetTextProperty());
tprop->SetJustificationToLeft(); // Ignored by textactor3d
tprop->SetVerticalJustificationToBottom(); // Ignored by textactor3d
vtkTextRenderer *tren = vtkTextRenderer::GetInstance();
if (tren)
{
tren->StringToPath(tprop.GetPointer(), vtkStdString(string),
tren->StringToPath(textAct->GetTextProperty(), vtkStdString(string),
path.GetPointer());
}
else
......@@ -745,12 +741,12 @@ void vtkGL2PSExporter::DrawTextActor3D(vtkTextActor3D *textAct,
double rasterPos[3] = {(actorBounds[1] + actorBounds[0]) * 0.5,
(actorBounds[3] + actorBounds[2]) * 0.5,
(actorBounds[5] + actorBounds[4]) * 0.5};
double *dcolor = tprop->GetColor();
double *dcolor = textAct->GetTextProperty()->GetColor();
unsigned char actorColor[4] = {
static_cast<unsigned char>(dcolor[0]*255),
static_cast<unsigned char>(dcolor[1]*255),
static_cast<unsigned char>(dcolor[2]*255),
static_cast<unsigned char>(tprop->GetOpacity()*255)};
static_cast<unsigned char>(textAct->GetTextProperty()->GetOpacity()*255)};
vtkGL2PSUtilities::Draw3DPath(path.GetPointer(), actorMatrix, rasterPos,
actorColor);
......
......@@ -115,13 +115,10 @@ public:
bool MathTextIsSupported() { return HasMathText; }
// Description:
// Given a text property and a string, get the bounding box [xmin, xmax] x
// [ymin, ymax]. Note that this is the bounding box of the area
// where actual pixels will be written, given a text/pen/baseline location
// of (0,0).
// For example, if the string starts with a 'space', or depending on the
// orientation, you can end up with a [-20, -10] x [5, 10] bbox (the math
// to get the real bbox is straightforward).
// Given a text property and a string, get the bounding box {xmin, xmax,
// ymin, ymax} of the rendered string in pixels. The origin of the bounding
// box is the anchor point described by the horizontal and vertical
// justification text property variables.
// Some rendering backends need the DPI of the target. If it is not provided,
// a DPI of 120 is assumed.
// Return true on success, false otherwise.
......@@ -146,6 +143,9 @@ public:
// texture on graphics hardware that requires texture image dimensions to be
// a power of two; textDims can be used to determine the texture coordinates
// needed to cleanly fit the text on the target.
// The origin of the image's extents is aligned with the anchor point
// described by the text property's vertical and horizontal justification
// options.
// Some rendering backends need the DPI of the target. If it is not provided,
// a DPI of 120 is assumed.
bool RenderString(vtkTextProperty *tprop, const vtkStdString &str,
......@@ -185,7 +185,9 @@ public:
// Description:
// Given a text property and a string, this function populates the vtkPath
// path with the outline of the rendered string.
// path with the outline of the rendered string. The origin of the path
// coordinates is aligned with the anchor point described by the text
// property's horizontal and vertical justification options.
// Return true on success, false otherwise.
bool StringToPath(vtkTextProperty *tprop, const vtkStdString &str,
vtkPath *path, int backend = Default)
......
......@@ -374,14 +374,6 @@ void vtkFreeTypeTools::ReleaseCacheManager()
}
}
//----------------------------------------------------------------------------
bool vtkFreeTypeTools::IsBoundingBoxValid(int bbox[4])
{
return (!bbox ||
bbox[0] == VTK_INT_MAX || bbox[1] == VTK_INT_MIN ||
bbox[2] == VTK_INT_MAX || bbox[3] == VTK_INT_MIN) ? false : true;
}
//----------------------------------------------------------------------------
bool vtkFreeTypeTools::GetBoundingBox(vtkTextProperty *tprop,
const vtkStdString& str,
......@@ -1151,8 +1143,6 @@ bool vtkFreeTypeTools::StringToPathInternal(vtkTextProperty *tprop,
return false;
}
// Adjust for justification
this->JustifyPath(path, metaData);
return true;
}
......@@ -1222,17 +1212,69 @@ bool vtkFreeTypeTools::CalculateBoundingBox(const T& str,
float c = cos(angle);
float s = sin(angle);
// The base of the current line, and temp vars for line offsets and origins
// Calculate the initial pen position. Assuming (0, 0) is the justified
// anchor point, we want to locate the leftmost point on the first baseline:
// 1) Start with the pen at the anchor point:
int pen[2] = {0, 0};
double offset[2] = {0., 0.};
int origin[2] = {0, 0};
// 2) Account for horizonal justification:
switch (metaData.textProperty->GetJustification())
{
case VTK_TEXT_CENTERED:
pen[0] -= metaData.maxLineWidth / 2;
break;
case VTK_TEXT_RIGHT:
pen[0] -= metaData.maxLineWidth;
break;
case VTK_TEXT_LEFT:
break;
default:
vtkErrorMacro(<< "Bad horizontal alignment flag: "
<< metaData.textProperty->GetJustification());
break;
}
// 3) Account for vertical justification:
int fullHeight = metaData.lineMetrics.size() * metaData.height *
metaData.textProperty->GetLineSpacing();
switch (metaData.textProperty->GetVerticalJustification())
{
case VTK_TEXT_CENTERED:
pen[1] += fullHeight / 2;
break;
case VTK_TEXT_BOTTOM:
pen[1] += fullHeight;
break;
case VTK_TEXT_TOP:
break;
default:
vtkErrorMacro(<< "Bad vertical alignment flag: "
<< metaData.textProperty->GetVerticalJustification());
break;
}
// 4) Move pen down to the first baseline, and account for "LineOffset":
pen[1] -= metaData.ascent + 1 + metaData.textProperty->GetLineOffset();
// 5) Now rotate:
int tmpX = vtkMath::Floor(c * pen[0] - s * pen[1] + 0.5);
int tmpY = vtkMath::Floor(s * pen[0] + c * pen[1] + 0.5);
pen[0] = tmpX;
pen[1] = tmpY;
// Initialize bbox
metaData.bbox[0] = metaData.bbox[1] = pen[0];
metaData.bbox[2] = metaData.bbox[3] = pen[1];
// Calculate line offset:
double offset[2] = {
0., -(metaData.height * metaData.textProperty->GetLineSpacing())};
int rotOffset[2] = {vtkMath::Floor(c * offset[0] - s * offset[1] + 0.5),
vtkMath::Floor(s * offset[0] + c * offset[1] + 0.5)};
// Compile the metrics data to determine the final bounding box. Set line
// origins here, too.
int origin[2] = {0, 0};
int justification = metaData.textProperty->GetJustification();
for (size_t i = 0; i < metaData.lineMetrics.size(); ++i)
{
......@@ -1262,13 +1304,9 @@ bool vtkFreeTypeTools::CalculateBoundingBox(const T& str,
metaData.bbox[2] = std::min(metaData.bbox[2], metrics.ymin + origin[1]);
metaData.bbox[3] = std::max(metaData.bbox[3], metrics.ymax + origin[1]);
// Calculate offset of next line
offset[0] = 0.;
offset[1] = -(metaData.height * metaData.textProperty->GetLineSpacing());
// Update pen position
pen[0] += vtkMath::Floor(c * offset[0] - s * offset[1] + 0.5);
pen[1] += vtkMath::Floor(s * offset[0] + c * offset[1] + 0.5);
pen[0] += rotOffset[0];
pen[1] += rotOffset[1];
}
// Adjust for shadow
......@@ -1294,14 +1332,49 @@ bool vtkFreeTypeTools::CalculateBoundingBox(const T& str,
}
}
// Sometimes the components of the bounding box are overestimated if
// the ascender/descender isn't utilized. Shift the box so that it contains
// 0 for better alignment. This essentially moves the anchor point back onto
// the border of the data.
tmpX = 0;
tmpY = 0;
if (metaData.bbox[0] > 0)
{
tmpX = -metaData.bbox[0];
}
else if (metaData.bbox[1] < 0)
{
tmpX = -metaData.bbox[1];
}
if (metaData.bbox[2] > 0)
{
tmpY = -metaData.bbox[2];
}
else if (metaData.bbox[3] < 0)
{
tmpY = -metaData.bbox[3];
}
if (tmpX != 0 || tmpY != 0)
{
metaData.bbox[0] += tmpX;
metaData.bbox[1] += tmpX;
metaData.bbox[2] += tmpY;
metaData.bbox[3] += tmpY;
for (size_t i = 0; i < metaData.lineMetrics.size(); ++i)
{
MetaData::LineMetrics &metrics = metaData.lineMetrics[i];
metrics.originX += tmpX;
metrics.originY += tmpY;
}
}
return true;
}
//----------------------------------------------------------------------------
void vtkFreeTypeTools::PrepareImageData(vtkImageData *data, int textBbox[4])
{
// The bounding box is the area that is going to be filled with pixels
// given a text origin of (0, 0). Calculate the bbox's dimensions.
// Calculate the bbox's dimensions
int textDims[2];
textDims[0] = (textBbox[1] - textBbox[0] + 1);
textDims[1] = (textBbox[3] - textBbox[2] + 1);
......@@ -1318,7 +1391,7 @@ void vtkFreeTypeTools::PrepareImageData(vtkImageData *data, int textBbox[4])
targetDims[1] = vtkMath::NearestPowerOfTwo(targetDims[1]);
}
// Calculate the target extent of the image using the text origin as (0, 0, 0)
// Calculate the target extent of the image.
int targetExtent[6];
targetExtent[0] = textBbox[0];
targetExtent[1] = textBbox[0] + targetDims[0] - 1;
......@@ -1356,53 +1429,6 @@ void vtkFreeTypeTools::PrepareImageData(vtkImageData *data, int textBbox[4])
(data->GetNumberOfPoints() * data->GetNumberOfScalarComponents()));
}
//----------------------------------------------------------------------------
void vtkFreeTypeTools::JustifyPath(vtkPath *path, MetaData &metaData)
{
double delta[2] = {0.0, 0.0};
double bounds[6];
vtkPoints *points = path->GetPoints();
points->GetBounds(bounds);
// Apply justification:
switch (metaData.textProperty->GetJustification())
{
default:
case VTK_TEXT_LEFT:
delta[0] = -bounds[0];
break;
case VTK_TEXT_CENTERED:
delta[0] = -(bounds[0] + bounds[1]) * 0.5;
break;
case VTK_TEXT_RIGHT:
delta[0] = -bounds[1];
break;
}
switch (metaData.textProperty->GetVerticalJustification())
{
default:
case VTK_TEXT_BOTTOM:
delta[1] = -bounds[2];
break;
case VTK_TEXT_CENTERED:
delta[1] = -(bounds[2] + bounds[3]) * 0.5;
break;
case VTK_TEXT_TOP:
delta[1] = -bounds[3];
}
if (delta[0] != 0 || delta[1] != 0) {
for (vtkIdType i = 0; i < points->GetNumberOfPoints(); ++i)
{
double *point = points->GetPoint(i);
point[0] += delta[0];
point[1] += delta[1];
points->SetPoint(i, point);
}
}
}
//----------------------------------------------------------------------------
template <typename StringType, typename DataType>
bool vtkFreeTypeTools::PopulateData(const StringType &str, DataType data,
......
......@@ -92,22 +92,15 @@ public:
vtkGetMacro(MaximumNumberOfBytes, unsigned long);
// Description:
// Given a text property and a string, get the bounding box [xmin, xmax] x
// [ymin, ymax]. Note that this is the bounding box of the area
// where actual pixels will be written, given a text/pen/baseline location
// of (0,0).
// For example, if the string starts with a 'space', or depending on the
// orientation, you can end up with a [-20, -10] x [5, 10] bbox (the math
// to get the real bbox is straightforward).
// Return 1 on success, 0 otherwise.
// You can use IsBoundingBoxValid() to test if the computed bbox
// is valid (it may not if GetBoundingBox() failed or if the string
// was empty).
// Given a text property and a string, get the bounding box {xmin, xmax,
// ymin, ymax} of the rendered string in pixels. The origin of the bounding
// box is the anchor point described by the horizontal and vertical
// justification text property variables.
// Returns true on success, false otherwise.
bool GetBoundingBox(vtkTextProperty *tprop, const vtkStdString& str,
int bbox[4]);
bool GetBoundingBox(vtkTextProperty *tprop, const vtkUnicodeString& str,
int bbox[4]);
bool IsBoundingBoxValid(int bbox[4]);
// Description:
// Given a text property and a string, this function initializes the
......@@ -115,6 +108,9 @@ public:
// will be overwritten by the pixel width and height of the rendered string.
// This is useful when ScaleToPowerOfTwo is true, and the image dimensions may
// not match the dimensions of the rendered text.
// The origin of the image's extents is aligned with the anchor point
// described by the text property's vertical and horizontal justification
// options.
bool RenderString(vtkTextProperty *tprop, const vtkStdString& str,
vtkImageData *data, int textDims[2] = NULL);
bool RenderString(vtkTextProperty *tprop, const vtkUnicodeString& str,
......@@ -122,7 +118,9 @@ public:
// Description:
// Given a text property and a string, this function populates the vtkPath
// path with the outline of the rendered string.
// path with the outline of the rendered string. The origin of the path
// coordinates is aligned with the anchor point described by the text
// property's horizontal and vertical justification options.
bool StringToPath(vtkTextProperty *tprop, const vtkStdString& str,
vtkPath *path);
bool StringToPath(vtkTextProperty *tprop, const vtkUnicodeString& str,
......@@ -197,10 +195,6 @@ protected:
// receive the text stored in str
void PrepareImageData(vtkImageData *data, int bbox[4]);
// Description:
// Internal helper method called by StringToPath
void JustifyPath(vtkPath *path, MetaData &metaData);
// Description:
// Given a text property, get the corresponding FreeType size object
// (a structure storing both a face and a specific size metric).
......
......@@ -608,6 +608,10 @@ void vtkTextActor::ComputeScaledFont(vtkViewport *viewport)
this->ScaledTextProperty->ShallowCopy(this->TextProperty);
}
// Combine this actor's orientation with the set text property's rotation
double rotAngle = this->TextProperty->GetOrientation() + this->Orientation;
this->ScaledTextProperty->SetOrientation(rotAngle);
if (this->TextScaleMode == TEXT_SCALE_MODE_NONE)
{
if (this->TextProperty)
......@@ -675,11 +679,9 @@ void vtkTextActor::ComputeScaledFont(vtkViewport *viewport)
// If the orientation has changed then we'll probably need to change our
// constrained font size as well
if(this->FormerOrientation != this->Orientation)
if(this->FormerOrientation != rotAngle)
{
this->Transform->Identity();
this->Transform->RotateZ(this->Orientation);
this->FormerOrientation = this->Orientation;
this->FormerOrientation = rotAngle;
orientationHasChanged = 1;
}
}
......@@ -722,7 +724,7 @@ void vtkTextActor::ComputeScaledFont(vtkViewport *viewport)
int max_height = static_cast<int>(this->MaximumLineHeight * size[1]);
int fsize = this->TextRenderer->GetConstrainedFontSize(
this->Input, this->TextProperty, size[0],
this->Input, this->ScaledTextProperty, size[0],
(size[1] < max_height ? size[1] : max_height));
if (fsize == -1)
......@@ -751,7 +753,9 @@ void vtkTextActor::ComputeScaledFont(vtkViewport *viewport)
// ----------------------------------------------------------------------------
void vtkTextActor::ComputeRectangle(vtkViewport *viewport)
{
int dims[3];
int dims[2] = {0, 0};
int anchorOffset[2] = {0, 0};
this->RectanglePoints->Reset();
if ( this->ImageData )
{
......@@ -765,6 +769,8 @@ void vtkTextActor::ComputeRectangle(vtkViewport *viewport)
}
dims[0] = ( text_bbox[1] - text_bbox[0] + 1 );
dims[1] = ( text_bbox[3] - text_bbox[2] + 1 );
anchorOffset[0] = text_bbox[0];
anchorOffset[1] = text_bbox[2];
// compute TCoords.
vtkFloatArray* tc = vtkFloatArray::SafeDownCast
......@@ -786,19 +792,9 @@ void vtkTextActor::ComputeRectangle(vtkViewport *viewport)
tc->InsertComponent(3, 0, tcXMax);
tc->InsertComponent(3, 1, 0.0);
}
else
{
dims[0] = dims[1] = 0;
}
// I could do this with a transform, but it is simple enough
// to rotate the four corners in 2D ...
double radians = vtkMath::RadiansFromDegrees( this->Orientation );
double c = cos( radians );
double s = sin( radians );
double xo = 0.0, yo = 0.0;
double x, y;
// When TextScaleMode is PROP, we justify text based on the rectangle
// formed by Position & Position2 coordinates
if( ( this->TextScaleMode == TEXT_SCALE_MODE_PROP ) || this->UseBorderAlign )
......@@ -848,76 +844,17 @@ void vtkTextActor::ComputeRectangle(vtkViewport *viewport)
default:
vtkErrorMacro( << "Bad alignment point value." );
}
//handle line offset. make sure we stay within the bounds defined by
//position1 & position2
double offset = this->TextProperty->GetLineOffset();
if( ( yo + offset + dims[1] ) > maxHeight )
{
yo = maxHeight - dims[1];
}
else if( ( yo + offset ) < 0 )
{
yo = 0;
}
else
{
yo += offset;
}
}
else
{
// I could get rid of "GetAlignmentPoint" and use justification directly.
switch ( this->GetAlignmentPoint() )
{
case 0:
break;
case 1:
xo = -dims[0] * 0.5;
break;
case 2:
xo = -dims[0];
break;
case 3:
yo = -dims[1] * 0.5;
break;
case 4:
yo = -dims[1] * 0.5;
xo = -dims[0] * 0.5;
break;
case 5:
yo = -dims[1] * 0.5;
xo = -dims[0];
break;
case 6:
yo = -dims[1];
break;
case 7:
yo = -dims[1];
xo = -dims[0] * 0.5;
break;
case 8:
yo = -dims[1];
xo = -dims[0];
break;
default:
vtkErrorMacro( << "Bad alignment point value." );
}
// handle line offset
yo += this->TextProperty->GetLineOffset();
xo = anchorOffset[0];
yo = anchorOffset[1];
} //end unscaled text case
x = xo;
y = yo;
this->RectanglePoints->InsertNextPoint( c*x-s*y,s*x+c*y,0.0 );
x = xo;
y = yo + dims[1];
this->RectanglePoints->InsertNextPoint( c*x-s*y,s*x+c*y,0.0 );
x = xo + dims[0];
y = yo + dims[1];
this->RectanglePoints->InsertNextPoint( c*x-s*y,s*x+c*y,0.0 );
x = xo + dims[0];
y = yo;
this->RectanglePoints->InsertNextPoint( c*x-s*y,s*x+c*y,0.0 );
this->RectanglePoints->InsertNextPoint(xo, yo, 0.0 );
this->RectanglePoints->InsertNextPoint(xo, yo + dims[1], 0.0 );
this->RectanglePoints->InsertNextPoint(xo + dims[0], yo + dims[1], 0.0 );
this->RectanglePoints->InsertNextPoint(xo + dims[0], yo, 0.0 );
}
// ----------------------------------------------------------------------------
......
......@@ -23,9 +23,6 @@
// excessively big textures for 45 degrees angles).
// This will be fixed first.
// - No checking is done at the moment regarding hardware texture size limits.
// - Alignment is not supported (soon).
// - Multiline is not supported.
// - Need to fix angle out of 0<->360
//
// .SECTION See Also
// vtkProp3D
......
......@@ -368,6 +368,50 @@ vtkMatplotlibMathTextUtilities::GetFontProperties(vtkTextProperty *tprop)
tpropWeight, tpropFontSize);
}
//----------------------------------------------------------------------------
void vtkMatplotlibMathTextUtilities::GetJustifiedBBox(int rows, int cols,
vtkTextProperty *tprop,
int bbox[])
{
bbox[0] = 0;
bbox[1] = cols - 1;
bbox[2] = 0;
bbox[3] = rows - 1;
int justifyOffset[2];
switch (tprop->GetJustification())
{
default:
case VTK_TEXT_LEFT:
justifyOffset[0] = 0;
break;
case VTK_TEXT_CENTERED:
justifyOffset[0] = bbox[1] / 2;
break;
case VTK_TEXT_RIGHT:
justifyOffset[0] = bbox[1];
break;
}
switch (tprop->GetVerticalJustification())
{
default:
case VTK_TEXT_BOTTOM:
justifyOffset[1] = 0;
break;
case VTK_TEXT_CENTERED:
justifyOffset[1] = bbox[3] / 2;
break;
case VTK_TEXT_TOP:
justifyOffset[1] = bbox[3];
break;
}
bbox[0] -= justifyOffset[0];
bbox[1] -= justifyOffset[0];
bbox[2] -= justifyOffset[1];
bbox[3] -= justifyOffset[1];
}
//----------------------------------------------------------------------------
void vtkMatplotlibMathTextUtilities::RotateCorners(double angleDeg,
double corners[4][2],
......@@ -414,52 +458,56 @@ void vtkMatplotlibMathTextUtilities::RotateCorners(double angleDeg,
//----------------------------------------------------------------------------
// This is more or less ported from vtkFreeTypeTools.
bool vtkMatplotlibMathTextUtilities::PrepareImageData(vtkImageData *data,
int bbox[4])
int textBbox[4])
{
int width = bbox[1] - bbox[0] + 1;
int height = bbox[3] - bbox[2] + 1;
// If the current image data is too small to render the text,
// or more than twice as big (too hungry), then resize
int imgDims[3], newImgDims[3];
data->GetDimensions(imgDims);
// Calculate the bbox's dimensions
int textDims[2];
textDims[0] = (textBbox[1] - textBbox[0] + 1);
textDims[1] = (textBbox[3] - textBbox[2] + 1);
// Calculate the size the image needs to be.
int targetDims[3];
targetDims[0] = textDims[0];
targetDims[1] = textDims[1];
targetDims[2] = 1;
// Scale to the next highest power of 2 if required.
if (this->ScaleToPowerOfTwo)
{
targetDims[0] = vtkMath::NearestPowerOfTwo(targetDims[0]);