mirror of
https://github.com/ONLYOFFICE/core.git
synced 2026-04-07 13:55:33 +08:00
427 lines
12 KiB
C++
427 lines
12 KiB
C++
|
|
#include "stdafx.h"
|
|
#include "TextToPath.h"
|
|
|
|
// constructor
|
|
CTextToPath::CTextToPath(const GraphicsPath& guidePath, REAL flatness)
|
|
: m_alignmentVertical(c_StringAlignmentNear)
|
|
, m_alignmentHorizontal(c_StringAlignmentNear)
|
|
, m_kerning(0.F)
|
|
, m_addKerning(0.F)
|
|
, m_path(guidePath.Clone())
|
|
{
|
|
// It all works only if the guide path is flattened
|
|
if (m_path)
|
|
m_path->Flatten(NULL, flatness);
|
|
}
|
|
|
|
// destructors
|
|
CTextToPath::~CTextToPath()
|
|
{
|
|
if (m_path)
|
|
delete m_path;
|
|
}
|
|
|
|
// Return: parameter t of (first) intersection of line segment pntA->pntB with circle (center, radius); r2 = radius^2.
|
|
// -1: no intersection
|
|
REAL CTextToPath::LineSegmentCircle(PointF pntA, PointF pntB, PointF center, REAL r2) const
|
|
{
|
|
// Translate to (0, 0) to get rid of center.
|
|
pntA = pntA - center;
|
|
pntB = pntB - center;
|
|
|
|
// Calculate squares of distance to (0, 0)
|
|
REAL rA2 = pntA.X * pntA.X + pntA.Y * pntA.Y;
|
|
REAL rB2 = pntB.X * pntB.X + pntB.Y * pntB.Y;
|
|
|
|
// If line segment is completely outside circle => no intersection, return -1
|
|
if (rA2 > r2 && rB2 > r2)
|
|
return -1;
|
|
|
|
// If line segment is completely inside circle => no intersection, return -1
|
|
if (rA2 < r2 && rB2 < r2)
|
|
return -1;
|
|
|
|
PointF pntDiff = pntB - pntA;
|
|
|
|
// line segment:
|
|
// x = xA + t * xDiff
|
|
// y = yA + t * yDiff, t >= 0, t <= 1
|
|
// circle:
|
|
// x^2 + y^2 = r2
|
|
// results in standard qudratic equation:
|
|
// a * t^2 + b * t + c = 0
|
|
// with:
|
|
// a = xDiff^2 + yDiff^2
|
|
// b = 2 * (xA * xDiff + yA * yDiff)
|
|
// c = rA2 - r2
|
|
|
|
REAL a = pntDiff.X * pntDiff.X + pntDiff.Y * pntDiff.Y;
|
|
REAL b = 2 * (pntA.X * pntDiff.X + pntA.Y * pntDiff.Y);
|
|
REAL c = rA2 - r2;
|
|
|
|
// discriminant D = b^2 - 4 * a * c
|
|
REAL D = b * b - 4 * a * c;
|
|
|
|
REAL t = -1; // indicate no intersection (wouldn't be possible)
|
|
|
|
// one intersection: t = -b / 2 * a
|
|
if (D == 0)
|
|
t = - b/(2 * a);
|
|
// two intersections: (t = -b +/- sqrt(D)) / 2 * a
|
|
else if (D > 0)
|
|
{
|
|
REAL sqrtD = (REAL)sqrt(D);
|
|
REAL t1 = (- b + sqrtD) / (2 * a);
|
|
REAL t2 = (- b - sqrtD) / (2 * a);
|
|
|
|
if (t1 >= 0 && t1 <= 1)
|
|
t = (t2 >= 0 && t2 < t1)? t2 : t1;
|
|
else if (t2 >= 0 && t2 <= 1)
|
|
t = t2;
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
// Convert position on path to Euclidian PointF
|
|
PointF CTextToPath::GetPathPoint(PathPosition position) const
|
|
{
|
|
PointF point(0, 0);
|
|
if (position < 0)
|
|
return point;
|
|
|
|
int iSegment = (int) floor(position);
|
|
if (iSegment < m_path->GetPointCount() - 1)
|
|
{
|
|
PathData pd;
|
|
if (m_path->GetPathData(&pd) == Ok)
|
|
{
|
|
point = pd.Points[iSegment];
|
|
PointF diff = pd.Points[iSegment +1] - pd.Points[iSegment];
|
|
REAL t = position - iSegment;
|
|
point.X += t * diff.X;
|
|
point.Y += t * diff.Y;
|
|
}
|
|
}
|
|
return point;
|
|
}
|
|
|
|
// Find the first position to the right where the path intersects with circle
|
|
PathPosition CTextToPath::FindRightPosition(PathPosition posCenter, REAL radius) const
|
|
{
|
|
if (posCenter < 0)
|
|
return -1;
|
|
|
|
int iStartSegment = (int) floor(posCenter);
|
|
if (iStartSegment >= m_path->GetPointCount() - 1)
|
|
return -1;
|
|
|
|
PathData pd;
|
|
if (m_path->GetPathData(&pd) == Ok)
|
|
{
|
|
PointF pntStart = GetPathPoint(posCenter);
|
|
|
|
REAL r2 = radius * radius;
|
|
|
|
// First, see if the intersection point is on the first segment
|
|
// I.o.w. see whether the radius is closer than the right point of the first segment
|
|
PointF diffRight = pntStart - pd.Points[iStartSegment + 1];
|
|
REAL r2Right = diffRight.X * diffRight.X + diffRight.Y * diffRight.Y;
|
|
if (r2 < r2Right)
|
|
{
|
|
// Yes, it is, so intersection point is on first segment.
|
|
PointF diffLeft = pntStart - pd.Points[iStartSegment];
|
|
REAL r2Left = diffLeft.X * diffLeft.X + diffLeft.Y * diffLeft.Y;
|
|
|
|
REAL rLeft = (REAL)sqrt(r2Left); // Distance from start point to left point of segment
|
|
REAL rRight =(REAL)sqrt(r2Right); // Distance from start point to right point of segment
|
|
|
|
return iStartSegment + 1.0F - (rRight - radius)/(rRight + rLeft);
|
|
}
|
|
|
|
// No, the searched point is not on the first segment
|
|
// So search in next segments
|
|
for (int i = iStartSegment + 1; i < m_path->GetPointCount() - 1; i++)
|
|
{
|
|
REAL t = LineSegmentCircle(pd.Points[i], pd.Points[i + 1], pntStart, r2);
|
|
if (t >= 0)
|
|
return i + t; // Found.
|
|
}
|
|
}
|
|
return -1; // Not found.
|
|
}
|
|
|
|
PathPosition CTextToPath::SetTransform(Graphics* graphics, PathPosition position, REAL wCell) const
|
|
{
|
|
// pntOrigin will be updated to the lower right of the character cell.
|
|
if (wCell == 0)
|
|
return -1; // Can't work with this.
|
|
|
|
// Use this information to find the right position.
|
|
PathPosition posRight = FindRightPosition(position, wCell);
|
|
if (posRight < 0)
|
|
return posRight; // Some error, ignore this glyph and return start position
|
|
|
|
// Convert lower left and lower right to Euclidian PointF's.
|
|
PointF pntLeft = GetPathPoint(position);
|
|
PointF pntRight = GetPathPoint(posRight);
|
|
|
|
// Use these to calculate the cosine and sine of the rotation
|
|
// Note that we don't need the value of the angle itself
|
|
PointF pntDiff = pntRight - pntLeft;
|
|
REAL cosPhi = pntDiff.X / wCell; // wCell != 0.
|
|
REAL sinPhi = pntDiff.Y / wCell;
|
|
|
|
// Fill in a transformation matrix, employing some clever
|
|
Matrix matrix(cosPhi, sinPhi, - sinPhi, cosPhi, pntLeft.X, pntLeft.Y);
|
|
graphics->SetTransform(&matrix);
|
|
|
|
return posRight;
|
|
}
|
|
|
|
// Transform indent in logical co-ordinates in position
|
|
PathPosition CTextToPath::GetPathPosition(REAL indent) const
|
|
{
|
|
PathData pd;
|
|
if (m_path->GetPathData(&pd) == Ok)
|
|
{
|
|
for (int i = 0; i < m_path->GetPointCount() - 1; i++)
|
|
{
|
|
PointF pntDiff = pd.Points[i] - pd.Points[i + 1];
|
|
REAL length = (REAL)sqrt(pntDiff.X * pntDiff.X + pntDiff.Y * pntDiff.Y);
|
|
if (indent < length)
|
|
return i + indent/length;
|
|
indent -= length;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Draw line path for debug
|
|
void CTextToPath::DrawLine(Graphics* Gr)
|
|
{
|
|
if (!Gr)
|
|
return;
|
|
|
|
int nCount = m_path->GetPointCount();
|
|
Point* pts = new Point[nCount];
|
|
m_path->GetPathPoints(pts, nCount);
|
|
Pen pen(Color(255, 0 ,0 ,0), 1);
|
|
Gr->DrawLines(&pen, pts, nCount);
|
|
if (pts)
|
|
delete[] pts;
|
|
}
|
|
|
|
void CTextToPath::DrawText(Graphics* Gr, Font* font, Painter::CBrush* brush, Painter::CTextureManager* textures, Painter::CEdgeText* edge, Painter::CShadow* shadow, BSTR Text, int length)
|
|
{
|
|
if (!Gr)
|
|
return;
|
|
|
|
// draw line path for debug
|
|
// DrawLine(Gr);
|
|
|
|
// get bound rect for string
|
|
REAL size = font->GetSize();
|
|
RectF rectDraw(0.F, 0.F, size*length, size);
|
|
StringFormat format;
|
|
format.SetAlignment(StringAlignmentCenter);
|
|
format.SetLineAlignment(StringAlignmentCenter);
|
|
Gr->SetTextRenderingHint(TextRenderingHintAntiAlias);
|
|
RectF rectString;
|
|
Gr->MeasureString(Text, length, font, rectDraw, &format, &rectString);
|
|
|
|
// save old smoothing mode and set high quality mode
|
|
SmoothingMode oldSmoothingMode = Gr->GetSmoothingMode();
|
|
Gr->SetSmoothingMode(SmoothingModeHighQuality);
|
|
|
|
// draw shadow
|
|
RectF rectCharacter;
|
|
PathPosition pos = GetStartPosition(Gr, font, format, Text, length, rectString);
|
|
REAL shift = m_kerning + m_addKerning;
|
|
if (shift)
|
|
pos = SetTransform(Gr, pos, shift/2.F);
|
|
PathPosition oldPos = pos;
|
|
if (shadow->Visible)
|
|
for(int index = 0; index < length; index++)
|
|
{
|
|
// get rect of current(index) letter
|
|
format.SetLineAlignment(StringAlignmentCenter);
|
|
Painter::GetRectCurrentCharacter(Gr, &format, font, Text, length, index, rectString, rectCharacter);
|
|
|
|
// get position letter on curve from begin and transform graphics for letter
|
|
if (index && shift)
|
|
pos = SetTransform(Gr, pos, shift);
|
|
pos = SetTransform(Gr, pos, rectCharacter.Width);
|
|
|
|
// draw letter
|
|
if (pos < 0)
|
|
break;
|
|
|
|
// set rest for letter
|
|
rectCharacter.X = 0.F;
|
|
rectCharacter.Y = -rectCharacter.Height;
|
|
rectCharacter.Height *= 2;
|
|
|
|
// set format
|
|
format.SetLineAlignment((StringAlignment)m_alignmentVertical);
|
|
|
|
// draw shadow
|
|
shadow->Draw(Gr, font, &format, Text + index, 1, rectCharacter);
|
|
|
|
// reset transforms
|
|
Gr->ResetTransform();
|
|
}
|
|
|
|
// draw text
|
|
pos = oldPos;
|
|
for(int index = 0; index < length; index++)
|
|
{
|
|
// get rect of current(index) letter
|
|
format.SetLineAlignment(StringAlignmentCenter);
|
|
Painter::GetRectCurrentCharacter(Gr, &format, font, Text, length, index, rectString, rectCharacter);
|
|
|
|
// get position letter on curve from begin and transform graphics for letter
|
|
if (index && shift)
|
|
pos = SetTransform(Gr, pos, shift);
|
|
pos = SetTransform(Gr, pos, rectCharacter.Width);
|
|
|
|
// draw letter
|
|
if (pos < 0)
|
|
break;
|
|
|
|
// set rest for letter
|
|
rectCharacter.X = 0.F;
|
|
rectCharacter.Y = -rectCharacter.Height;
|
|
rectCharacter.Height *= 2;
|
|
|
|
// set format
|
|
format.SetLineAlignment((StringAlignment)m_alignmentVertical);
|
|
|
|
// draw edge
|
|
edge->Draw(Gr, font, &format, Text + index, 1, rectCharacter);
|
|
|
|
// draw letter
|
|
Brush* pBrush = brush->GetBrush(textures, &rectCharacter, index);
|
|
Gr->DrawString(Text + index, 1, font, rectCharacter, &format, pBrush);
|
|
/*
|
|
// draw rect for letter for debug
|
|
Pen blackPen(Color(255, 0, 0, 0), 1);
|
|
Gr->DrawRectangle(&blackPen, rectCharacter);
|
|
*/
|
|
// reset transforms
|
|
Gr->ResetTransform();
|
|
}
|
|
|
|
// restore old smoothing mode
|
|
Gr->SetSmoothingMode(oldSmoothingMode);
|
|
}
|
|
|
|
// Get start position according to horizontal alignment
|
|
PathPosition CTextToPath::GetStartPosition(Graphics* Gr, Font* font, StringFormat& format, BSTR Text, int length, RectF& rectString)
|
|
{
|
|
PathPosition pos = GetPathPosition(0);
|
|
m_addKerning = 0.F;
|
|
|
|
// case alignment near
|
|
if (c_StringAlignmentNear == m_alignmentHorizontal)
|
|
return 0.F;
|
|
|
|
RectF rectCharacter;
|
|
for(int index = 0; index < length; index++)
|
|
{
|
|
// get rect of current(index) letter
|
|
format.SetLineAlignment(StringAlignmentCenter);
|
|
Painter::GetRectCurrentCharacter(Gr, &format, font, Text, length, index, rectString, rectCharacter);
|
|
|
|
// get position letter on curve from begin and transform graphics for letter
|
|
pos = SetTransform(Gr, pos, rectCharacter.Width + m_kerning);
|
|
|
|
// string is long then curve
|
|
if (pos < 0)
|
|
return 0.F;
|
|
}
|
|
|
|
// get rect of 0-th letter
|
|
RectF testRect;
|
|
format.SetLineAlignment(StringAlignmentCenter);
|
|
Painter::GetRectCurrentCharacter(Gr, &format, font, Text, length, 0, rectString, testRect);
|
|
|
|
// get count of free cells
|
|
int nCount = -1;
|
|
PathPosition posAdd = pos;
|
|
while (pos >= 0)
|
|
{
|
|
nCount++;
|
|
pos = SetTransform(Gr, pos, testRect.Width + m_kerning);
|
|
}
|
|
|
|
// case alignment center
|
|
if (c_StringAlignmentCenter == m_alignmentHorizontal)
|
|
{
|
|
nCount = nCount/2;
|
|
pos = (GetPathPosition(0));
|
|
for(int index = 0; index < nCount; index++)
|
|
pos = SetTransform(Gr, pos, testRect.Width + m_kerning);
|
|
|
|
return pos;
|
|
}
|
|
|
|
// case alignment far
|
|
if (c_StringAlignmentFar == m_alignmentHorizontal)
|
|
{
|
|
pos = (GetPathPosition(0));
|
|
for(int index = 0; index < nCount; index++)
|
|
pos = SetTransform(Gr, pos, testRect.Width + m_kerning);
|
|
|
|
return pos;
|
|
}
|
|
|
|
// case alignment stretch
|
|
if (c_StringAlignmentStretch == m_alignmentHorizontal)
|
|
{
|
|
m_addKerning = (testRect.Width + m_kerning)*nCount/length;
|
|
|
|
// check result
|
|
pos = GetPathPosition(0);
|
|
for(int index = 0; index < length; index++)
|
|
{
|
|
// get rect of current(index) letter
|
|
format.SetLineAlignment(StringAlignmentCenter);
|
|
Painter::GetRectCurrentCharacter(Gr, &format, font, Text, length, index, rectString, rectCharacter);
|
|
|
|
// get position letter on curve from begin and transform graphics for letter
|
|
pos = SetTransform(Gr, pos, rectCharacter.Width + m_kerning + m_addKerning);
|
|
|
|
// string is long then curve
|
|
if (pos < 0)
|
|
{
|
|
nCount--;
|
|
break;
|
|
}
|
|
}
|
|
m_addKerning = (testRect.Width + m_kerning)*nCount/length;
|
|
|
|
return 0.F;
|
|
}
|
|
|
|
return 0.F;
|
|
}
|
|
|
|
// format functions
|
|
void CTextToPath::SetAlignmentVertical(int alignmentVertical)
|
|
{
|
|
if (c_StringAlignmentNear <= alignmentVertical && c_StringAlignmentFar >= alignmentVertical)
|
|
m_alignmentVertical = alignmentVertical;
|
|
}
|
|
void CTextToPath::SetAlignmentHorizontal(int alignmentHorizontal)
|
|
{
|
|
if (c_StringAlignmentNear <= alignmentHorizontal && c_StringAlignmentStretch >= alignmentHorizontal)
|
|
m_alignmentHorizontal = alignmentHorizontal;
|
|
}
|
|
void CTextToPath::SetKerning(REAL kerning)
|
|
{
|
|
if (kerning >= 0.F)
|
|
m_kerning = kerning;
|
|
}
|