Files
opennurbs/opennurbs_glyph_outline.cpp
2018-10-10 22:43:34 +01:00

3064 lines
85 KiB
C++

//
// Copyright (c) 1993-2018 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
#include "opennurbs.h"
#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif
#include "opennurbs_internal_glyph.h"
#include "opennurbs_win_dwrite.h"
unsigned int ON_Outline::FigureCount() const
{
return m_figures.UnsignedCount();
}
const ON_ClassArray< ON_OutlineFigure >& ON_Outline::Figures() const
{
return m_figures;
}
const ON_OutlineFigure& ON_Outline::Figure(
int i
) const
{
return (i >= 0 && i < m_figures.Count()) ? m_figures[i] : ON_OutlineFigure::Unset;
}
unsigned int ON_OutlineAccumulator::CurrentFigurePointCount() const
{
return m_point_accumulator.UnsignedCount();
}
unsigned int ON_Outline::OutlinePointCount() const
{
unsigned int outline_point_count = 0;
for (unsigned int i = 0; i < m_figures.UnsignedCount(); i++)
outline_point_count += m_figures[i].m_points.UnsignedCount();
return outline_point_count;
}
unsigned int ON_OutlineAccumulator::ErrorCount() const
{
return m_error_count;
}
const ON_BoundingBox ON_Outline::OutlineBoundingBox() const
{
while (0 == m_bbox_status)
{
m_bbox_status = 7;
m_bbox = ON_BoundingBox::NanBoundingBox;
const unsigned int figure_count = m_figures.UnsignedCount();
if (0 == figure_count)
break;
ON_BoundingBox bbox = m_figures[0].BoundingBox();
for (unsigned int i = 1; i < figure_count; i++)
{
bbox.Union(m_figures[i].BoundingBox());
}
if (bbox.IsValid() && bbox.IsNotEmpty())
{
m_bbox = bbox;
m_bbox_status = 1;
}
break;
}
return m_bbox;
}
double ON_Outline::AreaEstimate() const
{
if (
ON_OutlineFigure::Type::SingleStroke == m_figure_type
|| ON_OutlineFigure::Type::DoubleStroke == m_figure_type
)
{
return 0.0;
}
double outline_area = 0.0;
const ON__UINT32 figure_count = m_figures.UnsignedCount();
for (ON__UINT32 i = 0; i < figure_count; i++)
{
outline_area += m_figures[i].AreaEstimate();
}
return outline_area;
}
void ON_Outline::Reverse()
{
const ON__UINT32 figure_count = m_figures.UnsignedCount();
for (ON__UINT32 i = 0; i < figure_count; i++)
{
m_figures[i].ReverseFigure();
}
}
void ON_Outline::SortFigures(
ON_OutlineFigure::Orientation outer_loop_orientation
)
{
if (
ON_OutlineFigure::Orientation::Clockwise != outer_loop_orientation
&& ON_OutlineFigure::Orientation::CounterClockwise != outer_loop_orientation
)
return;
if (outer_loop_orientation == m_sorted_figure_outer_orientation)
return;
if (ON_OutlineFigure::Orientation::Error == m_sorted_figure_outer_orientation)
return; // not sortable
m_sorted_figure_outer_orientation = outer_loop_orientation;
const int figure_count = m_figures.Count();
if (figure_count <= 0)
return;
const ON_OutlineFigure::Orientation inner_loop_orientation
= (ON_OutlineFigure::Orientation::Clockwise == outer_loop_orientation)
? ON_OutlineFigure::Orientation::CounterClockwise
: ON_OutlineFigure::Orientation::Clockwise;
ON_SimpleArray<ON_OutlineFigure*> outer_figures(figure_count);
ON_SimpleArray<ON_OutlineFigure*> inner_figures(figure_count);
ON_SimpleArray<ON_OutlineFigure*> ignored_figures(figure_count);
for (int i = 0; i < figure_count; i++)
{
ON_OutlineFigure* ptr = &m_figures[i];
if (
(ON_OutlineFigure::Type::Perimeter == ptr->FigureType()
|| ON_OutlineFigure::Type::Unknown == ptr->FigureType()
|| ON_OutlineFigure::Type::Unset == ptr->FigureType())
)
{
const double figure_area = ptr->AreaEstimate();
const ON_BoundingBox figure_bbox = ptr->BoundingBox();
const ON_OutlineFigure::Orientation figure_orientation = ptr->FigureOrientation();
if (
0.0 != figure_area && ON_IsValid(figure_area)
&& figure_bbox.IsNotEmpty() && false == figure_bbox.IsPoint()
&& (outer_loop_orientation == figure_orientation
|| inner_loop_orientation == figure_orientation)
)
{
if (outer_loop_orientation == ptr->FigureOrientation())
outer_figures.Append(ptr);
else
inner_figures.Append(ptr);
continue;
}
// Mark this figure has NotPerimeter because something about
// its path is not valid. This will prevent it from being
// used in meshing.
ptr->m_figure_type = ON_OutlineFigure::Type::NotPerimeter;
}
// ignored figures are ones that are not a perimeter of an inner
// or outer boundary.
ignored_figures.Append(ptr);
}
unsigned int outer_count = outer_figures.UnsignedCount();
unsigned int inner_count = inner_figures.UnsignedCount();
if ( 0 == outer_count )
{
m_sorted_figure_outer_orientation = ON_OutlineFigure::Orientation::Error;
return;
}
if (0 == inner_count )
{
return;
}
const int winding_number_sign
= (ON_OutlineFigure::Orientation::Clockwise == outer_loop_orientation)
? -1
: 1;
const unsigned int outer_and_inner_count = outer_count + inner_count;
ON_SimpleArray<ON_OutlineFigure*> sorted_figures(figure_count);
while (sorted_figures.UnsignedCount() < outer_and_inner_count)
{
const unsigned int sorted_figures_count0 = sorted_figures.UnsignedCount();
for (unsigned int outer_dex = 0; outer_dex < outer_count; outer_dex++)
{
ON_OutlineFigure* outer_figure = outer_figures[outer_dex];
if (nullptr == outer_figure)
continue;
outer_figures[outer_dex] = nullptr;
sorted_figures.Append(outer_figure);
const double outer_area = fabs(outer_figure->AreaEstimate());
const ON_BoundingBox outer_bbox = outer_figure->BoundingBox();
for (unsigned int inner_dex = 0; inner_dex < inner_count; inner_dex++)
{
ON_OutlineFigure* inner_figure = inner_figures[inner_dex];
if (nullptr == inner_figure)
continue; // inner_figures[inner_dex] assigned to previous outer figure
const double inner_area = fabs(inner_figure->AreaEstimate());
if (false == (inner_area < outer_area))
continue;
const ON_BoundingBox inner_bbox = inner_figure->BoundingBox();
if (false == outer_bbox.Includes(inner_bbox))
continue;
const int wn0 = winding_number_sign * outer_figure->WindingNumber(inner_figure->m_points[0].m_point);
if (wn0 <= 0)
{
// Starting point of inner_figure is inside f and not inside outer_figure.
continue;
}
// Based on area, bounding box, and inner starting point winding number,
// inner_figure is either inside of outer_figure or figures intersect.
for (unsigned int k = outer_dex + 1; k < outer_count; k++)
{
const ON_OutlineFigure* f = outer_figures[k];
if (nullptr == f)
continue;
const double f_area = fabs(f->AreaEstimate());
if (false == (inner_area < f_area))
continue;
const ON_BoundingBox f_bbox = f->BoundingBox();
if (false == (f_bbox.Includes(inner_bbox)))
continue;
// f is an outer figure and based on area and bounding box,
// inner_figure could be inside of f as well.
// Use winding number tests to determine if outer_figure or f is a better choice.
const int wn1 = winding_number_sign * f->WindingNumber(inner_figure->m_points[0].m_point);
if (wn1 > 0)
{
// Starting point of inner figure is inside f.
if (wn0 <= 0)
{
// Setting inner_figure = nullptr prevents it from being assigned to outer_figure.
inner_figure = nullptr;
break;
}
// Starting point of inner_figure is inside f and inside outer_figure.
if (f_area < outer_area && outer_bbox.Includes(f_bbox))
{
// Outer_figure is larger than f, so f is a better choice for this inner_figure.
// Setting inner_figure = nullptr prevents it from being assigned to outer_figure.
inner_figure = nullptr;
break;
}
if (outer_area < f_area && f_bbox.Includes(outer_bbox))
{
// f is larger than outer_figure, so outer_figure is a better choice than f.
// continue testing other outer figures
continue;
}
// If we get here, either some of the figures (outer_figure, inner_figure, f)
// intersect or there is a later element in outer_figures[] that will be the
// best choice.
}
else
{
// Starting point of inner figure is not inside f.
if (wn0 > 0)
{
// Starting point of inner figure is inside outer_figure and not inside f.
// outer_figure is a better choice.
continue;
}
}
// Ambiguous case and if the outline is valid there is another outer figure
// that will be better choice. The following hueristics can prevent
// further searching if f is more likely than outer_figure.
if (outer_bbox.Includes(f_bbox))
{
// f is a smaller outer figure so we will prefer it.
// Setting inner_figure = nullptr prevents it from being assigned to outer_figure.
inner_figure = nullptr;
break;
}
if (
f->m_figure_index > outer_figure->m_figure_index
&& inner_figure->m_figure_index > f->m_figure_index
)
{
// The order of the figures in the source information is
// ... outer_figure, ..., f, ..., inner_figure.
// In general, this indicates f is a better guess that
// outer_figure. Setting inner_figure = nullptr here
// prevents it from being assigned to outer_figure.
// When we got around to testing f, we may find an
// outer_figure[index > k] that is an even better candidate
// than f.
inner_figure = nullptr;
break;
}
}
if (nullptr == inner_figure)
{
// There are other out figures that are better candidates
// for holding inner_figure.
continue;
}
// inner figure is inside outer_figure
inner_figures[inner_dex] = nullptr;
sorted_figures.Append(inner_figure);
}
}
if (sorted_figures.UnsignedCount() == outer_and_inner_count)
break; // finished
// typically we end up here when an outer figure is incorrect oriented
// ComicSans U+0048 (+/- glpyh) is one example of many. In ComicSans U+0048
// the + and - figures are disjoint and have opposite orientations.
// Convert the largest unused inner figure to an outer figure.
if (sorted_figures.UnsignedCount() <= sorted_figures_count0)
break; // no progress made
if (inner_count <= 0)
break; // nothing left to try.
unsigned int largest_inner_area_index = ON_UNSET_UINT_INDEX;
double largest_inner_area = 0.0;
for (unsigned int inner_dex = 0; inner_dex < inner_count; inner_dex++)
{
ON_OutlineFigure* inner_figure = inner_figures[inner_dex];
if (nullptr == inner_figure)
continue; // inner_figures[inner_dex] assigned to previous outer figure
const double inner_area = fabs(inner_figure->AreaEstimate());
if (inner_area > largest_inner_area)
{
largest_inner_area = inner_area;
largest_inner_area_index = inner_dex;
}
}
if (ON_UNSET_UINT_INDEX == largest_inner_area_index)
break;
ON_OutlineFigure* new_outer_figure = inner_figures[largest_inner_area_index];
if (nullptr == new_outer_figure)
break;
// convert new_outer_figure to an outer figure and continue sorting.
new_outer_figure->ReverseFigure();
if (largest_inner_area_index+1 < inner_count)
{
inner_figures[largest_inner_area_index] = inner_figures[inner_count - 1];
inner_figures[inner_count - 1] = nullptr;
}
inner_figures.SetCount(inner_count - 1);
outer_figures.Append(new_outer_figure);
outer_count = outer_figures.UnsignedCount();
inner_count = inner_figures.UnsignedCount();
}
if (sorted_figures.UnsignedCount() != outer_and_inner_count)
{
// unable to sort outer and inner figures.
// Input is probably not valid.
// Use original order.
ON_ERROR("Unable to sort outer and inner figures.");
m_sorted_figure_outer_orientation = ON_OutlineFigure::Orientation::Error;
return;
}
// Put single stroke and other non-perimeter figures on the end.
for (unsigned int k = 0; k < ignored_figures.UnsignedCount(); k++)
{
ON_OutlineFigure* f = ignored_figures[k];
if (nullptr != f)
sorted_figures.Append(f);
}
if (sorted_figures.Count() == figure_count)
{
for (int k = 0; k < figure_count; k++)
{
if (sorted_figures[k] != &m_figures[k])
{
// Need to reorder m_figures[] array
ON_ClassArray<ON_OutlineFigure> tmp(figure_count);
for (int i = 0; i < figure_count; i++)
{
tmp.Append(*sorted_figures[i]);
}
m_figures = tmp;
break;
}
}
}
return;
}
ON_OutlineFigure::Orientation ON_Outline::SortedFigureOuterOrientation() const
{
return m_sorted_figure_outer_orientation;
}
ON_OutlineFigure::Orientation ON_Outline::SortedFigureInnerOrientation() const
{
const ON_OutlineFigure::Orientation outer_orientation = SortedFigureOuterOrientation();
if (ON_OutlineFigure::Orientation::CounterClockwise == outer_orientation)
return ON_OutlineFigure::Orientation::Clockwise;
if (ON_OutlineFigure::Orientation::Clockwise == outer_orientation)
return ON_OutlineFigure::Orientation::CounterClockwise;
return outer_orientation;
}
const ON_TextBox ON_Outline::GlyphMetrics() const
{
return m_glyph_metrics;
}
const ON_OutlineFigurePoint ON_OutlineAccumulator::CurrentFigureStartPoint() const
{
return m_figure_start;
}
const ON_OutlineFigurePoint ON_OutlineAccumulator::CurrentFigurePreviousPoint() const
{
return m_figure_prev;
}
const ON_OutlineFigurePoint ON_OutlineAccumulator::CurrentFigurePoint() const
{
return m_figure_current;
}
const bool ON_OutlineAccumulator::CurrentFigureAccumulating() const
{
return Internal_InFigure();
}
ON__UINT32 ON_OutlineAccumulator::UnitsPerEM() const
{
return m_units_per_em;
}
ON__UINT32 ON_Outline::UnitsPerEM() const
{
return m_units_per_em;
}
void ON_Outline::SetUnitsPerEM(
ON__UINT32 units_per_em
)
{
m_units_per_em = units_per_em;
if (m_units_per_em > 0)
m_short_tolerance = (m_units_per_em > 0.0) ? (m_units_per_em / 2056.0) : 0.0;
}
ON_OutlineFigure::Type ON_OutlineAccumulator::FigureType() const
{
return m_figure_type;
}
bool ON_OutlineAccumulator::IsInitialized() const
{
return (1 == m_status);
}
bool ON_OutlineAccumulator::IsFinalized() const
{
return (2 == m_status);
}
bool ON_OutlineAccumulator::IsInitializedOrFinalized() const
{
return IsInitialized() || IsFinalized();
}
ON_Outline* ON_OutlineAccumulator::HarvestOutline()
{
ON_Outline* outline = m_outline;
m_outline = nullptr;
if (outline == m_managed_outline)
m_managed_outline = nullptr;
Clear();
return outline;
}
void ON_OutlineAccumulator::Clear()
{
if (m_managed_outline)
{
if (m_outline == m_managed_outline)
m_outline = nullptr;
delete m_managed_outline;
m_managed_outline = nullptr;
}
if ( nullptr != m_outline)
*m_outline = ON_Outline::Unset;
m_units_per_em = 0;
m_status = 0;
m_figure_type = ON_OutlineFigure::Type::Unset;
m_figure_depth = 0;
m_error_count = 0;
AbandonCurrentFigure();
}
bool ON_OutlineAccumulator::BeginGlyphOutline(
ON__UINT32 font_units_per_em,
ON_OutlineFigure::Type figure_type,
ON_Outline* destination_outline
)
{
Clear();
m_units_per_em = font_units_per_em;
m_status = (m_units_per_em > 0) ? 1 : 0;
m_figure_type = figure_type;
if (nullptr == destination_outline)
{
m_managed_outline = new ON_Outline();
m_outline = m_managed_outline;
}
else
{
*destination_outline = ON_Outline::Unset;
m_outline = destination_outline;
}
m_outline->SetUnitsPerEM(font_units_per_em);
m_outline->m_figure_type = figure_type;
return (1 == m_status);
}
bool ON_OutlineFigure::Internal_HasValidEnds(
bool bLogErrors
) const
{
if (m_points.UnsignedCount() >= 2
&& m_points[0].IsBeginFigurePoint()
&& m_points.Last()->IsEndFigurePoint()
)
return true;
if (bLogErrors)
{
ON_ERROR("Invalid figure start or end information.");
}
return false;
}
static void Internal_GrowBBox(
const ON_2fPoint& pmin,
const ON_2fPoint& pmax,
ON_2fPoint& bbox_min,
ON_2fPoint& bbox_max
)
{
if (pmin.x < bbox_min.x) bbox_min.x = pmin.x; else if (pmax.x > bbox_max.x) bbox_max.x = pmax.x;
if (pmin.y < bbox_min.y) bbox_min.y = pmin.y; else if (pmax.y > bbox_max.y) bbox_max.y = pmax.y;
}
const ON_BoundingBox ON_OutlineFigure::BoundingBox() const
{
while (0 == m_bbox_status)
{
m_bbox_status = 7;
const ON__UINT32 figure_end_dex = Internal_FigureEndDex(false);
if (0 == figure_end_dex)
break;
const ON_OutlineFigurePoint* a = m_points.Array();
ON_2fPoint bbox_min = a[0].m_point;
ON_2fPoint bbox_max = bbox_min;
ON_2fPoint bbox_cv_min = bbox_min;
ON_2fPoint bbox_cv_max = bbox_max;
for (ON__UINT32 i = 0; i <= figure_end_dex; i++)
{
const ON__UINT32 degree = ON_OutlineFigure::Internal_SegmentDegree(i);
if (0 == degree)
continue;
ON_OutlineFigurePoint p = a[i];
if (false == p.IsOnFigure())
continue;
Internal_GrowBBox(p.m_point, p.m_point, bbox_min, bbox_max);
if (degree < 2)
continue;
p = a[++i];
Internal_GrowBBox(p.m_point, p.m_point, bbox_cv_min, bbox_cv_max);
if (3 == degree)
{
p = a[++i];
Internal_GrowBBox(p.m_point, p.m_point, bbox_cv_min, bbox_cv_max);
}
}
if (
bbox_cv_min.x >= bbox_min.x
&& bbox_cv_max.x <= bbox_max.x
&& bbox_cv_min.y >= bbox_min.y
&& bbox_cv_max.y <= bbox_max.y
)
{
// If there were any bezier segments, their control points were inside the bounding box of the segment end points.
m_bbox_min = bbox_min;
m_bbox_max = bbox_max;
m_bbox_status = 1;
break;
}
// get bezeir bounding boxes as needed
ON_OutlineFigurePoint cv[4];
cv[3] = a[0]; // to suppress potintial uninitialized memory warning
for (ON__UINT32 i = 0; i <= figure_end_dex; i++)
{
const ON__UINT32 degree = ON_OutlineFigure::Internal_SegmentDegree(i);
if (degree < 1 || degree > 3)
continue;
cv[0] = a[i++];
cv[1] = a[i];
cv[2] = a[i+1];
bbox_cv_min = cv[1].m_point;
bbox_cv_max = bbox_cv_min;
if (3 == degree)
{
Internal_GrowBBox(cv[2].m_point, cv[2].m_point, bbox_cv_min, bbox_cv_max);
i++;
cv[3] = a[i+1];
}
if (
bbox_cv_min.x >= bbox_min.x
&& bbox_cv_max.x <= bbox_max.x
&& bbox_cv_min.y >= bbox_min.y
&& bbox_cv_max.y <= bbox_max.y
)
{
// the control points of this bezier are inside the current bounding box.
continue;
}
// get tight bounding box of this bezier
ON_BezierCurve bez(3, false, degree + 1);
for (ON__UINT32 j = 0; j <= degree; j++)
bez.SetCV(j, ON_3dPoint(cv[j].m_point.x, cv[j].m_point.y, 0.0));
const ON_BoundingBox bez_bbox = bez.BoundingBox();
if (bez_bbox.IsNotEmpty())
{
const double minf = ON_UNSET_FLOAT;
const double maxf = ON_UNSET_POSITIVE_FLOAT;
if (
minf < bez_bbox.m_min.x
&& bez_bbox.m_max.x < maxf
&& minf < bez_bbox.m_min.y
&& bez_bbox.m_max.y < maxf
)
{
bbox_cv_min.Set((float)bez_bbox.m_min.x, (float)bez_bbox.m_min.y);
bbox_cv_max.Set((float)bez_bbox.m_max.x, (float)bez_bbox.m_max.y);
}
}
Internal_GrowBBox(bbox_cv_min, bbox_cv_max, bbox_cv_min, bbox_cv_max);
}
m_bbox_min = bbox_min;
m_bbox_max = bbox_max;
m_bbox_status = 1;
if (m_bbox_min == m_bbox_max)
{
if (
ON_OutlineFigure::Type::Perimeter == m_figure_type
|| ON_OutlineFigure::Type::Unset == m_figure_type
|| ON_OutlineFigure::Type::Unknown == m_figure_type
)
{
m_figure_type = ON_OutlineFigure::Type::NotPerimeter;
}
}
break;
}
if (1 == m_bbox_status)
return ON_BoundingBox(ON_3dPoint(m_bbox_min), ON_3dPoint(m_bbox_max));
return ON_BoundingBox::NanBoundingBox;
}
bool ON_OutlineFigure::Internal_NegateY( ON_2fPoint& p )
{
if (0.0 != p.y && p.y > ON_UNSET_FLOAT && p.y < ON_UNSET_POSITIVE_FLOAT)
{
p.y = -p.y;
return true;
}
return false;
}
bool ON_OutlineFigure::NegateY()
{
bool rc = false;
const ON__UINT32 count = m_points.UnsignedCount();
ON_OutlineFigurePoint* a = m_points.Array();
for (ON__UINT32 i = 0; i < count; i++)
{
if ( ON_OutlineFigure::Internal_NegateY(a[i].m_point) )
rc = true;
}
if (rc)
{
if (ON_OutlineFigure::Orientation::Clockwise == m_orientation)
m_orientation = ON_OutlineFigure::Orientation::CounterClockwise;
else if (ON_OutlineFigure::Orientation::CounterClockwise == m_orientation)
m_orientation = ON_OutlineFigure::Orientation::Clockwise;
if (ON_IsValid(m_area_estimate) && 0.0 != m_area_estimate)
m_area_estimate = -m_area_estimate;
}
return rc;
}
static double Internal_DeltaArea(
const ON_2dPoint& p0,
const ON_2dPoint& p1
)
{
return (p0.x - p1.x)*(p0.y + p1.y);
}
ON_OutlineFigure::Type ON_OutlineFigure::FigureType() const
{
return m_figure_type;
}
ON__UINT16 ON_OutlineFigure::FigureIndex() const
{
return m_figure_index;
}
double ON_OutlineFigure::BoxArea() const
{
const ON_BoundingBox bbox = BoundingBox();
double bbox_area = 0.0;
if (bbox.IsNotEmpty())
{
const double dx = bbox.m_max.x - bbox.m_min.x;
const double dy = bbox.m_max.y - bbox.m_min.y;
bbox_area = (dx >= 0.0 && dy >= 0.0) ? (dx*dy) : 0.0;
}
else
{
bbox_area = 0.0;
}
return bbox_area;
}
ON__UINT32 ON_OutlineFigure::UnitsPerEM() const
{
return m_units_per_em;
}
double ON_OutlineFigure::AreaEstimate() const
{
while (0 == m_area_status)
{
m_area_estimate = ON_DBL_QNAN;
m_area_status = 7;
const ON__UINT32 figure_end_dex = Internal_FigureEndDex(false);
if (0 == figure_end_dex)
break;
const ON_OutlineFigurePoint* a = m_points.Array();
const ON_OutlineFigurePoint figure_start = a[0];
const ON_OutlineFigurePoint figure_end = a[figure_end_dex];
if (
ON_OutlineFigurePoint::Type::BeginFigureOpen == figure_start.m_point_type
|| ON_OutlineFigurePoint::Type::EndFigureOpen == figure_end.m_point_type
|| figure_end_dex <= 2
)
{
m_area_estimate = 0.0;
m_area_status = 1;
if (
ON_OutlineFigure::Type::Perimeter == m_figure_type
|| ON_OutlineFigure::Type::Unset == m_figure_type
|| ON_OutlineFigure::Type::Unknown == m_figure_type
)
{
m_figure_type = ON_OutlineFigure::Type::NotPerimeter;
}
break;
}
ON_2dPoint cv[4], p0, p1;
double quad[3][3];
double cubic[3][4];
quad[0][0] = ON_UNSET_VALUE;
cubic[0][0] = ON_UNSET_VALUE;
double twice_area = 0.0;
p1 = figure_start.m_point;
ON__UINT32 i;
for ( i = 1; i < figure_end_dex; i++)
{
const ON_OutlineFigurePoint p = a[i];
p0 = p1;
p1 = p.m_point;
if (ON_OutlineFigurePoint::Type::LineTo == p.m_point_type)
{
twice_area += Internal_DeltaArea(p0, p1);
continue;
}
if (ON_OutlineFigurePoint::Type::QuadraticBezierPoint == p.m_point_type)
{
if (
p.IsOffFigure()
&& i + 1 < figure_end_dex
&& p.m_point_type == a[i + 1].m_point_type
&& a[i + 1].IsOnFigure()
)
{
// approximate quadratic with 4 line segments
if (ON_UNSET_VALUE == quad[0][0])
{
for (int tdex = 1; tdex <= 3; tdex++)
{
const double t = 0.25 * tdex;
//const double s = 1.0 - t;
for (int cvdex = 0; cvdex < 3; cvdex++)
{
quad[tdex - 1][cvdex] = ON_EvaluateBernsteinBasis(2, cvdex, t);
}
}
}
cv[0] = p0;
cv[1] = p1;
cv[2] = a[++i].m_point;
p1 = p0;
for (int j = 0; j < 3; j++)
{
p0 = p1;
p1 = quad[j][0] * cv[0] + quad[j][1] * cv[1] + quad[j][2] * cv[2];
twice_area += Internal_DeltaArea(p0, p1);
}
p0 = p1;
p1 = cv[2];
twice_area += Internal_DeltaArea(p0, p1);
continue;
}
// error
break;
}
if (ON_OutlineFigurePoint::Type::CubicBezierPoint == p.m_point_type)
{
if (
// approximate cubic with 4 line segments
p.IsOffFigure()
&& i + 2 < figure_end_dex
&& p.m_point_type == a[i + 1].m_point_type
&& a[i + 1].IsOffFigure()
&& p.m_point_type == a[i + 2].m_point_type
&& a[i + 2].IsOnFigure()
)
{
if (ON_UNSET_VALUE == cubic[0][0])
{
for (int tdex = 1; tdex <= 3; tdex++)
{
const double t = 0.25 * tdex;
//const double s = 1.0 - t;
for (int cvdex = 0; cvdex < 4; cvdex++)
{
cubic[tdex - 1][cvdex] = ON_EvaluateBernsteinBasis(3, cvdex, t);
}
}
}
cv[0] = p0;
cv[1] = p1;
cv[2] = a[++i].m_point;
cv[3] = a[++i].m_point;
p1 = p0;
for (int j = 0; j < 3; j++)
{
p0 = p1;
p1 = cubic[j][0] * cv[0] + cubic[j][1] * cv[1] + cubic[j][2] * cv[2] + cubic[j][3] * cv[3];
twice_area += Internal_DeltaArea(p0, p1);
}
p0 = p1;
p1 = cv[3];
twice_area += Internal_DeltaArea(p0, p1);
continue;
}
// error
break;
}
break;
}
if (i != figure_end_dex)
{
ON_ERROR("Area calculation failed.");
if (
ON_OutlineFigure::Type::Perimeter == m_figure_type
|| ON_OutlineFigure::Type::Unset == m_figure_type
|| ON_OutlineFigure::Type::Unknown == m_figure_type
)
{
m_figure_type = ON_OutlineFigure::Type::NotPerimeter;
}
break; // error
}
p0 = p1;
p1 = figure_start.m_point;
twice_area += Internal_DeltaArea(p0, p1);
m_area_estimate = 0.5*twice_area;
m_area_status = 1;
break;
}
return m_area_estimate;
}
bool ON_OutlineFigure::ReverseFigure()
{
const bool error_rc = false;
const ON__UINT32 figure_end_dex = Internal_FigureEndDex(true);
if (0 == figure_end_dex)
return error_rc;
const ON__UINT32 figure_start_dex = 0;
ON_OutlineFigurePoint* a = m_points.Array();
// Original beginning point
const ON_OutlineFigurePoint B0 = a[figure_start_dex];
// Original ending point
const ON_OutlineFigurePoint E0 = a[figure_end_dex];
for (ON__UINT32 i = figure_start_dex+1; i < figure_end_dex; i++)
{
if ( false == a[i].IsInteriorFigurePoint() )
{
// a[] containts errors, unsets, or embedded figures
return error_rc;
}
}
// New beginning point
ON_OutlineFigurePoint B1 = a[figure_end_dex-1];
B1.m_point_type = B0.m_point_type;
// New ending point
ON_OutlineFigurePoint E1 = B1;
E1.m_point_type = E0.m_point_type;
// shift internal type flags to account for segment reversal
for (ON__UINT32 i = figure_start_dex+1; i < figure_end_dex; i++)
{
a[i - 1].m_point_type = a[i].m_point_type;
}
// reverse a[] order
ON__UINT32 j = figure_end_dex-1;
for (ON__UINT32 i = figure_start_dex; i < j; i++, j--)
{
ON_OutlineFigurePoint x = a[i];
a[i] = a[j];
a[j] = x;
}
// set new begin/end
a[figure_start_dex] = B1;
a[figure_end_dex] = E1;
if (ON_OutlineFigure::Orientation::Clockwise == m_orientation)
m_orientation = ON_OutlineFigure::Orientation::CounterClockwise;
else if (ON_OutlineFigure::Orientation::CounterClockwise == m_orientation)
m_orientation = ON_OutlineFigure::Orientation::Clockwise;
if (ON_IsValid(m_area_estimate) && 0.0 != m_area_estimate)
m_area_estimate = -m_area_estimate;
return true;
}
unsigned int ON_OutlineFigure::Internal_SegmentDegree(
ON__UINT32 segment_start_dex
) const
{
const ON__UINT32 count = m_points.UnsignedCount();
if (count < 2 || segment_start_dex > count-2)
return 0;
const ON_OutlineFigurePoint* a = m_points.Array();
if (nullptr == a)
return 0;
const ON_OutlineFigurePoint p0 = a[segment_start_dex];
if (false == p0.IsOnFigure())
return 0;
const ON_OutlineFigurePoint p1 = a[segment_start_dex+1];
switch (p1.m_point_proximity)
{
case ON_OutlineFigurePoint::Proximity::OnFigure:
if (ON_OutlineFigurePoint::Type::LineTo == p1.m_point_type || p1.IsEndFigurePoint())
return 1;
break;
case ON_OutlineFigurePoint::Proximity::OffFigure:
if (segment_start_dex + 2 < count)
{
const ON_OutlineFigurePoint p2 = a[segment_start_dex + 2];
if (p2.m_point_type != p1.m_point_type || p2.m_figure_index != p1.m_figure_index)
break;
if (ON_OutlineFigurePoint::Type::QuadraticBezierPoint == p1.m_point_type)
{
if (p2.IsOnFigure())
return 2;
}
else if (ON_OutlineFigurePoint::Type::CubicBezierPoint == p1.m_point_type)
{
if (false == p2.IsOffFigure())
break;
if (segment_start_dex + 3 >= count)
break;
const ON_OutlineFigurePoint p3 = a[segment_start_dex + 3];
if (p3.m_point_type == p1.m_point_type && p3.IsOnFigure())
return 3;
}
}
break;
}
return 0;
}
ON_OutlineFigure::Orientation ON_OutlineFigure::FigureOrientation() const
{
if (ON_OutlineFigure::Orientation::Unset == m_orientation)
{
const double area_estimate = AreaEstimate();
const double area_tolerance = (m_short_tolerance > 0.0) ? (m_short_tolerance*m_short_tolerance) : 1e-10;
if (fabs(area_estimate) <= area_tolerance)
m_orientation = ON_OutlineFigure::Orientation::NotOriented;
else if (area_estimate > 0.0)
m_orientation = ON_OutlineFigure::Orientation::CounterClockwise;
else if (area_estimate < 0.0)
m_orientation = ON_OutlineFigure::Orientation::Clockwise;
else
m_orientation = ON_OutlineFigure::Orientation::Error;
}
return m_orientation;
}
unsigned int ON_OutlineFigure::GetFigureCurves(
double scale,
bool b3d,
ON_SimpleArray< ON_Curve* >& figure_curves
) const
{
ON_SimpleArray< ON_NurbsCurve* >& figure_nurbs_curves = *((ON_SimpleArray< ON_NurbsCurve* > *)&figure_curves);
return GetFigureCurves(
scale,
b3d,
figure_nurbs_curves
);
}
unsigned int ON_OutlineFigure::GetFigureCurves(
double scale,
bool b3d,
ON_SimpleArray< ON_NurbsCurve* >& figure_curves
) const
{
if (false == Internal_HasValidEnds(false))
return 0;
const unsigned int figure_curves_count0 = figure_curves.UnsignedCount();
const bool bApplyScale = ON_IsValid(scale) && scale > 0.0 && fabs(scale - 1.0) > 1.0e-5;
if (false == bApplyScale)
scale = 1.0;
const ON__UINT32 figure_start_dex = 0;
const ON__UINT32 figure_end_dex = m_points.UnsignedCount() - 1;
//const ON_OutlineFigurePoint figure_start = m_points[figure_start_dex];
//const ON_OutlineFigurePoint figure_end = m_points[figure_end_dex];
// Find the end of this figure curve and estimate its NURBS curve segment_count.
//bool bClosedFigure
// = ON_OutlineFigurePoint::Type::EndFigureOpen != figure_end.m_point_type
// && figure_end.IsOnFigure()
// && figure_start.m_point == figure_end.m_point
// ;
// estimate number of NURBS curve segments
figure_curves.Reserve(Internal_EstimateFigureSegmentCount());
// get NURBS curve segments
ON__UINT32 segment_start_dex = figure_start_dex;
while (segment_start_dex < figure_end_dex)
{
ON__UINT32 i = 0;
ON_NurbsCurve* figures_segment = Internal_GetFigureCurve(
figure_end_dex,
segment_start_dex,
&i,
b3d,
nullptr
);
if (i <= segment_start_dex)
{
ON_ERROR("Failed to parse a figure segment.");
//bClosedFigure = false;
break;
}
if (nullptr != figures_segment)
{
if (bApplyScale)
figures_segment->Scale(scale);
figure_curves.Append(figures_segment);
}
//else
//{
// bClosedFigure = false;
//}
segment_start_dex = i;
}
return (figure_curves.UnsignedCount() - figure_curves_count0);
}
void ON_Outline::Internal_ClearCachedValues() const
{
m_bbox_status = 0;
m_bbox = ON_BoundingBox::NanBoundingBox;
}
ON__UINT16 ON_Outline::AppendFigure(
const ON_SimpleArray<ON_OutlineFigurePoint>& points
)
{
return AppendFigure(points.UnsignedCount(), points.Array());
}
ON__UINT16 ON_Outline::AppendFigure(
size_t point_count,
const ON_OutlineFigurePoint* points
)
{
const bool bSkipPointFigures = true;
return Internal_AppendFigure(point_count, points, 0.0, bSkipPointFigures);
}
void ON_Outline::SetGlyphMetrics(
ON_TextBox glyph_metrics
)
{
m_glyph_metrics = glyph_metrics;
}
ON__UINT16 ON_Outline::Internal_AppendFigure(
size_t point_count_as_size_t,
const ON_OutlineFigurePoint* points,
double short_tolerance,
bool bSkipPointFigures
)
{
if (0 == point_count_as_size_t)
return 0;
const unsigned int point_count = (unsigned int)point_count_as_size_t;
if (point_count < 2 || nullptr == points)
{
ON_ERROR("Invalid point_count or points[] array.");
return 0;
}
const ON_OutlineFigurePoint figure_start = points[0];
if (
false == figure_start.IsBeginFigurePoint()
|| false == figure_start.IsOnFigure()
|| false == figure_start.m_point.IsValid()
)
{
ON_ERROR("points[0] is not a valid BeginFigure point.");
return 0;
}
const ON_OutlineFigurePoint figure_end = points[point_count-1];
if (
false == figure_end.IsEndFigurePoint()
|| false == figure_end.IsOnFigure()
|| false == figure_end.m_point.IsValid()
)
{
ON_ERROR("points[point_count-1] is not a valid EndFigure point.");
return 0;
}
if (bSkipPointFigures && 2 == point_count && figure_start.m_point == figure_end.m_point)
return 0;
const bool bDoubleStrokeReductionTest
= ON_OutlineFigure::Type::DoubleStroke == m_figure_type
&& point_count >= 3
&& ON_OutlineFigurePoint::Type::BeginFigureClosed == figure_start.PointType()
&& ON_OutlineFigurePoint::Type::EndFigureClosed == figure_end.PointType()
&& figure_start.m_point == figure_end.m_point;
while (
bDoubleStrokeReductionTest
&& point_count >= 4
&& m_figures.UnsignedCount() > 0
)
{
ON_OutlineFigure& prev_figure = m_figures[m_figures.UnsignedCount() - 1];
if (ON_OutlineFigure::Type::DoubleStroke != prev_figure.m_figure_type)
break;
if (point_count != (prev_figure.m_points.UnsignedCount()))
break;
const ON_OutlineFigurePoint prev_figure_start = prev_figure.m_points[0];
const ON_OutlineFigurePoint prev_figure_end = prev_figure.m_points[point_count-1];
if (ON_OutlineFigurePoint::Type::BeginFigureClosed != prev_figure_start.PointType())
break;
if (ON_OutlineFigurePoint::Type::EndFigureClosed != prev_figure_end.PointType())
break;
if (false == (prev_figure_start.m_point == figure_start.m_point) )
break;
if (false == (prev_figure_end.m_point == figure_start.m_point) )
break;
for (int dup_check_pass = 0; dup_check_pass < 2; dup_check_pass++)
{
bool bDuplicateLoop = true;
for (unsigned int i = 0; i < point_count; i++)
{
const unsigned int j = (0 == dup_check_pass) ? i : (point_count - i - 1);
const ON_OutlineFigurePoint& prev_point = prev_figure.m_points[j];
bDuplicateLoop
= (prev_point.IsOnFigure() == points[i].IsOnFigure() || prev_point.PointType() == points[i].PointType())
&& prev_point.m_point == points[i].m_point;
if (false == bDuplicateLoop)
break;
}
if (false == bDuplicateLoop)
continue;
// This figure is the double stroke duplicate of the previous figure (double stroke glphys like O).
prev_figure.m_points[0].m_point_type = ON_OutlineFigurePoint::Type::BeginFigureOpen;
if (
prev_figure.m_points[point_count - 2].IsOnFigure()
&& false == (prev_figure_end.m_point == prev_figure.m_points[point_count - 2].m_point)
)
{
ON_OutlineFigurePoint& new_figure_end = prev_figure.m_points.AppendNew();
ON_OutlineFigurePoint& p = prev_figure.m_points[prev_figure.m_points.UnsignedCount() - 2];
p.m_point_type = ON_OutlineFigurePoint::Type::LineTo;
new_figure_end = p;
new_figure_end.m_point_type = ON_OutlineFigurePoint::Type::EndFigureOpen;
}
prev_figure.m_figure_type = ON_OutlineFigure::Type::SingleStroke;
return prev_figure.FigureIndex();
}
break;
}
ON_OutlineFigure& figure = m_figures.AppendNew();
figure.m_figure_index = (ON__UINT16)m_figures.UnsignedCount();
figure.m_short_tolerance = (short_tolerance>0.0) ? short_tolerance : m_short_tolerance;
figure.m_points.Append((int)point_count, points);
figure.m_points[0] = figure_start;
figure.m_figure_type = m_figure_type;
figure.m_units_per_em = this->m_units_per_em;
while (bDoubleStrokeReductionTest)
{
// check for a double stroke figure like those in CamBam Stick fonts
unsigned int i0 = 0;
ON_2fPoint Qpoint(ON_2fPoint::NanPoint);
bool bPrependQseg = false;
unsigned int i1 = (unsigned int)(point_count - 1);
if (i1 - 2 > 0)
{
if (
false == figure.m_points[i1 - 2].IsOnFigure()
&& figure.m_points[i1 - 1].IsOnFigure()
&& figure.m_points[i1 - 1].m_point == figure_end.m_point
)
{
i1--;
}
else if (
figure.m_points[i1 - 2].IsOnFigure()
&& figure.m_points[i1 - 2].m_point == figure_start.m_point
&& figure.m_points[i1 - 1].IsOnFigure()
&& ON_OutlineFigurePoint::Type::LineTo == figure.m_points[i1 - 1].PointType()
)
{
Qpoint = figure.m_points[i1 - 1].m_point;
if (false == (Qpoint == figure_end.m_point))
bPrependQseg = true;
i1 -= 2;
}
}
if (i0 >= i1)
break;
if (0 != (i1 - i0) % 2)
break;
while (i0 < i1)
{
if (false == (figure.m_points[i0].m_point == figure.m_points[i1].m_point))
break;
if (false == figure.m_points[i0].IsOnFigure() || false == figure.m_points[i1].IsOnFigure())
break;
i0++;
i1--;
while (
i0 < i1
&& false == figure.m_points[i0].IsOnFigure()
&& false == figure.m_points[i1].IsOnFigure()
&& figure.m_points[i0].PointType() == figure.m_points[i1].PointType()
&& figure.m_points[i0].m_point == figure.m_points[i1].m_point
)
{
i0++;
i1--;
}
}
if (i0 != i1)
break;
if (false == figure.m_points[i1].IsOnFigure())
break;
if (figure.m_points[i1].IsEndFigurePoint())
break;
if ((size_t)(i1 + 2) > point_count)
break;
if ( bPrependQseg )
{
if ((i1 + 3) > point_count)
break;
for (unsigned int i = i1 + 1; i > 0; i--)
figure.m_points[i] = figure.m_points[i - 1];
figure.m_points[0].m_point = Qpoint;
figure.m_points[1].m_point_type = ON_OutlineFigurePoint::Type::LineTo;
i1++;
}
figure.m_points[0].m_point_type = ON_OutlineFigurePoint::Type::BeginFigureOpen;
figure.m_points[i1 + 1].m_point = figure.m_points[i1].m_point;
figure.m_points[i1 + 1].m_point_type = ON_OutlineFigurePoint::Type::EndFigureOpen;
figure.m_points[i1 + 1].m_point_proximity = ON_OutlineFigurePoint::Proximity::OnFigure;
figure.m_figure_type = ON_OutlineFigure::Type::SingleStroke;
figure.m_points.SetCount(i1 + 2);
break;
}
m_sorted_figure_outer_orientation = ON_OutlineFigure::Orientation::Unset;
return figure.m_figure_index;
}
ON_OutlineFigure::Type ON_Outline::FigureType() const
{
return m_figure_type;
}
unsigned int ON_Outline::GetOutlineCurves(
double scale,
bool b3d,
ON_ClassArray< ON_SimpleArray< ON_Curve* > >& outline_curves
)
const
{
const ON__UINT32 outline_curves_count0 = outline_curves.UnsignedCount();
const ON__UINT32 figure_count = m_figures.UnsignedCount();
if (figure_count <= 0)
return 0;
outline_curves.Reserve(outline_curves.UnsignedCount() + figure_count);
for ( ON__UINT32 i = 0; i <figure_count; i++ )
{
ON_SimpleArray< ON_Curve* >& figure_curves = outline_curves.AppendNew();
m_figures[i].GetFigureCurves(scale, b3d, figure_curves);
if (figure_curves.UnsignedCount() <= 0)
{
figure_curves.Remove();
continue;
}
}
return outline_curves.UnsignedCount() - outline_curves_count0;
}
ON__UINT32 ON_OutlineFigure::Internal_FigureEndDex(
bool bLogErrors
) const
{
const ON__UINT32 count = m_points.UnsignedCount();
if (
count >= 2
&& m_points[0].IsBeginFigurePoint()
&& m_points[count - 1].IsEndFigurePoint()
)
{
return (count - 1);
}
if (bLogErrors)
{
ON_ERROR("Figure is missing start or end point.");
}
return 0;
}
ON__UINT32 ON_OutlineFigure::Internal_EstimateFigureSegmentCount() const
{
ON__UINT32 figure_end_dex = Internal_FigureEndDex(false);
const ON__UINT32 count = m_points.UnsignedCount();
if (0 == figure_end_dex || figure_end_dex >= count)
return 0;
ON_OutlineFigurePoint::Type point_type = ON_OutlineFigurePoint::Type::Unset;
const ON_OutlineFigurePoint* a = m_points.Array();
const ON_OutlineFigurePoint figure_end = a[figure_end_dex];
if (
figure_end.IsOnFigure()
&& ON_OutlineFigurePoint::Type::EndFigureClosed == figure_end.m_point_type
&& 0 != a[figure_end_dex - 1].IsOnFigure()
&& a[figure_end_dex - 1].m_point == figure_end.m_point
)
{
// ignore final zero length segment
figure_end_dex--;
}
int segment_count = 0;
for (ON__UINT32 i = 1; i <= figure_end_dex; i++)
{
if (false == a[i].IsOnFigure() || a[i].m_point_type == point_type)
continue;
point_type = a[i].m_point_type;
segment_count++;
}
return segment_count;
}
class ON_NurbsCurve* ON_OutlineFigure::Internal_GetFigureCurve(
ON__UINT32 figure_end_dex,
ON__UINT32 segment_start_dex,
ON__UINT32* segment_end_dex,
bool b3d,
class ON_NurbsCurve* destination_curve
) const
{
ON_NurbsCurve* nurbs_curve = destination_curve;
if (nullptr != nurbs_curve)
{
nurbs_curve->m_dim = 0;
nurbs_curve->m_is_rat = 0;
nurbs_curve->m_order = 0;
nurbs_curve->m_cv_count = 0;
}
*segment_end_dex = ON_UNSET_UINT_INDEX;
if (
segment_start_dex >= figure_end_dex
|| figure_end_dex >= m_points.UnsignedCount()
|| (figure_end_dex - segment_start_dex) < 1
)
{
ON_ERROR("Invalid start and end indices.");
return nullptr;
}
const ON_OutlineFigurePoint* a = m_points.Array();
if (nullptr == a)
return nullptr;
const ON_OutlineFigurePoint segment_start = a[segment_start_dex];
if (false == segment_start.IsOnFigure())
{
ON_ERROR("m_outline_points[segment_start_dex].m_bToPoint is false.");
return nullptr;
}
if (0 == segment_start.m_figure_index)
{
ON_ERROR("m_outline_points[segment_start_dex].m_contour_index is zero.");
return nullptr;
}
const unsigned int degree = Internal_SegmentDegree(segment_start_dex);
if (0 == degree)
{
ON_ERROR("m_outline_points[segment_start_dex] or nearby subsequent points are non valid.");
return nullptr;
}
ON__UINT32 i1 = segment_start_dex + degree;
while(i1+degree <= figure_end_dex)
{
if (segment_start.m_figure_index != a[i1].m_figure_index)
break;
if (degree != Internal_SegmentDegree(i1))
break;
if ( a[i1 - 1].m_point == a[i1 + 1].m_point )
{
if (degree == 1)
{
// double back line segment overlap
break;
}
if (
a[i1-degree].m_point == a[i1+degree].m_point
&& (2 == degree || a[i1-2].m_point == a[i1+2].m_point)
)
{
// double back bezier overlap
break;
}
}
i1 += degree;
}
*segment_end_dex = i1;
if (1 == degree)
{
while (segment_start_dex < i1 && a[i1 - 1].m_point == a[i1].m_point)
i1--;
while (segment_start_dex < i1 && a[segment_start_dex].m_point == a[segment_start_dex + 1].m_point)
segment_start_dex++;
if (segment_start_dex >= i1)
return nullptr; // zero length line segment
}
else
{
if (
false == a[i1].IsEndFigurePoint()
&& i1 < figure_end_dex
&& a[i1 + 1].IsEndFigurePoint()
&& a[i1].m_point == a[i1 + 1].m_point
)
{
// Happens when the last segment in a figure is a bezier that ends at the figure starting point (common).
*segment_end_dex = i1 + 1;
}
}
const int cv_count = (int)(i1 - segment_start_dex + 1);
const int order = (int)(degree + 1);
if (cv_count < order)
{
ON_ERROR("Bug in this function.");
return nullptr;
}
const int dim = b3d ? 3 : 2;
const int knot_count = cv_count + order - 2;
// Create NURBS curve
if (nullptr == nurbs_curve)
nurbs_curve = new ON_NurbsCurve(dim, false, order, cv_count);
else
{
nurbs_curve->ReserveCVCapacity(cv_count*dim);
nurbs_curve->ReserveKnotCapacity(knot_count);
nurbs_curve->m_dim = dim;
nurbs_curve->m_is_rat = 0;
nurbs_curve->m_order = order;
nurbs_curve->m_cv_count = cv_count;
nurbs_curve->m_cv_stride = dim;
}
double k = 0.0;
double* knot = nurbs_curve->m_knot;
double* knot1 = knot + knot_count;
switch (degree)
{
case 1:
while(knot < knot1)
{
*knot++ = k;
k += 1.0;
}
break;
case 2:
while(knot < knot1)
{
*knot++ = k;
*knot++ = k;
k += 1.0;
}
break;
case 3:
while(knot < knot1)
{
*knot++ = k;
*knot++ = k;
*knot++ = k;
k += 1.0;
}
break;
}
nurbs_curve->m_cv_count = 0;
ON_3dPoint prev_cv = ON_3dPoint::NanPoint;
int cv_dex = 0;
ON__UINT32 i;
for( i = segment_start_dex; i <= i1 && cv_dex < cv_count; i++)
{
const ON_3dPoint cv(a[i].m_point);
if (cv_dex > 0 && 2 == order && prev_cv == cv)
continue;
nurbs_curve->SetCV(cv_dex, cv);
cv_dex++;
prev_cv = cv;
}
nurbs_curve->m_cv_count = cv_dex;
if (i <= i1)
{
ON_ERROR("Bug in this function.");
}
if (nurbs_curve->m_cv_count < nurbs_curve->m_order)
{
ON_ERROR("Bug in this function.");
if (destination_curve == nurbs_curve)
{
nurbs_curve->m_dim = 0;
nurbs_curve->m_is_rat = 0;
nurbs_curve->m_order = 0;
nurbs_curve->m_cv_count = 0;
}
else
{
delete nurbs_curve;
}
nurbs_curve = nullptr;
}
return nurbs_curve;
}
#if 1
class Internal_OutlineFigureToPolyline
{
public:
double m_2x_tolerance = 0.0; // 2*tolerance (2* saves mulitplications)
void(*m_PointCallbackFunc)(float x, float y, void*);
void* m_context = nullptr;
ON_2fPoint m_prev_point = ON_2fPoint::NanPoint;
unsigned int m_point_count = 0;
void AddPoint(ON_2fPoint pt)
{
if (pt.IsValid() && false == (pt == m_prev_point))
{
m_PointCallbackFunc(pt.x, pt.y, m_context );
m_prev_point = pt;
m_point_count++;
}
}
void AddQuadraticBezier(ON_2fPoint bez_cv[3])
{
Internal_OutlineFigureToPolyline::Internal_AddBezier(0, 3, bez_cv);
}
void AddCubicBezier(ON_2fPoint bez_cv[4])
{
Internal_OutlineFigureToPolyline::Internal_AddBezier(0, 4, bez_cv);
}
public:
static void PointCallbackFunc2f(float x, float y, void*);
static void PointCallbackFunc2d(float x, float y, void*);
static void PointCallbackFunc3f(float x, float y, void*);
static void PointCallbackFunc3d(float x, float y, void*);
private:
void Internal_AddBezier(
int level,
int order,
const ON_2fPoint bez_cv[] // bez[order] control points
);
};
void Internal_OutlineFigureToPolyline::PointCallbackFunc2f(float x, float y, void* context)
{
((ON_SimpleArray<ON_2fPoint> *)(context))->Append(ON_2fPoint(x, y));
}
void Internal_OutlineFigureToPolyline::PointCallbackFunc2d(float x, float y, void* context)
{
((ON_SimpleArray<ON_2dPoint> *)(context))->Append(ON_2dPoint(x, y));
}
void Internal_OutlineFigureToPolyline::PointCallbackFunc3f(float x, float y, void* context)
{
((ON_SimpleArray<ON_3fPoint> *)(context))->Append(ON_3fPoint(x, y, 0.0));
}
void Internal_OutlineFigureToPolyline::PointCallbackFunc3d(float x, float y, void* context)
{
((ON_SimpleArray<ON_3dPoint> *)(context))->Append(ON_3dPoint(x, y, 0.0));
}
void Internal_OutlineFigureToPolyline::Internal_AddBezier(
int level,
int order,
const ON_2fPoint bez_cv[]
)
{
if ( order == 2 )
{
this->AddPoint(bez_cv[1]);
return;
}
const float* cv0 = &bez_cv[0].x;
if (3 == order )
{
if (level > 0)
{
if (
fabs(bez_cv[0].x + bez_cv[2].x - 2.0*bez_cv[1].x) <= 2.0*m_2x_tolerance
&& fabs(bez_cv[0].y + bez_cv[2].y - 2.0*bez_cv[1].y) <= 2.0*m_2x_tolerance
)
{
// bez midpoint is within tolerance of the chord from bez[0] to bez[2]
this->AddPoint(bez_cv[2]);
return;
}
}
}
else if ( 4 == order )
{
if ( level > 1 )
{
// x coordintat of bezier evaluated at t 0.5 is
// a = 1/8*bez_cv[0].x + 3/8*bez_cv[1].x + 3/8*bez_cv[2].x + 1/8*bez_cv[3].x
//
// x coordinate of midpoint of chord is
// b = (bez_cv[0].x + bez_cv[3].x)/2
//
// a-b = c = 3/8(bez_cv[0].x - bez_cv[1].x - bez_cv[2].x + bez_cv[3].x)
// c <= tol if and only if 2*c <= 2*tol
//
if (
fabs( 0.75*(bez_cv[0].x - bez_cv[1].x - bez_cv[2].x + bez_cv[3].x) ) <= m_2x_tolerance
&& fabs( 0.75*(bez_cv[0].y - bez_cv[1].y - bez_cv[2].y + bez_cv[3].y) ) <= m_2x_tolerance
)
{
// bez midpoint is within tolerance of the chord from bez[0] to bez[3]
this->AddPoint(bez_cv[3]);
return;
}
}
}
else
{
// invalid order
return;
}
// use de Casteljau to bisect bezier
float f14[14];
ON_2fPoint* cv = (ON_2fPoint*)f14;
float* cv1 = &cv[0].x;
*cv1++ = *cv0++; *cv1++ = *cv0++; // cv[0] = bez_cv[0]
cv1 += 2;
*cv1++ = *cv0++; *cv1++ = *cv0++; // cv[2] = bez_cv[1]
cv1 += 2;
*cv1++ = *cv0++; *cv1++ = *cv0++; // cv[4] = bez_cv[2]
if ( 4 == order )
{
cv1 += 2;
*cv1++ = *cv0++; *cv1++ = *cv0++; // cv[6] = bez_cv[3]
}
float* cv2;
for ( int k = 1; k < order; k++ )
{
cv1 = &cv[k].x;
cv0 = cv1 - 2;
cv2 = cv1 + 2;
int i = order-k;
while (i--)
{
*cv1++ = 0.5f*(*cv0++ + *cv2++);
*cv1++ = 0.5f*(*cv0++ + *cv2++);
cv1 += 2;
cv0 += 2;
cv2 += 2;
}
}
if ( level >= 8 )
{
// reached recursion limit
// cv[order-1] = middle point = bez(1/2)
this->AddPoint(cv[order-1]);
// cv[2*order-2].x = end point = bez(1)
this->AddPoint(cv[2*order-2]);
return;
}
else
{
// add left half
Internal_OutlineFigureToPolyline::Internal_AddBezier(level + 1, order, cv);
// add right half
Internal_OutlineFigureToPolyline::Internal_AddBezier(level + 1, order, cv + (order - 1));
}
return;
}
#endif
double ON_OutlineFigure::DefaultPolylineTolerance(
double units_per_em
)
{
const double default_polyline_tolerance
= (units_per_em > 0.0 && units_per_em < ON_UNSET_POSITIVE_VALUE && units_per_em != ON_UNSET_FLOAT)
? (units_per_em / 1024.0)
: 1.0;
return default_polyline_tolerance;
}
double ON_OutlineFigure::DefaultPolylineTolerance() const
{
return ON_OutlineFigure::DefaultPolylineTolerance(m_units_per_em);
}
unsigned int ON_OutlineFigure::GetPolyline(
double tolerance,
void(* PointCallbackFunc)(float x, float y, void *),
void * context
) const
{
if (nullptr == PointCallbackFunc)
return 0;
const ON_OutlineFigurePoint* a = m_points.Array();
const unsigned int c = m_points.UnsignedCount();
if (nullptr == a || c <= 0)
return 0;
Internal_OutlineFigureToPolyline tp;
tp.m_PointCallbackFunc = PointCallbackFunc;
tp.m_context = context;
const double default_tolerance = DefaultPolylineTolerance();
tp.m_2x_tolerance = 2.0*((tolerance > 0.0) ? tolerance : default_tolerance);
ON_2fPoint bez_cv[4];
for (unsigned int i = 0; i < c; i++)
{
if (tp.m_point_count > 0)
{
if (
ON_OutlineFigurePoint::Type::QuadraticBezierPoint == a[i].m_point_type
&& i + 1 < c
&& ON_OutlineFigurePoint::Type::QuadraticBezierPoint == a[i + 1].m_point_type
)
{
bez_cv[0] = tp.m_prev_point;
bez_cv[1] = a[i++].m_point;
bez_cv[2] = a[i].m_point;
tp.AddQuadraticBezier(bez_cv);
continue;
}
if (
ON_OutlineFigurePoint::Type::CubicBezierPoint == a[i].m_point_type
&& i + 2 < c
&& ON_OutlineFigurePoint::Type::CubicBezierPoint == a[i + 1].m_point_type
&& ON_OutlineFigurePoint::Type::CubicBezierPoint == a[i + 2].m_point_type
)
{
bez_cv[0] = tp.m_prev_point;
bez_cv[1] = a[i++].m_point;
bez_cv[2] = a[i++].m_point;
bez_cv[3] = a[i].m_point;
tp.AddCubicBezier(bez_cv);
continue;
}
}
tp.AddPoint(a[i].m_point);
}
return tp.m_point_count;
}
unsigned int ON_OutlineFigure::GetPolyline(
double tolerance,
ON_SimpleArray<ON_2fPoint>& points
) const
{
if (m_points.UnsignedCount() < 2)
return 0;
points.Reserve(3 * m_points.UnsignedCount());
return GetPolyline(tolerance, Internal_OutlineFigureToPolyline::PointCallbackFunc2f, &points);
}
unsigned int ON_OutlineFigure::GetPolyline(
double tolerance,
ON_SimpleArray<ON_2dPoint>& points
) const
{
if (m_points.UnsignedCount() < 2)
return 0;
points.Reserve(3 * m_points.UnsignedCount());
return GetPolyline(tolerance, Internal_OutlineFigureToPolyline::PointCallbackFunc2d, &points);
}
unsigned int ON_OutlineFigure::GetPolyline(
double tolerance,
ON_SimpleArray<ON_3fPoint>& points
) const
{
if (m_points.UnsignedCount() < 2)
return 0;
points.Reserve(3 * m_points.UnsignedCount());
return GetPolyline(tolerance, Internal_OutlineFigureToPolyline::PointCallbackFunc3f, &points);
}
unsigned int ON_OutlineFigure::GetPolyline(
double tolerance,
ON_SimpleArray<ON_3dPoint>& points
) const
{
if (m_points.UnsignedCount() < 2)
return 0;
points.Reserve(3 * m_points.UnsignedCount());
return GetPolyline(tolerance, Internal_OutlineFigureToPolyline::PointCallbackFunc3d, &points);
}
static void ON_Internal_FigureWindingNumberCallback(float x, float y, void *context)
{
((ON_WindingNumber*)context)->AddBoundary(ON_2dPoint(x, y));
}
int ON_OutlineFigure::WindingNumber(
ON_2fPoint winding_point
) const
{
if (false == winding_point.IsValid())
return 0;
const ON_BoundingBox bbox = BoundingBox();
if (false == (winding_point.x >= bbox.m_min.x && winding_point.x <= bbox.m_max.x))
return false;
if (false == (winding_point.y >= bbox.m_min.y && winding_point.y <= bbox.m_max.y))
return false;
const ON_OutlineFigurePoint* a = m_points.Array();
const unsigned int c = m_points.UnsignedCount();
if (nullptr == a || c <= 0)
return 0;
ON_WindingNumber wn;
wn.SetWindingPoint(winding_point.x,winding_point.y);
Internal_OutlineFigureToPolyline tp;
tp.m_PointCallbackFunc = ON_Internal_FigureWindingNumberCallback;
tp.m_context = &wn;
tp.m_2x_tolerance = 2.0;
ON_2fPoint bez_cv[4];
ON_2fPoint bbox_min, bbox_max;
for (unsigned int i = 0; i < c; i++)
{
if (tp.m_point_count > 0)
{
if (
ON_OutlineFigurePoint::Type::QuadraticBezierPoint == a[i].m_point_type
&& i + 1 < c
&& ON_OutlineFigurePoint::Type::QuadraticBezierPoint == a[i + 1].m_point_type
)
{
bez_cv[0] = tp.m_prev_point;
bez_cv[1] = a[i++].m_point;
bez_cv[2] = a[i].m_point;
bbox_min = bez_cv[0];
bbox_max = bbox_min;
for (int j = 1; j < 2; j++)
{
if (bez_cv[j].x < bbox_min.x)
bbox_min.x = bez_cv[j].x;
else if (bez_cv[j].x > bbox_max.x)
bbox_max.x = bez_cv[j].x;
if (bez_cv[j].y < bbox_min.y)
bbox_min.y = bez_cv[j].y;
else if (bez_cv[j].y > bbox_max.y)
bbox_max.y = bez_cv[j].y;
}
if (bbox.m_min.y <= winding_point.y && winding_point.y <= bbox.m_max.y)
{
if (bbox.m_min.x <= winding_point.x && winding_point.x <= bbox.m_max.x)
tp.AddQuadraticBezier(bez_cv);
else
wn.AddBoundary(ON_2dPoint(tp.m_prev_point), ON_2dPoint(a[i].m_point));
}
tp.m_prev_point = bez_cv[2];
continue;
}
if (
ON_OutlineFigurePoint::Type::CubicBezierPoint == a[i].m_point_type
&& i + 2 < c
&& ON_OutlineFigurePoint::Type::CubicBezierPoint == a[i + 1].m_point_type
&& ON_OutlineFigurePoint::Type::CubicBezierPoint == a[i + 2].m_point_type
)
{
bez_cv[0] = tp.m_prev_point;
bez_cv[1] = a[i++].m_point;
bez_cv[2] = a[i++].m_point;
bez_cv[3] = a[i].m_point;
bbox_min = bez_cv[0];
bbox_max = bbox_min;
for (int j = 1; j < 3; j++)
{
if (bez_cv[j].x < bbox_min.x)
bbox_min.x = bez_cv[j].x;
else if (bez_cv[j].x > bbox_max.x)
bbox_max.x = bez_cv[j].x;
if (bez_cv[j].y < bbox_min.y)
bbox_min.y = bez_cv[j].y;
else if (bez_cv[j].y > bbox_max.y)
bbox_max.y = bez_cv[j].y;
}
if (bbox.m_min.y <= winding_point.y && winding_point.y <= bbox.m_max.y)
{
if (bbox.m_min.x <= winding_point.x && winding_point.x <= bbox.m_max.x)
tp.AddCubicBezier(bez_cv);
else
wn.AddBoundary(ON_2dPoint(tp.m_prev_point), ON_2dPoint(a[i].m_point));
}
tp.m_prev_point = bez_cv[3];
continue;
}
}
if ( tp.m_point_count > 0 )
wn.AddBoundary(ON_2dPoint(tp.m_prev_point), ON_2dPoint(a[i].m_point));
tp.AddPoint(a[i].m_point);
}
return wn.WindingNumber();
}
bool ON_Outline::IsValidOutline(
bool bLogErrors
) const
{
const ON__UINT32 figure_count = m_figures.UnsignedCount();
if (figure_count <= 0)
{
if (bLogErrors) ON_ERROR("No figures in the outline");
return false;
}
for (ON__UINT32 i = 0; i < figure_count; i++)
{
if (false == m_figures[i].IsValidFigure(bLogErrors))
return false;
}
return true;
}
bool ON_OutlineAccumulator::EndOutline()
{
return EndOutline(false, ON_Outline::DefaultOuterOrientation);
}
bool ON_OutlineAccumulator::EndOutline(
bool bNegatePointY,
ON_OutlineFigure::Orientation orientation
)
{
if (1 != m_status)
return false;
AbandonCurrentFigure();
m_status = 2;
ON_Outline& outline = Internal_Outline();
if (bNegatePointY)
{
const ON__UINT32 figure_count = outline.m_figures.UnsignedCount();
for (ON__UINT32 i = 0; i < figure_count; i++)
outline.m_figures[i].NegateY();
}
if (
ON_OutlineFigure::Type::SingleStroke != m_figure_type
&& ON_OutlineFigure::Type::DoubleStroke != m_figure_type
)
{
const double desired_area_sign =
ON_OutlineFigure::Orientation::CounterClockwise == orientation
? 1.0
: (ON_OutlineFigure::Orientation::Clockwise == orientation ? -1.0 : 0.0);
if (0.0 != desired_area_sign)
{
double outline_area = outline.AreaEstimate();
if ((desired_area_sign*outline_area) < 0.0)
outline.Reverse();
outline.SortFigures(orientation);
}
}
return true;
}
const ON_OutlineFigurePoint ON_OutlineAccumulator::ActiveFigureStartPoint() const
{
return (m_figure_depth > 0) ? m_figure_start : ON_OutlineFigurePoint::Unset;
}
const ON_OutlineFigurePoint ON_OutlineAccumulator::ActiveFigureCurrentPoint() const
{
return (m_figure_depth > 0) ? m_figure_current : ON_OutlineFigurePoint::Unset;
}
bool ON_OutlineAccumulator::BeginFigure(
ON_OutlineFigurePoint::Type point_type,
ON_2fPoint point_location
)
{
if (1 != m_status)
{
ON_ERROR("ON_OutlineAccumulator is not initialized.");
Internal_AccumulateError(true);
return false;
}
if ( false == ON_OutlineFigurePoint::IsBeginFigurePointType(point_type) )
{
ON_ERROR("Invalid point_type for BeginFigure2f.");
Internal_AccumulateError(true);
return false;
}
return Internal_AccumulatePoint(point_type,point_location, true);
}
bool ON_OutlineAccumulator::AppendLine(
ON_2fPoint point_location
)
{
if (false == Internal_InFigure())
{
ON_ERROR("Not continuing a figure.");
Internal_AccumulateError(true);
return false;
}
if (false == m_figure_current.IsOnFigure())
{
ON_ERROR("Invalid start of line segment.");
Internal_AccumulateError(true);
return false;
}
if (m_figure_current.m_point == point_location)
{
// silently skip empty segments.
return false;
}
return Internal_AccumulatePoint(
ON_OutlineFigurePoint::Type::LineTo,
point_location,
true
);
}
bool ON_OutlineAccumulator::AppendQuadraticBezier(
ON_2fPoint cv1,
ON_2fPoint cv2
)
{
if (false == Internal_InFigure())
{
ON_ERROR("Not continuing a figure.");
Internal_AccumulateError(true);
return false;
}
if (false == m_figure_current.IsOnFigure())
{
ON_ERROR("Invalid start of quadratic bezier segment.");
Internal_AccumulateError(true);
return false;
}
const ON_2fPoint cv0 = m_figure_current.m_point;
if (cv0 == cv1 && cv0 == cv2)
{
// silently skip empty segments
return false;
}
if (cv0 == cv1 || cv2 == cv1)
return AppendLine(cv2);
if (cv0 == cv2)
{
// silently skip degenerate segments
//ON_ERROR("Degenerate quadratic bezier segment.");
return false;
}
const ON_OutlineFigurePoint::Type point_type = ON_OutlineFigurePoint::Type::QuadraticBezierPoint;
const ON__UINT32 count0 = m_point_accumulator.UnsignedCount();
const bool rc
= Internal_AccumulatePoint(point_type, cv1, false)
&& Internal_AccumulatePoint(point_type, cv2, true)
;
if (false == rc)
{
m_figure_current = ON_OutlineFigurePoint::Error;
if ( m_point_accumulator.UnsignedCount() > count0)
m_point_accumulator.SetCount((int)count0);
}
return rc;
}
static bool Internal_IsQuadraticBezier(
ON_2fPoint cv0,
ON_2fPoint cv1,
ON_2fPoint cv2,
ON_2fPoint cv3,
ON_2fPoint& qcv1
)
{
const double ftol = 1.0 / 4098.0;
const double itol = 0.5*ftol;
double s, t, c0, c1, c2, c3;
// test x coordinate
c0 = ((double)cv0.x);
c1 = ((double)cv1.x);
s = 0.5*(3.0*c1 - c0);
c2 = ((double)cv2.x);
c3 = ((double)cv3.x);
t = 0.5*(3.0*c2 - c3);
// q1x = candidate for quadratic cv
s = (s == t) ? s : 0.5*(s + t);
t = floor(s);
if (t + 0.5 < s)
t = ceil(s);
const double q1x = fabs(s - t) <= itol ? t : s;
// test to see if cv1.x was a result of degree increase
t = (2.0*q1x + c0) / 3.0;
if (!(fabs(t - c1) <= ftol))
return false;
// test to see if cv2.x was a result of degree increase
t = (2.0*q1x + c3) / 3.0;
if (!(fabs(t - c2) <= ftol))
return false;
// test y coordinate
c0 = ((double)cv0.y);
c1 = ((double)cv1.y);
s = 0.5*(3.0*c1 - c0);
c2 = ((double)cv2.y);
c3 = ((double)cv3.y);
t = 0.5*(3.0*c2 - c3);
// q1y = candidate for quadratic cv
s = (s == t) ? s : 0.5*(s + t);
t = floor(s);
if (t + 0.5 < s)
t = ceil(s);
const double q1y = fabs(s - t) <= itol ? t : s;
// test to see if cv1.y was a result of degree increase
t = (2.0*q1y + c0) / 3.0;
if (!(fabs(t - c1) <= ftol))
return false;
// test to see if cv2.y was a result of degree increase
t = (2.0*q1y + c3) / 3.0;
if (!(fabs(t - c2) <= ftol))
return false;
// Cubic bezier is actually a quadratic and the internal quadratic control point has integer coordinates
qcv1.x = (float)q1x;
qcv1.y = (float)q1y;
return true;
}
bool ON_OutlineAccumulator::AppendCubicBezier(
ON_2fPoint cv1,
ON_2fPoint cv2,
ON_2fPoint cv3
)
{
if (false == Internal_InFigure())
{
ON_ERROR("Not continuing a figure.");
Internal_AccumulateError(true);
return false;
}
if (false == m_figure_current.IsOnFigure())
{
ON_ERROR("Invalid beginning of a quadratic bezeir segment.");
Internal_AccumulateError(true);
return false;
}
const ON_2fPoint cv0 = m_figure_current.m_point;
if (cv0 == cv1 && cv0 == cv2 && cv0 == cv3)
{
// silently skip empty segments
return false;
}
if (cv0 == cv3)
{
// Check for degenerate cubic (all cvs on a line segment)
const double triangle_area2x
= cv0.y*(cv2.x - cv1.x)
+ cv1.y*(cv0.x - cv2.x)
+ cv2.y*(cv1.x - cv0.x);
const double tol = UnitsPerEM() / 16384.0;
if (fabs(triangle_area2x) <= tol*tol)
{
// silently skip degenerate segments
//ON_ERROR("Degenerate cubic bezier segment.");
return false;
}
}
// Some glyph outline API's including Microsoft's IDWrite::GetGlyphRunOutline()
// convert quadratic beziers into to cubic beziers.
// Every capital letter in the Microsoft Arial font uses quadratic beziers
// an is an example of where this test helps yield higher quality results.
ON_2fPoint qcv1;
if (Internal_IsQuadraticBezier(cv0, cv1, cv2, cv3, qcv1))
{
return AppendQuadraticBezier(qcv1, cv3);
}
const ON_OutlineFigurePoint::Type point_type = ON_OutlineFigurePoint::Type::CubicBezierPoint;
const ON__UINT32 count0 = m_point_accumulator.UnsignedCount();
const bool rc
= Internal_AccumulatePoint(point_type, cv1, false)
&& Internal_AccumulatePoint(point_type, cv2, false)
&& Internal_AccumulatePoint(point_type, cv3, true)
;
if (false == rc)
{
m_figure_current = ON_OutlineFigurePoint::Error;
if ( m_point_accumulator.UnsignedCount() > count0)
m_point_accumulator.SetCount((int)count0);
}
return rc;
}
bool ON_OutlineAccumulator::EndFigure(
ON_OutlineFigurePoint::Type point_type
)
{
if ( false == ON_OutlineFigurePoint::IsEndFigurePointType(point_type) )
{
ON_ERROR("Invalid point_type for EndFigure.");
Internal_AccumulateError(true);
return false;
}
if (false == Internal_InFigure())
{
ON_ERROR("Not continuing a figure.");
Internal_AccumulateError(true);
return false;
}
const bool rc = Internal_AccumulatePoint(point_type, ON_2fPoint::NanPoint, false);
return rc;
}
bool ON_OutlineFigure::IsValidFigure(
bool bLogErrors
) const
{
const ON__UINT32 figure_end_dex = Internal_FigureEndDex(bLogErrors);
if (0 == figure_end_dex)
{
return false;
}
const ON_OutlineFigurePoint* a = m_points.Array();
const ON_OutlineFigurePoint figure_start = a[0];
if (false == figure_start.IsBeginFigurePoint())
{
if ( bLogErrors ) ON_ERROR("m_points[0] is not a BeginFigure point.");
return false;
}
if ( ON_OutlineFigurePoint::Type::BeginFigureUnknown == figure_start.m_point_type)
{
// Finished figures never have BeginFigureUnknown point type.
if ( bLogErrors ) ON_ERROR("m_outline_points[figure_start_dex] is ON_OutlineFigurePoint::Type::BeginFigureUnknown.");
return false;
}
const ON__UINT16 figure_id = figure_start.m_figure_index;
if (0 == figure_id)
{
if ( bLogErrors ) ON_ERROR("BeginFigure point has 0 = m_contour_index.");
return false;
}
const ON_OutlineFigurePoint figure_end = a[figure_end_dex];
if (figure_id != figure_end.m_figure_index)
{
if ( bLogErrors ) ON_ERROR("BeginFigure and EndFigure points have diffent m_contour_index values.");
return false;
}
if ( false == figure_start.IsOnFigure())
{
if ( bLogErrors ) ON_ERROR("BeginFigure point IsOnFigure() is false.");
return false;
}
if (false == figure_end.IsOnFigure())
{
if ( bLogErrors ) ON_ERROR("EndFigure point IsOnFigure() is false.");
return false;
}
const bool bClosedFigure = (ON_OutlineFigurePoint::Type::BeginFigureOpen != figure_start.m_point_type);
if (bClosedFigure)
{
if (ON_OutlineFigurePoint::Type::EndFigureClosed != figure_end.m_point_type)
{
if ( bLogErrors ) ON_ERROR("Closed figure is not terminated with an EndFigureClosed point.");
return false;
}
if (false == (figure_start.m_point == figure_end.m_point))
{
if ( bLogErrors ) ON_ERROR("Closed figure has different start and end points.");
return false;
}
}
else
{
if (ON_OutlineFigurePoint::Type::EndFigureOpen != figure_end.m_point_type)
{
if ( bLogErrors ) ON_ERROR("Open figure is not terminated with an EndFigureOpen point.");
return false;
}
}
// Validate EndFigure bToPoint setting.
ON_OutlineFigurePoint p0, p1;
p1 = figure_start;
for (ON__UINT32 i = 1; i < figure_end_dex; i++)
{
p0 = p1;
p1 = a[i];
if (figure_id != p1.m_figure_index)
{
if (bLogErrors) ON_ERROR("BeginFigure and interior point have different m_contour_index values.");
return false;
}
if (ON_OutlineFigurePoint::Type::LineTo == p1.m_point_type)
{
if (false == p1.IsOnFigure())
{
if (bLogErrors) ON_ERROR("LineTo point IsOnFigure() is false.");
return false;
}
continue;
}
if (ON_OutlineFigurePoint::Type::QuadraticBezierPoint == p1.m_point_type)
{
if (false == p1.IsOffFigure())
{
if (bLogErrors) ON_ERROR("Interior QuadraticBezier point IsOffFigure() is false.");
return false;
}
if (i + 1 >= figure_end_dex)
{
if (bLogErrors) ON_ERROR("Invalid QuadraticBezier segment.");
return false;
}
p1 = a[++i];
if (figure_id != p1.m_figure_index)
{
if (bLogErrors) ON_ERROR("BeginFigure and interior point have different m_contour_index values.");
return false;
}
if (ON_OutlineFigurePoint::Type::QuadraticBezierPoint != p1.m_point_type)
{
if (bLogErrors) ON_ERROR("Invalid end of QuadraticBezier segment point type.");
return false;
}
if (false == p1.IsOnFigure())
{
if (bLogErrors) ON_ERROR("End of QuadraticBezier point IsOnFigure() is false.");
return false;
}
continue;
}
if (ON_OutlineFigurePoint::Type::CubicBezierPoint == p1.m_point_type)
{
if (false == p1.IsOffFigure())
{
if (bLogErrors) ON_ERROR("Interior CubicBezierPoint point IsOffFigure() is false.");
return false;
}
for (int cv_dex = 2; cv_dex <= 3; cv_dex++)
{
if (i + 1 >= figure_end_dex)
{
if (bLogErrors) ON_ERROR("Invalid CubicBezierPoint segment.");
return false;
}
p1 = a[++i];
if (figure_id != p1.m_figure_index)
{
if (bLogErrors) ON_ERROR("BeginFigure and interior point have different m_contour_index values.");
return false;
}
if (ON_OutlineFigurePoint::Type::CubicBezierPoint != p1.m_point_type)
{
if (bLogErrors) ON_ERROR("Invalid end of QuadraticBezier segment point type.");
return false;
}
if (2 == cv_dex)
{
if (false == p1.IsOffFigure())
{
if (bLogErrors) ON_ERROR("Interior CubicBezierPoint point IsOffFigure() is false.");
return false;
}
}
else
{
if (false == p1.IsOnFigure())
{
if (bLogErrors) ON_ERROR("End of QuadraticBezier point IsOnFigure() is false.");
return false;
}
}
}
continue;
}
if (bLogErrors) ON_ERROR("Invalid point type for figure interior.");
return false;
}
return true;
}
const ON_Outline& ON_OutlineAccumulator::Outline() const
{
return (nullptr == m_outline) ? *m_outline : ON_Outline::Unset;
}
ON_Outline& ON_OutlineAccumulator::Internal_Outline()
{
if (nullptr == m_outline)
{
m_outline = new ON_Outline();
m_outline->SetUnitsPerEM(m_units_per_em);
}
return *m_outline;
}
bool ON_OutlineAccumulator::Internal_InFigure() const
{
if (
1 == m_status
&& 1 == m_figure_depth
&& m_point_accumulator.UnsignedCount() > 0
&& m_figure_start.IsBeginFigurePoint()
&& m_figure_start.m_figure_index > 0
&& m_figure_current.m_figure_index == m_figure_start.m_figure_index
&& (m_figure_prev.m_figure_index == m_figure_start.m_figure_index || 1 == m_point_accumulator.UnsignedCount())
)
{
return true;
}
return false;
}
void ON_OutlineAccumulator::Internal_AccumulateError(
bool bCancelCurrentFigure
)
{
m_error_count++;
if (bCancelCurrentFigure)
{
m_figure_depth = 0;
}
}
void ON_OutlineAccumulator::ON_OutlineAccumulator::AbandonCurrentFigure()
{
m_figure_depth = 0;
m_point_accumulator.SetCount(0);
m_figure_start = ON_OutlineFigurePoint::Unset;
m_figure_prev = ON_OutlineFigurePoint::Unset;
m_figure_current = ON_OutlineFigurePoint::Unset;
}
bool ON_OutlineAccumulator::Internal_AccumulatePoint(
ON_OutlineFigurePoint::Type point_type,
ON_2fPoint point_location,
bool bPointInBoundingBox
)
{
int state = 0; // 0 = error, 1 = begin figure, 2 = continue figure, 3 = end figure
switch (point_type)
{
case ON_OutlineFigurePoint::Type::Unset:
ON_ERROR("point_type is Unset");
break;
case ON_OutlineFigurePoint::Type::QuadraticBezierPoint:
case ON_OutlineFigurePoint::Type::CubicBezierPoint:
case ON_OutlineFigurePoint::Type::LineTo:
state = 2; // continue a figure
break;
case ON_OutlineFigurePoint::Type::BeginFigureUnknown:
case ON_OutlineFigurePoint::Type::BeginFigureOpen:
case ON_OutlineFigurePoint::Type::BeginFigureClosed:
if ( ON_OutlineFigure::Type::SingleStroke == m_figure_type )
point_type = ON_OutlineFigurePoint::Type::BeginFigureOpen;
bPointInBoundingBox = true;
state = 1; // begin a figure
break;
//case ON_OutlineFigurePoint::Type::LineToCloseContour:
case ON_OutlineFigurePoint::Type::EndFigureClosed:
if ( ON_OutlineFigure::Type::SingleStroke == m_figure_type )
point_type = ON_OutlineFigurePoint::Type::EndFigureOpen;
state = 3; // end a figure - point_location input parameter is ignored.
break;
case ON_OutlineFigurePoint::Type::EndFigureOpen:
state = 3; // end a figure - point_location input parameter is ignored.
break;
case ON_OutlineFigurePoint::Type::Error:
break;
default:
ON_ERROR("point_type is invalid.");
break;
}
switch (state)
{
case 0: // error
break;
case 1: // begin a figure
if (0 != m_figure_depth)
{
ON_ERROR("Beginning a figure in an unexpected state.");
Internal_AccumulateError(true);
}
bPointInBoundingBox = true;
m_point_accumulator.SetCount(0);
if (0 == m_point_accumulator.Capacity())
m_point_accumulator.Reserve(64);
m_figure_depth = 1;
m_figure_start = ON_OutlineFigurePoint::Unset;
m_figure_prev = ON_OutlineFigurePoint::Unset;
m_figure_current = ON_OutlineFigurePoint::Unset;
break;
case 2: // continue a figure
if ( false == Internal_InFigure() )
{
ON_ERROR("Continuing a figure in an unexpected state.");
state = 0;
}
else
{
if (ON_OutlineFigurePoint::Type::LineTo == point_type)
bPointInBoundingBox = true;
}
break;
case 3: // end a figure
if ( false == Internal_InFigure() )
{
ON_ERROR("Ending a figure in an unexpected state.");
state = 0;
}
else
{
if ( ON_OutlineFigurePoint::Type::BeginFigureUnknown == m_figure_start.m_point_type )
{
if ( ON_OutlineFigurePoint::Type::EndFigureClosed == point_type )
m_figure_start.m_point_type = ON_OutlineFigurePoint::Type::BeginFigureClosed;
else if ( ON_OutlineFigurePoint::Type::EndFigureOpen == point_type )
m_figure_start.m_point_type = ON_OutlineFigurePoint::Type::BeginFigureOpen;
ON_OutlineFigurePoint q = m_point_accumulator[0];
if (
ON_OutlineFigurePoint::Type::BeginFigureUnknown == q.m_point_type
&& m_figure_start.m_figure_index == q.m_figure_index
&& m_figure_start.m_point == q.m_point
)
{
q.m_point_type = m_figure_start.m_point_type;
m_point_accumulator[0] = q;
}
}
if (ON_OutlineFigurePoint::Type::BeginFigureOpen == m_figure_start.m_point_type)
{
if (ON_OutlineFigurePoint::Type::EndFigureOpen != point_type)
{
ON_ERROR("EndFigureClosed used to finish an open figure");
point_type = ON_OutlineFigurePoint::Type::EndFigureOpen;
}
point_location = m_figure_current.m_point;
}
else
{
if (ON_OutlineFigurePoint::Type::EndFigureClosed != point_type)
{
ON_ERROR("EndFigureOpen used to finish an closed figure");
point_type = ON_OutlineFigurePoint::Type::EndFigureClosed;
}
point_location = m_figure_start.m_point;
}
bPointInBoundingBox = true;
}
m_figure_depth = 0;
break;
default: // invalid value for state
{
ON_ERROR("Bug in this function.");
state = 0;
}
break;
}
if (0 == state)
{
Internal_AccumulateError(false);
m_figure_current = ON_OutlineFigurePoint::Unset;
return false;
}
// Accumulate the information that goes with this point.
ON_Outline& outline = Internal_Outline();
const ON__UINT16 figure_index = (ON__UINT16)(1+outline.FigureCount());
ON_OutlineFigurePoint gp;
gp.m_point_type = point_type;
gp.m_point_proximity = bPointInBoundingBox ? ON_OutlineFigurePoint::Proximity::OnFigure : ON_OutlineFigurePoint::Proximity::OffFigure;
gp.m_figure_index = figure_index;
gp.m_point = point_location;
m_figure_prev = m_figure_current;
m_figure_current = gp;
if (state <= 2)
{
if (1 == state)
{
m_figure_start = gp;
m_figure_prev = ON_OutlineFigurePoint::Unset;
}
}
m_point_accumulator.Append(gp);
if (3 == state)
{
outline.AppendFigure(m_point_accumulator);
AbandonCurrentFigure();
}
// ready for the next point.
return true;
}
bool ON_FontGlyph::GetOutline(
bool bSingleStrokeFont,
class ON_Outline& outline
) const
{
outline = ON_Outline::Unset;
const ON_Font* font = Font();
if (nullptr == font)
return false;
// When it comes to the difference between a single stroke and a double stroke font,
// users and programmers are confused most of the time. This code protects
// them from the consequences of that confusion.
ON_OutlineFigure::Type font_figure_type = font->OutlineFigureType();
if (ON_OutlineFigure::Type::SingleStroke == font_figure_type)
bSingleStrokeFont = true;
else if (ON_OutlineFigure::Type::DoubleStroke == font_figure_type)
bSingleStrokeFont = false;
else if (bSingleStrokeFont)
font_figure_type = ON_OutlineFigure::Type::SingleStroke;
bool rc = false;
if (nullptr != ON_Font::Internal_CustomGetGlyphOutlineFunc)
{
rc = ON_Font::Internal_CustomGetGlyphOutlineFunc(
this,
bSingleStrokeFont,
outline
);
}
else
{
#if defined(ON_OS_WINDOWS_GDI)
// Use Windows Direct Write based tools
rc = ON_WindowsDWriteGetGlyphOutline(
this,
font_figure_type,
outline
);
#elif defined(ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE)
// Use Apple Core Text based tools
rc = ON_AppleFontGetGlyphOutline(
this,
font_figure_type,
outline
);
#elif defined(OPENNURBS_FREETYPE_SUPPORT)
// Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT.
// Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile").
// Use freetype based tools (least reliable results)
rc = ON_FreeTypeGetGlyphOutline(
this,
font_figure_type,
outline
);
#endif
}
return (rc || outline.FigureCount() > 0);
}
bool ON_FontGlyph::GetGlyphContours(
bool bSingleStrokeFont,
double height_of_capital,
ON_ClassArray< ON_SimpleArray< ON_Curve* > >& glyph_contours,
ON_BoundingBox* glyph_bbox,
ON_3dVector* glyph_advance
) const
{
const ON_Font* font = Font();
if (nullptr == font)
return false;
ON_Outline outline;
GetOutline(bSingleStrokeFont, outline);
const ON_FontMetrics fm = font->FontUnitFontMetrics();
double scale = 1.0;
if (height_of_capital > 0.0 && height_of_capital < ON_UNSET_POSITIVE_FLOAT)
{
scale = fm.GlyphScale(height_of_capital);
}
else if (
ON_UNSET_VALUE == height_of_capital
|| ON_UNSET_POSITIVE_VALUE == height_of_capital
|| ON_UNSET_FLOAT == height_of_capital
|| ON_UNSET_POSITIVE_FLOAT == height_of_capital
)
{
// returne results in font design units
scale = 1.0;
}
else
{
// normalized units.
const double font_UPM = font->FontUnitFontMetrics().AscentOfCapital();
if ( font_UPM > 0.0 && font_UPM < ON_UNSET_POSITIVE_FLOAT )
scale = ((double)ON_Font::AnnotationFontCellHeight) / font_UPM;
}
unsigned int rc = outline.GetOutlineCurves(
scale,
true, // 3d curves
glyph_contours
);
const ON_TextBox glyph_metrics = outline.GlyphMetrics();
if (nullptr != glyph_advance)
*glyph_advance = scale * ON_3dVector(glyph_metrics.m_advance.i, glyph_metrics.m_advance.j, 0.0);
if (nullptr != glyph_bbox)
*glyph_bbox = ON_BoundingBox(
scale*ON_3dPoint(glyph_metrics.m_bbmin.i, glyph_metrics.m_bbmin.j, 0.0),
scale*ON_3dPoint(glyph_metrics.m_bbmax.i, glyph_metrics.m_bbmax.j, 0.0)
);
return (rc > 0);
}