mirror of
https://github.com/mcneel/opennurbs.git
synced 2026-03-04 14:09:41 +08:00
Co-authored-by: Andrew Le Bihan <andy@mcneel.com> Co-authored-by: Dale Fugier <dale@mcneel.com> Co-authored-by: Dale Lear <dalelear@mcneel.com> Co-authored-by: Greg Arden <greg@mcneel.com> Co-authored-by: Jussi <jussi@mcneel.com> Co-authored-by: Lowell <lowell@mcneel.com> Co-authored-by: Rajaa Issa <rajaa@mcneel.com> Co-authored-by: Steve Baer <steve@mcneel.com> Co-authored-by: alain <alain@mcneel.com> Co-authored-by: chuck <chuck@mcneel.com> Co-authored-by: piac <giulio@mcneel.com>
3269 lines
87 KiB
C++
3269 lines
87 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();
|
|
}
|
|
}
|
|
|
|
const bool Internal_FigureBoxesAreDisjoint(
|
|
const ON_BoundingBox& a,
|
|
const ON_BoundingBox& b
|
|
)
|
|
{
|
|
// figure boxes are 2d - ignore z.
|
|
// figures are closed loops, so we can use <= and >= instead of < and >
|
|
if (a.m_min[0] >= b.m_max[0])
|
|
return true;
|
|
if (a.m_max[0] <= b.m_min[0])
|
|
return true;
|
|
if (a.m_min[1] >= b.m_max[1])
|
|
return true;
|
|
if (a.m_max[1] <= b.m_min[1])
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static int Internal_CompareAreaEstimate(ON_OutlineFigure* const* lhs, ON_OutlineFigure* const* rhs)
|
|
{
|
|
// Used to sort the figures_sorted_by_size[] array which is constructed in a way
|
|
// that we know all elements are not nullptr and have valid AreaEstimates().
|
|
const double lhs_area = fabs((*lhs)->AreaEstimate());
|
|
const double rhs_area = fabs((*rhs)->AreaEstimate());
|
|
if (lhs_area > rhs_area)
|
|
return -1; // largest areas are before smaller areas
|
|
if (lhs_area < rhs_area)
|
|
return 1; // largest areas are before smaller areas
|
|
return 0;
|
|
}
|
|
|
|
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*> sorted_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)
|
|
)
|
|
{
|
|
sorted_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);
|
|
}
|
|
|
|
const unsigned int sorted_figure_count = sorted_figures.UnsignedCount();
|
|
|
|
// Sort figures so the ones with largest included area are first.
|
|
sorted_figures.QuickSort(Internal_CompareAreaEstimate);
|
|
|
|
// As we look at each element of sorted_figures[], it gets assigned to
|
|
// outer_figures[] or inner_figures[]. If it is assigned to inner_figures[],
|
|
// the smallest figure that contains it is assigned to inner_figures_parent[].
|
|
//
|
|
// Over the years, many more complicated approaches to sorting were tried,
|
|
// all of which attempted to take into account the orientations from the font file.
|
|
// It is so common for the orientations in the font files to be wrong,
|
|
// that ignoring them is the most efficient and reliable approach to date.
|
|
// It is extremly common for outer figures to overlap.
|
|
ON_SimpleArray<ON_OutlineFigure*> outer_figures(sorted_figure_count);
|
|
ON_SimpleArray<ON_OutlineFigure*> inner_figures(sorted_figure_count);
|
|
ON_SimpleArray<ON_OutlineFigure*> inner_figures_parent(sorted_figure_count);
|
|
|
|
for ( unsigned k = 0; k < sorted_figure_count; ++k)
|
|
{
|
|
ON_OutlineFigure* f = sorted_figures[k];
|
|
|
|
// This for(int k0; ...) loop finds the smallest larger figure that contains f and uses
|
|
// the orientation of this containing loop to determine the orientation of f.
|
|
ON_OutlineFigure* parent_outer_figure = nullptr;
|
|
for (unsigned k0 = 0; k0 < k; ++k0)
|
|
{
|
|
// Given our assumptions, it is impossible for bigger_f to be inside of f.
|
|
ON_OutlineFigure* bigger_f = sorted_figures[k0];
|
|
if (false == f->IsInsideOf(bigger_f, false))
|
|
continue;
|
|
|
|
if (outer_loop_orientation == bigger_f->FigureOrientation())
|
|
{
|
|
// f is inside of bigger_f and bigger_f is an outer loop.
|
|
parent_outer_figure = bigger_f;
|
|
}
|
|
else if (inner_loop_orientation == bigger_f->FigureOrientation())
|
|
{
|
|
// f is inside of bigger_f and bigger_f is an inner loop.
|
|
// So f is a nested outer (like the registered trademark glyph)
|
|
parent_outer_figure = nullptr;
|
|
}
|
|
}
|
|
|
|
if (nullptr == parent_outer_figure)
|
|
{
|
|
outer_figures.Append(f);
|
|
if (inner_loop_orientation == f->FigureOrientation())
|
|
{
|
|
// convert this figure to an outer.
|
|
f->ReverseFigure();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
inner_figures.Append(f);
|
|
inner_figures_parent.Append(parent_outer_figure);
|
|
if (outer_loop_orientation == f->FigureOrientation())
|
|
{
|
|
// convert this figure to an inner
|
|
f->ReverseFigure();
|
|
}
|
|
}
|
|
}
|
|
|
|
const unsigned outer_count = outer_figures.UnsignedCount();
|
|
const unsigned inner_count = inner_figures.UnsignedCount();
|
|
|
|
if ( 0 == outer_count )
|
|
{
|
|
m_sorted_figure_outer_orientation = ON_OutlineFigure::Orientation::Error;
|
|
return;
|
|
}
|
|
|
|
if (0 == inner_count)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Sort figures by outer followed by its inners.
|
|
sorted_figures.SetCount(0);
|
|
for (unsigned i = 0; i < outer_count; ++i)
|
|
{
|
|
ON_OutlineFigure* outer_figure = outer_figures[i];
|
|
if (nullptr == outer_figure)
|
|
continue;
|
|
sorted_figures.Append(outer_figure);
|
|
for (unsigned j = 0; j < inner_count; ++j)
|
|
{
|
|
if (outer_figure == inner_figures_parent[j])
|
|
{
|
|
ON_OutlineFigure* inner_figure = inner_figures[j];
|
|
inner_figures[j] = nullptr;
|
|
inner_figures_parent[j] = nullptr;
|
|
if (nullptr != inner_figure)
|
|
sorted_figures.Append(inner_figure);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 = 0U; i < figure_end_dex; ++i)
|
|
{
|
|
const ON__UINT32 degree = ON_OutlineFigure::Internal_SegmentDegree(i);
|
|
if (degree < 1U || degree > 3U)
|
|
continue;
|
|
// a[i] = start of bezier (line,quadratic,or cubic)
|
|
ON_OutlineFigurePoint p = a[i+degree]; // p = a[i+degree] = end of bezier (line,quadratic,or cubic)
|
|
Internal_GrowBBox(p.m_point, p.m_point, bbox_min, bbox_max);
|
|
if (degree >= 2U)
|
|
{
|
|
p = a[++i]; // p = 1st interior cv of a quadratic or cubic bezier
|
|
Internal_GrowBBox(p.m_point, p.m_point, bbox_cv_min, bbox_cv_max);
|
|
if (3U == degree)
|
|
{
|
|
p = a[++i]; // p = 2nd interior cv of a cubic bezier
|
|
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 = 0U; i < figure_end_dex; ++i)
|
|
{
|
|
const ON__UINT32 degree = ON_OutlineFigure::Internal_SegmentDegree(i);
|
|
if (degree < 2U || degree > 3U)
|
|
continue; // correct to continue when degree = 1
|
|
cv[0] = a[i]; // [a[i] = start of quadratic or cubic bezier
|
|
cv[1] = a[++i]; // 1st interior cv of a quadratic or cubic bezer
|
|
bbox_cv_min = cv[1].m_point;
|
|
bbox_cv_max = bbox_cv_min;
|
|
cv[2] = a[i + 1]; // end of quadratic bezier or penultimate cv of a cubic bezier
|
|
if (3U == degree)
|
|
{
|
|
Internal_GrowBBox(cv[2].m_point, cv[2].m_point, bbox_cv_min, bbox_cv_max);
|
|
++i;
|
|
cv[3] = a[i + 1]; // end of a cubic bezier
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
unsigned ON_OutlineFigure::GetUpToFourPointsOnFigure(ON_2fPoint p[4]) const
|
|
{
|
|
if (nullptr == p)
|
|
return 0;
|
|
|
|
unsigned point_count = 0;
|
|
const ON__UINT32 end_dex = Internal_FigureEndDex(false);
|
|
if (end_dex > 0)
|
|
{
|
|
// p[0] = point at the start
|
|
const ON_2fPoint p0 = this->m_points[0].m_point;
|
|
p[point_count++] = p0;
|
|
|
|
|
|
// p[1] = point between start and middle
|
|
ON_2fPoint prev_p = p0;
|
|
unsigned i1 = end_dex / 2;
|
|
if (i1 >= 2)
|
|
{
|
|
for (ON__UINT32 i = i1 - 1; i > 0; --i)
|
|
{
|
|
if (false == this->m_points[i].IsOnFigure())
|
|
continue;
|
|
const ON_2fPoint candidate_p = this->m_points[i].m_point;
|
|
if (prev_p != candidate_p)
|
|
{
|
|
prev_p = candidate_p;
|
|
p[point_count++] = candidate_p;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// p[2] = point between middle and end
|
|
for (/*empty init*/; i1 <= end_dex; ++i1)
|
|
{
|
|
if (false == this->m_points[i1].IsOnFigure())
|
|
continue;
|
|
const ON_2fPoint candidate_p = this->m_points[i1].m_point;
|
|
if (prev_p != candidate_p)
|
|
{
|
|
prev_p = candidate_p;
|
|
p[point_count++] = candidate_p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// p[3] = point close to end
|
|
for (ON__UINT32 i = end_dex; i > i1; --i)
|
|
{
|
|
if (false == this->m_points[i].IsOnFigure())
|
|
continue;
|
|
const ON_2fPoint candidate_p = this->m_points[i].m_point;
|
|
if (prev_p != candidate_p && p0 != candidate_p)
|
|
{
|
|
p[point_count++] = candidate_p;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (unsigned i = point_count; i < 4; ++i)
|
|
p[i] = ON_2fPoint::NanPoint;
|
|
return point_count;
|
|
}
|
|
|
|
static bool Internal_ExtraInsideOfPolylineText(
|
|
const ON_OutlineFigure* outer_figure,
|
|
const ON_OutlineFigure* inner_figure
|
|
)
|
|
{
|
|
// This function is called when an extra test is needed to be certain
|
|
// that outer_pline contains inner_pline. Generating the input
|
|
// for this text and performing this test is computationally expensive.
|
|
// It is called sparingly.
|
|
|
|
if (nullptr == outer_figure || nullptr == inner_figure)
|
|
return false;
|
|
|
|
ON_SimpleArray<ON_2dPoint> outer_pline;
|
|
outer_figure->GetPolyline(0.0, outer_pline);
|
|
const unsigned outer_count = outer_pline.UnsignedCount();
|
|
if (outer_count < 4)
|
|
return false;
|
|
|
|
ON_SimpleArray<ON_2dPoint> inner_pline;
|
|
inner_figure->GetPolyline(0.0, inner_pline);
|
|
const unsigned inner_count = inner_pline.UnsignedCount();
|
|
if (inner_count < 2)
|
|
return false;
|
|
|
|
const ON_2dPoint* a = outer_pline.Array();
|
|
const ON_2dPoint* b = inner_pline.Array();
|
|
|
|
// Bmin, Bmax = bounding box of inner_pline.
|
|
ON_2dPoint Bmin = b[0];
|
|
ON_2dPoint Bmax = Bmin;
|
|
double c;
|
|
for (unsigned j = 1; j < inner_count; ++j)
|
|
{
|
|
c = b[j].x;
|
|
if (c < Bmin.x)
|
|
Bmin.x = c;
|
|
else if (c > Bmax.x)
|
|
Bmax.x = c;
|
|
c = b[j].y;
|
|
if (c < Bmin.y)
|
|
Bmin.y = c;
|
|
else if (c > Bmax.y)
|
|
Bmax.y = c;
|
|
}
|
|
|
|
ON_2dPoint Amin;
|
|
ON_2dPoint Amax;
|
|
ON_2dPoint A[2] = { ON_2dPoint::NanPoint,a[0] };
|
|
ON_2dPoint B[2] = { ON_2dPoint::NanPoint,ON_2dPoint::NanPoint };
|
|
double alpha[3] = { ON_DBL_QNAN, ON_DBL_QNAN, ON_DBL_QNAN };
|
|
for (unsigned i = 1; i < outer_count; ++i)
|
|
{
|
|
A[0] = A[1];
|
|
A[1] = a[i];
|
|
if (A[0] == A[1])
|
|
continue;
|
|
|
|
// Amin,Amax = bounding box of outer line segment
|
|
|
|
if (A[0].x <= A[1].x)
|
|
{
|
|
Amin.x = A[0].x;
|
|
Amax.x = A[1].x;
|
|
}
|
|
else
|
|
{
|
|
Amin.x = A[1].x;
|
|
Amax.x = A[0].x;
|
|
}
|
|
if (Amax.x < Bmin.x)
|
|
continue;
|
|
if (Amin.x > Bmax.x)
|
|
continue;
|
|
|
|
if (A[0].y <= A[1].y)
|
|
{
|
|
Amin.y = A[0].y;
|
|
Amax.y = A[1].y;
|
|
}
|
|
else
|
|
{
|
|
Amin.y = A[1].y;
|
|
Amax.y = A[0].y;
|
|
}
|
|
if (Amax.y < Bmin.y)
|
|
continue;
|
|
if (Amin.y > Bmax.y)
|
|
continue;
|
|
|
|
alpha[0] = ON_DBL_QNAN;
|
|
|
|
B[1] = b[0];
|
|
for (unsigned j = 1; j < inner_count; ++j)
|
|
{
|
|
B[0] = B[1];
|
|
B[1] = b[j];
|
|
if (B[0] == B[1])
|
|
continue;
|
|
|
|
// line segment bounding box check
|
|
if (B[0].x < Amin.x && B[1].x < Amin.x)
|
|
continue;
|
|
if (B[0].y < Amin.y && B[1].y < Amin.y)
|
|
continue;
|
|
if (B[0].x > Amax.x && B[1].x > Amax.x)
|
|
continue;
|
|
if (B[0].y > Amax.y && B[1].y > Amax.y)
|
|
continue;
|
|
|
|
if (alpha[0] != alpha[0])
|
|
{
|
|
// need to initialize alpha[3]
|
|
alpha[0] = A[1].y - A[0].y;
|
|
alpha[1] = A[0].x - A[1].x;
|
|
alpha[2] = A[1].x * A[0].y - A[0].x * A[1].y;
|
|
};
|
|
|
|
double h[2] = {
|
|
alpha[0] * B[0].x + alpha[1] * B[0].y + alpha[2],
|
|
alpha[0] * B[1].x + alpha[1] * B[1].y + alpha[2]
|
|
};
|
|
if (h[0] < 0.0 && h[1] < 0.0)
|
|
continue; // B[0] and B[1] on same side of infinte line through A[0],A[1]
|
|
if (h[0] > 0.0 && h[1] > 0.0)
|
|
continue; // B[0] and B[1] on same side of infinte line through A[0],A[1]
|
|
|
|
const double beta[3] =
|
|
{
|
|
B[1].y - B[0].y,
|
|
B[0].x - B[1].x,
|
|
B[1].x * B[0].y - B[0].x * B[1].y
|
|
};
|
|
|
|
h[0] = beta[0] * B[0].x + beta[1] * B[0].y + beta[2];
|
|
h[1] = beta[0] * B[1].x + beta[1] * B[1].y + beta[2];
|
|
if (h[0] < 0.0 && h[1] < 0.0)
|
|
continue; // A[0] and A[1] on same side of infinte line through B[0],B[1]
|
|
if (h[0] > 0.0 && h[1] > 0.0)
|
|
continue; // A[0] and A[1] on same side of infinte line through B[0],B[1]
|
|
|
|
// The line segment A[0],A[1] and line segment B[0],B[1] intersect someplace.
|
|
// The location of the intersection doesn't matter, even if it's one of the end points.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ON_OutlineFigure::IsInsideOf(
|
|
const ON_OutlineFigure* outer_figure,
|
|
bool bPerformExtraChecking
|
|
) const
|
|
{
|
|
if (nullptr == outer_figure)
|
|
return false;
|
|
|
|
const ON_OutlineFigure::Orientation outer_orientation = outer_figure->FigureOrientation();
|
|
if (ON_OutlineFigure::Orientation::Clockwise != outer_orientation && ON_OutlineFigure::Orientation::CounterClockwise != outer_orientation)
|
|
return false;
|
|
|
|
|
|
const ON_BoundingBox this_bbox = this->BoundingBox();
|
|
const ON_BoundingBox outer_bbox = outer_figure->BoundingBox();
|
|
if (false == outer_bbox.Includes(this_bbox))
|
|
{
|
|
// The bounding boxes are from the glyph outline control polygons.
|
|
// There are rare case when this f is inside of other_f
|
|
// but this_bbox is not contained in other_bbox. In practice, this
|
|
// is quite rare, but that's why "probably" is part of this function's name.
|
|
return false;
|
|
}
|
|
|
|
const double outer_area = fabs(outer_figure->AreaEstimate());
|
|
const double this_area = fabs(this->AreaEstimate());
|
|
if (false == (0.0 < this_area && this_area < outer_area))
|
|
return false; // this figure is too big to be inside of other_f
|
|
|
|
// Check that the 3 standard test points on this are inside of other_f.
|
|
// Again, it is possible that the 3 test points are inside but some other
|
|
// point is outside. In practice this is rare and that possibility
|
|
// is why "probably" is in this function's name.
|
|
|
|
const int outer_orientation_sign = ON_OutlineFigure::Orientation::CounterClockwise == outer_orientation ? 1 : -1;
|
|
|
|
ON_2fPoint this_p[4];
|
|
unsigned this_point_count = this->GetUpToFourPointsOnFigure(this_p);
|
|
|
|
for (unsigned i = 0; i < this_point_count; ++i)
|
|
{
|
|
// check start point.
|
|
const int wn = outer_orientation_sign * outer_figure->WindingNumber(this_p[i]);
|
|
if (1 != wn)
|
|
{
|
|
// this_p[i] is not inside of other_f.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (0 == this_point_count)
|
|
return false;
|
|
|
|
const ON_OutlineFigure::Orientation this_orientation = this->FigureOrientation();
|
|
|
|
if (outer_orientation == this_orientation || bPerformExtraChecking)
|
|
{
|
|
// The context that calls this function is sorting nested loops.
|
|
// The orientation of outer_figure has been decided and set at this point.
|
|
// When this orientation of this is not different, we need more checking
|
|
// to verify that the orientaion from the font definition file was really "wrong".
|
|
// The "A crossbar" in Bahnschrift U+00C5 is one of many cases that
|
|
// require this additional checking. More generally, glyphs with
|
|
// orientations set correctly and which use overlapping outer
|
|
// boundaries need this test to prevent incorrectly. This situation is
|
|
// common in fonts like Bahnschrift and fonts for Asian language scripts
|
|
// that have "brush stroke" boundaries that overlap.
|
|
if (false == Internal_ExtraInsideOfPolylineText(outer_figure, this))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
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).
|
|
// Freetype is basically a file reading utility that can parse outline
|
|
// information in older formats of font files. It fails on newer formats
|
|
// and it also commonly fails to correctly map UNICODE code points to
|
|
// glyph indices. Freetype should only be used as a last resort.
|
|
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);
|
|
}
|
|
|
|
|
|
|