@@ -1,772 +1,182 @@
// The methods ImGuiEdtaaHelper::make_distance_mapd(...) and ImGuiEdtaaHelper::make_distance_mapb(...)
// are based on https://github.com/rougier/freetype-gl/blob/master/distance-field.c [Copyright 2011,2012 Nicolas P. Rougier. All rights reserved.]
// freetype-gl license:
/*
/* =========================================================================
* Freetype GL - A C OpenGL Freetype engine
* Platform: Any
* WWW: https://github.com/rougier/freetype-gl
* -------------------------------------------------------------------------
* Copyright 2011,2012 Nicolas P. Rougier. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY NICOLAS P. ROUGIER ''AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL NICOLAS P. ROUGIER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of Nicolas P. Rougier.
* =========================================================================
*/
// "edtaa3func.c.inl" by Stefan Gustavson has its own (MIT) license at the top of it
#include " imguiSdfGenerator.h"
#include < math.h>
#include " imgui.h"
#define IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_PLACEMENT_NEW
#include " imgui_internal.h"
// TODO: add support for -lfreetype if possible
// TODO: Here we compile "stb_rect_pack.h" and "stb_truetype.h" twice in 2 static units (see imgui_draw.cpp)...
#define STBRP_ASSERT (x ) IM_ASSERT(x)
#ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
#define STBRP_STATIC
#define STB_RECT_PACK_IMPLEMENTATION
#endif
#include " stb_rect_pack.h"
#define STBTT_malloc (x,u ) ((void )(u), ImGui::MemAlloc(x))
#define STBTT_free (x,u ) ((void )(u), ImGui::MemFree(x))
#define STBTT_assert (x ) IM_ASSERT(x)
#ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
#define STBTT_STATIC
#define STB_TRUETYPE_IMPLEMENTATION
#else
#define STBTT_DEF extern
#endif
#include " stb_truetype.h"
namespace ImGuiSdfGenerator {
// This namespace should contain stuff that is independent
// on both stb_truetype and freetype
// except one method that should be used as binding
struct SdfExtraParams {
float sdExtentInPixels; // signed distant extent [e.g. 2 pxls means that the sd alpha extends two pixels on each side of the glyph contour]
float sdCenterValue; // the alpha value on the glyph contour [usually 0.5]
float sdFlatnessInPixels; // 1.f [0.25f]
const ImFontConfig* cfg;
SdfExtraParams (const ImFontConfig* _cfg,float _sdCenterValue=0 .5f ,float _sdFlatnessInPixels=1 .f) : sdCenterValue(_sdCenterValue),sdFlatnessInPixels(_sdFlatnessInPixels),cfg(_cfg) {
IM_ASSERT (cfg);
IM_ASSERT (cfg->SizePixels >0 );
sdExtentInPixels=(float )cfg->SizePixels *0 .125f ;
sdExtentInPixels*=float (cfg->OversampleH >cfg->OversampleV ?cfg->OversampleH :cfg->OversampleV );
IM_ASSERT (sdFlatnessInPixels>0 );
}
};
inline static float ImVec2Dot (const ImVec2& S1,const ImVec2& S2) {return (S1.x *S2.x +S1.y *S2.y );}
inline static float ImVec2Cross (const ImVec2& P0,const ImVec2& P1) {return P0.x *P1.y - P1.x *P0.y ;}
inline static bool AreFuzzyEqual (float v0,float v1,float eps = float (0.0000001 )) {return fabs (v0-v1)<eps;}
inline static bool AreFuzzyEqual (const ImVec2& v0,const ImVec2& v1,float eps = float (0.0000001 )) {return (AreFuzzyEqual (v0.x ,v1.x ,eps) && AreFuzzyEqual (v0.y ,v1.y ,eps));}
inline static float GetSegmentLength (const ImVec2& p0,const ImVec2& p1) {return sqrt ((p1.x -p0.x )*(p1.x -p0.x )+(p1.y -p0.y )*(p1.y -p0.y ));}
inline static float GetQuadraticBezierCurveLength (const ImVec2& p0,const ImVec2& p1,const ImVec2& p2) {
// http://www.malczak.linuxpl.com/blog/quadratic-bezier-curve-length/
// Good approximation, but might fail: it might return NaN or infinite [Test case: P0=(5.9732384830713272,4.372880969196558) C0=(5.6681537218391895,4.372880969196558) P1=(5.3791260533034801,4.372880969196558)]
ImVec2 a (p0.x -float (2 )*p1.x +p2.x ,p0.y -float (2 )*p1.y +p2.y );
ImVec2 b (float (2 )*p1.x -float (2 )*p0.x ,float (2 )*p1.y -float (2 )*p0.y );
const float A = float (4 )*(a.x *a.x +a.y *a.y );
const float B = float (4 )*(a.x *b.x +a.y *b.y );
const float C = b.x *b.x +b.y *b.y ;
const float Sabc = float (2 )*sqrt (A+B+C);
const float A_2 = sqrt (A);
const float A_32 = float (2 )*A*A_2;
const float C_2 = float (2 )*sqrt (C);
const float BA = B/A_2;
return (A_32*Sabc+A_2*B*(Sabc-C_2) + (float (4 )*C*A-B*B)*log ((float (2 )*A_2+BA+Sabc)/(BA+C_2)))/(float (4 )*A_32);
}
inline static float GetQuadraticBezierCurveLengthAlternative (const ImVec2& p0,const ImVec2& p1,const ImVec2& p2,int num_segments=5 ) {
// Approximated by flattening the curve into 'num_segments' intermediate points.
if (num_segments<=0 ) num_segments = 5 ;
ImVec4 W;
float length=0 ,t,u;
ImVec2 L = p0,tp;
for (int i = 0 ; i < num_segments; i++) {
t = (float )(i+1 )/(float )(num_segments+1 );u = float (1.0 ) - t;
W.x =u*u;W.y =float (2 )*u*t;W.z =t*t;W.w =float (0 );
tp.x = W.x *p0.x + W.y *p1.x + W.z *p2.x ;
tp.y = W.x *p0.y + W.y *p1.y + W.z *p2.y ;
length+=GetSegmentLength (L,tp);
L=tp;
}
tp = p2;
length+=GetSegmentLength (L,tp);
return length;
#ifdef __cplusplus
extern " C" {
#include " edtaa3func.c.inl"
}
inline static float GetCubicBezierCurveLength (const ImVec2& p0,const ImVec2& p1,const ImVec2& p2,const ImVec2& p3,int num_segments=5 ) {
// Approximated by flattening the curve into 'num_segments' intermediate points.
if (num_segments<=0 ) num_segments = 5 ;
ImVec4 W;
float length=0 ,t,u;
ImVec2 L = p0,tp;
for (int i = 0 ; i < num_segments; i++) {
t = (float )(i+1 )/(float )(num_segments+1 );u = float (1.0 ) - t;
W.x =u*u*u;W.y =float (3 )*u*u*t;W.z =float (3 )*u*t*t;W.w =t*t*t;
tp.x = W.x *p0.x + W.y *p1.x + W.z *p2.x + W.w *p3.x ;
tp.y = W.x *p0.y + W.y *p1.y + W.z *p2.y + W.w *p3.y ;
length+=GetSegmentLength (L,tp);
L=tp;
#else // __cplusplus
#include " edtaa3func.c.inl"
#endif // __cplusplus
namespace ImGuiEdtaaHelper {
// The purpose of TmpData is to allow releasing the memory all togther
// at the end of a multiple call to make_distance_mapb(...)/make_distance_mapd(...)
// [However we don't use this feature here]
struct TmpData {
ImVector<double > v_data;
ImVector<short > v_xdist;
ImVector<short > v_ydist;
ImVector<double > v_gx;
ImVector<double > v_gy;
ImVector<double > v_outside;
ImVector<double > v_inside;
void freeMemory () {
TmpData tmp;
v_data.swap (tmp.v_data );
v_xdist.swap (tmp.v_xdist );
v_ydist.swap (tmp.v_ydist );
v_gx.swap (tmp.v_gx );
v_outside.swap (tmp.v_outside );
v_inside.swap (tmp.v_inside );
}
tp = p3;
length+=GetSegmentLength (L,tp);
};
static TmpData tmpData;
static void FreeTmpMemory () {tmpData.freeMemory ();}
return length;
}
// These two methods are adapted from: https://github.com/rougier/freetype-gl (Copyright 2011,2012 Nicolas P. Rougier),
// so that the memory they use can optionally be freed once, after all the glyphs are processed.
static double * make_distance_mapd ( double *data, unsigned int width, unsigned int height, TmpData* pOptionalTmpData=NULL )
{
if (!pOptionalTmpData) pOptionalTmpData=&tmpData;
inline static float GetSquaredDistancePointSegment (const ImVec2& P,const ImVec2& S0,const ImVec2& S1,float eps = float (0.0000001 )) {
// Based on: http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
const float l2 = (S0.x -S1.x )*(S0.x -S1.x )+(S0.y -S1.y )*(S0.y -S1.y ); // segment length squared
if (l2 < eps) {
// S1 == S2 case
return (P.x -S0.x )*(P.x -S0.x )+(P.y -S0.y )*(P.y -S0.y );
}
ImVec2 T (S1.x -S0.x ,S1.y -S0.y ),PS1 (P.x -S0.x ,P.y -S0.y );
const float tf = ImVec2Dot (PS1, T)/l2;
const float minTf = float (1 ) < tf ? float (1 ) : tf;
const float t = float (0 ) > minTf ? float (0 ) : minTf;
// T = Projection on the segment
T.x = S0.x + T.x *t;
T.y = S0.y + T.y *t;
return (P.x -T.x )*(P.x -T.x )+(P.y -T.y )*(P.y -T.y );
}
inline static float GetSquaredDistanceToQuadraticBezierCurve (const ImVec2& point,const ImVec2& p0,const ImVec2& p1,const ImVec2& p2,int num_segments=2 ,ImVec2* pChk0=NULL ,ImVec2* pChk1=NULL ,float eps = float (0.0000001 )) {
static bool firstRun = true ;
static const int max_num_intermediate_segments = 25 ; // >0
static const int total_num_segment = (max_num_intermediate_segments+1 )*max_num_intermediate_segments/2 ;
static int start_num_segment_index[max_num_intermediate_segments+1 ];
static ImVec4 weights[total_num_segment];
const unsigned int area = width * height;
IM_ASSERT (area>0 );
pOptionalTmpData->v_xdist .resize (area); short * xdist = &pOptionalTmpData->v_xdist [0 ];
pOptionalTmpData->v_ydist .resize (area); short * ydist = &pOptionalTmpData->v_ydist [0 ];
pOptionalTmpData->v_gx .resize (area); double * gx = &pOptionalTmpData->v_gx [0 ];
pOptionalTmpData->v_gy .resize (area); double * gy = &pOptionalTmpData->v_gy [0 ];
pOptionalTmpData->v_outside .resize (area); double * outside=&pOptionalTmpData->v_outside [0 ];
pOptionalTmpData->v_inside .resize (area); double * inside= &pOptionalTmpData->v_inside [0 ];
if (num_segments>max_num_intermediate_segments) num_segments = max_num_intermediate_segments ;
else if (num_segments< 0 ) num_segments= 0 ;
double vmin = DBL_MAX ;
unsigned int i ;
if (firstRun) {
// static init here
firstRun = false ;
int cnt = 0 ;
for (int seg = 0 ; seg <= max_num_intermediate_segments; seg++) {
start_num_segment_index[seg] = cnt;
for (int i = 1 ; i <= seg; i++) {
float t = (float )i/(float )(seg+1 );
float u = float (1.0 ) - t;
ImVec4& W = weights[cnt];
W.x =u*u;
W.y =float (2 )*u*t;
W.z =t*t;
W.w =float (0 ); // optional
++cnt;
}
}
// Compute outside = edtaa3(bitmap); % Transform background (0's)
computegradient ( data, width, height, gx, gy);
edtaa3 (data, gx, gy, width, height, xdist, ydist, outside);
for ( i=0 ; i<area; ++i) {
if ( outside[i] < 0.0 ) outside[i] = 0.0 ;
}
float minSquaredDistance=FLT_MAX,tmp; // FLT_MAX is probably in <limits.h>
ImVec2 L = p0,tp;
for (int i = start_num_segment_index[num_segments],isz=i+num_segments; i < isz; i++) {
const ImVec4& W=weights[i];
tp.x = W.x *p0.x + W.y *p1.x + W.z *p2.x ;
tp.y = W.x *p0.y + W.y *p1.y + W.z *p2.y ;
tmp = GetSquaredDistancePointSegment (point,L,tp,eps);
if (minSquaredDistance>=tmp) {minSquaredDistance=tmp;if (pChk0) *pChk0=L;if (pChk1) *pChk1=tp;}
L=tp;
// Compute inside = edtaa3(1-bitmap); % Transform foreground (1's)
memset ( gx, 0 , sizeof (double )*area );
memset ( gy, 0 , sizeof (double )*area );
for ( i=0 ; i<area; ++i) data[i] = 1 - data[i];
computegradient ( data, width, height, gx, gy );
edtaa3 ( data, gx, gy, width, height, xdist, ydist, inside );
for ( i=0 ; i<area; ++i ) {
if ( inside[i] < 0 ) inside[i] = 0.0 ;
}
tp = p2;
tmp = GetSquaredDistancePointSegment (point,L,tp,eps);
if (minSquaredDistance>tmp) {minSquaredDistance=tmp;if (pChk0) *pChk0=L;if (pChk1) *pChk1=tp;}
return minSquaredDistance;
}
inline static float GetSquaredDistanceToCubicBezierCurve (const ImVec2& point,const ImVec2& p0,const ImVec2& p1,const ImVec2& p2,const ImVec2& p3,int num_segments=2 ,ImVec2* pChk0=NULL ,ImVec2* pChk1=NULL ,float eps = float (0.0000001 )) {
static bool firstRun = true ;
static const int max_num_intermediate_segments = 25 ; // >0
static const int total_num_segment = (max_num_intermediate_segments+1 )*max_num_intermediate_segments/2 ;
static int start_num_segment_index[max_num_intermediate_segments+1 ];
static ImVec4 weights[total_num_segment];
if (num_segments>max_num_intermediate_segments) num_segments = max_num_intermediate_segments;
else if (num_segments<0 ) num_segments=0 ;
if (firstRun) {
// static init here
firstRun = false ;
int cnt = 0 ;
for (int seg = 0 ; seg <= max_num_intermediate_segments; seg++) {
start_num_segment_index[seg] = cnt;
for (int i = 1 ; i <= seg; i++) {
float t = (float )i/(float )(seg+1 );
float u = float (1.0 ) - t;
ImVec4& W = weights[cnt];
W.x =u*u*u;
W.y =float (3 )*u*u*t;
W.z =float (3 )*u*t*t;
W.w =t*t*t;
++cnt;
}
}
// distmap = outside - inside; % Bipolar distance field
for ( i=0 ; i<area; ++i) {
outside[i] -= inside[i];
if ( outside[i] < vmin ) vmin = outside[i];
}
float minSquaredDistance=FLT_MAX,tmp; // FLT_MAX is probably in <limits.h>
ImVec2 L = p0,tp;
for (int i = start_num_segment_index[num_segments],isz=i+num_segments; i < isz; i++) {
const ImVec4& W=weights[i];
tp.x = W.x *p0.x + W.y *p1.x + W.z *p2.x + W.w *p3.x ;
tp.y = W.x *p0.y + W.y *p1.y + W.z *p2.y + W.w *p3.y ;
vmin = fabs (vmin);
tmp = GetSquaredDistancePointSegment (point,L,tp,eps);
if (minSquaredDistance>tmp) {minSquaredDistance=tmp;if (pChk0) *pChk0=L;if (pChk1) *pChk1=tp;}
L=tp;
for ( i=0 ; i<area; ++i) {
double v = outside[i];
if ( v < -vmin) outside[i] = -vmin;
else if ( v > +vmin) outside[i] = +vmin;
data[i] = (outside[i]+vmin)/(2 *vmin);
}
tp = p3;
tmp = GetSquaredDistancePointSegment (point,L,tp,eps);
if (minSquaredDistance>tmp) {minSquaredDistance=tmp;if (pChk0) *pChk0=L;if (pChk1) *pChk1=tp;}
return minSquaredDistance ;
return data ;
}
// Usage of BezierChunk: use only the first 3 constructors and fill an array of BezierChunks, then call Init(...) on it and finally RasterizeGlyphShapeSDF(...)
struct BezierChunk {
enum Type {
TYPE_SEGMENT=0 ,
TYPE_QUADRATIC,
TYPE_CUBIC
};
BezierChunk (const ImVec2& _P0,const ImVec2& _P1) : P0(_P0),P1(_P1),num_segments(-1 ),length(-1 ),type(TYPE_SEGMENT) {}
BezierChunk (const ImVec2& _P0,const ImVec2& _C0,const ImVec2& _P1) : P0(_P0),C0(_C0),P1(_P1),num_segments(-1 ),length(-1 ),type(TYPE_QUADRATIC) {}
BezierChunk (const ImVec2& _P0,const ImVec2& _C0,const ImVec2& _C1,const ImVec2& _P1) : P0(_P0),C0(_C0),C1(_C1),P1(_P1),num_segments(-1 ),length(-1 ),type(TYPE_CUBIC) {}
ImVec2 P0,C0,C1,P1;
mutable int num_segments; // number of internal point to approximate TYPE_QUADRATIC and TYPE_CUBIC curves into straight segments
mutable float length;
Type type;
BezierChunk () : num_segments(-1 ),length(-1 ),type(TYPE_SEGMENT){}
inline static void Init (BezierChunk* chunksIn,int numChunksIn,float scale_flatness_in_pixels=float (0.75 )) {
IM_ASSERT (chunksIn && numChunksIn>0 );
IM_ASSERT (scale_flatness_in_pixels>0 );
for (int i=0 ;i<numChunksIn;i++) {
const BezierChunk& c = chunksIn[i];
if (c.type ==TYPE_SEGMENT) continue ; // c.length = GetSegmentLength(c.P0,c.P1);
else if (c.type ==TYPE_QUADRATIC) {
c.length = GetQuadraticBezierCurveLength (c.P0 ,c.C0 ,c.P1 ); // Good approximation, but might fail: c.length = NaN or infinite [Test case: P0=(5.9732384830713272,4.372880969196558) C0=(5.6681537218391895,4.372880969196558) P1=(5.3791260533034801,4.372880969196558)]
if (!(c.length >=0 ) || c.length >100000000000 )
c.length = GetQuadraticBezierCurveLengthAlternative (c.P0 ,c.C0 ,c.P1 ); // Fallback
IM_ASSERT (c.length >=0 ); //
}
else if (c.type ==TYPE_CUBIC) c.length = GetCubicBezierCurveLength (c.P0 ,c.C0 ,c.C1 ,c.P1 );
else continue ;
c.num_segments = c.length /scale_flatness_in_pixels; // Num intermediate points used to flatten the curve into line chunks
IM_ASSERT (c.num_segments >=0 );
// printf("length=%1.4f num_segments=%d\n",c.length,c.num_segments);
}
}
inline static ImVec2 AssignPoint (float unscaledPointX,float unscaledPointY,float scale_x=float (1 ),float scale_y=float(1 ),const ImVec2& postScaleOffset=ImVec2(0 ,0 )) {
return ImVec2 (
unscaledPointX*scale_x-postScaleOffset.x // -0.25f
,
(unscaledPointY*(-scale_y)-postScaleOffset.y )// -0.25f
);
}
static void RasterizeGlyphShapeSDF (
unsigned char * imageOutPxls,int imageOutW,int imageOutH,int imageOutStride,
const BezierChunk* chunksIn,int numChunksIn,const int * numChunksPerContourIn,int numChunksPerContourInSize,
const SdfExtraParams& sdExtraParams,float eps = float (0.0000001 )
)
{
IM_ASSERT (imageOutPxls && imageOutW>0 && imageOutH>0 && imageOutStride>0 );
IM_ASSERT (chunksIn && numChunksIn>0 && numChunksPerContourIn && numChunksPerContourInSize>0 );
// IM_ASSERT(scale!=0);
typedef struct floatPixel_ {
float d;bool isOutside;
floatPixel_ () : d(-1 ),isOutside(true ) {}
inline void process (float _d,bool _isOutside) {if (d<0 || _d<d) {d=_d;isOutside=_isOutside;}}
} floatPixel;
const float sdCenterValuePer255 = sdExtraParams.sdCenterValue *255 .0f ;
const float oneMinussdCenterValuePer255 = 255 .0f -sdCenterValuePer255;
const float sdExtentInPixelsSquared = sdExtraParams.sdExtentInPixels *sdExtraParams.sdExtentInPixels ;
for (int y=0 ;y<imageOutH;y++) {
unsigned char * ppxl = NULL ; // previous pixel
for (int x=0 ;x<imageOutW;x++) {
floatPixel fpxl;
ImVec2 P (round (x),round (y)),CHK0,CHK1;bool isOutside;
int chunkIndex=0 ;float accurateDistanceSquared (-1.0 );
for (int contourIndex=0 ;contourIndex<numChunksPerContourInSize;contourIndex++) {
const int numChunks = numChunksPerContourIn[contourIndex];
for (int maxChunkIndexOfThisContour=chunkIndex+numChunks;chunkIndex<maxChunkIndexOfThisContour;chunkIndex++) {
const BezierChunk& c = chunksIn[chunkIndex];
if (c.type ==TYPE_SEGMENT) accurateDistanceSquared = GetSquaredDistancePointSegment (P,c.P0 ,c.P1 ,eps);
else if (c.type ==TYPE_QUADRATIC) {
IM_ASSERT (c.num_segments >=0 ); // 'chunksIn' must be inited before calling this method
accurateDistanceSquared = GetSquaredDistanceToQuadraticBezierCurve (P,c.P0 ,c.C0 ,c.P1 ,c.num_segments ,&CHK0,&CHK1,eps);
}
else if (c.type ==TYPE_CUBIC) {
IM_ASSERT (c.num_segments >=0 ); // 'chunksIn' must be inited before calling this method
accurateDistanceSquared = GetSquaredDistanceToCubicBezierCurve (P,c.P0 ,c.C0 ,c.C1 ,c.P1 ,c.num_segments ,&CHK0,&CHK1,eps);
}
else {
IM_ASSERT (true ); // Why here?
continue ;
}
if (accurateDistanceSquared>=sdExtentInPixelsSquared) continue ;
if (fpxl.d >=0 && accurateDistanceSquared>fpxl.d ) continue ;
isOutside = true ;
if (c.type ==TYPE_SEGMENT) {
if (ImVec2Cross (ImVec2 (c.P1 .x -c.P0 .x ,c.P1 .y -c.P0 .y ),ImVec2 (P.x -c.P0 .x ,P.y -c.P0 .y ))>=float (0 )) isOutside = !isOutside;
}
else {
if (ImVec2Cross (ImVec2 (CHK1.x -CHK0.x ,CHK1.y -CHK0.y ),ImVec2 (P.x -CHK0.x ,P.y -CHK0.y ))>=float (0 )) isOutside = !isOutside;
}
fpxl.process (accurateDistanceSquared,isOutside);
}
}
// -------------------------------------------------------------
// Assign pxl in [0,255] from fpxl based on 'sdExtentInPixels' and 'sdCenterValue'
unsigned char & pxl = imageOutPxls[imageOutStride*y+x];
// IM_ASSERT(fpxl.d>=0);
if (fpxl.d <0 ) pxl = (!ppxl || (*ppxl<=sdCenterValuePer255)) ? 0 : 255 ;
else if (fpxl.d >=sdExtentInPixelsSquared) pxl = fpxl.isOutside ? 0 : 255 ;
else
{
fpxl.d = sqrt (fpxl.d ); // non-squared distance
if (fpxl.isOutside ) {
fpxl.d = (sdCenterValuePer255*(sdExtraParams.sdExtentInPixels -fpxl.d )/sdExtraParams.sdExtentInPixels ); // value in [0,sdCenterValuePer255]
IM_ASSERT (fpxl.d >=0 && fpxl.d <=sdCenterValuePer255);
IM_ASSERT (fpxl.d >=0 && fpxl.d <=255 );
pxl = (unsigned char ) fpxl.d ;
}
else {// inside
fpxl.d = (sdCenterValuePer255+oneMinussdCenterValuePer255*(sdExtraParams.sdExtentInPixels -fpxl.d )/sdExtraParams.sdExtentInPixels ); // value in [sdCenterValuePer255,sdCenterValuePer255+oneMinussdCenterValuePer255]
IM_ASSERT (fpxl.d >=sdCenterValuePer255 && fpxl.d <=sdCenterValuePer255+oneMinussdCenterValuePer255);
IM_ASSERT (fpxl.d >=0 && fpxl.d <=255 );
pxl = (unsigned char ) fpxl.d ;
}
}
ppxl = &pxl;
// ---------------------------------------------------------------
}
}
}
};
typedef ImVector<BezierChunk> BezierChunkVector;
typedef ImVector<int > IntVector;
#ifdef __STB_INCLUDE_STB_TRUETYPE_H__
STBTT_DEF bool stbtt_RasterizeSD (stbtt__bitmap *result,float flatness_in_pixels, stbtt_vertex *vertices, int num_vertices, float scale_x,float scale_y, int x_off, int y_off,const SdfExtraParams& sdExtraParams,void *userdata=NULL )
static unsigned char * make_distance_mapb (const unsigned char *img,unsigned int width, unsigned int height, unsigned char * imgOutSameSize, TmpData* pOptionalTmpData=NULL )
{
const ImVec2 postScaleOffset (x_off,y_off );
IM_ASSERT (imgOutSameSize );
BezierChunkVector chunks;IntVector numChunksPerContour ;
if (!pOptionalTmpData) pOptionalTmpData=&tmpData ;
static const float eps = float (0.0000001 );
const unsigned int area = width * height;
IM_ASSERT (area>0 );
pOptionalTmpData->v_data .resize (area);
double * data = &pOptionalTmpData->v_data [0 ];
// Filling chunks and numChunksPerContour:
if (num_vertices<=0 || !vertices || scale_x==0 || scale_y==0 ) {
chunks.resize (0 );
numChunksPerContour.resize (0 );
return false ;
}
chunks.reserve (num_vertices);
numChunksPerContour.reserve (4 );
int cnt = 0 ;BezierChunk c;BezierChunk* ppc=NULL ;ImVec2 tmpP0;
for (int i=0 ;i<num_vertices;i++) {
const stbtt_vertex& v= vertices[i];
if (v.type ==STBTT_vmove) {
if (cnt>0 ) {
numChunksPerContour.push_back (cnt);
cnt=0 ;ppc=NULL ;
}
tmpP0 = BezierChunk::AssignPoint (v.x ,v.y ,scale_x,scale_y,postScaleOffset);
continue ;
}
else {
// Assign P1 and P0
c.P1 = BezierChunk::AssignPoint (v.x ,v.y ,scale_x,scale_y,postScaleOffset);
if (ppc) c.P0 = ppc->P1 ;
else {
c.P0 = tmpP0;
IM_ASSERT (cnt==0 );
IM_ASSERT (ppc==NULL );
}
if (AreFuzzyEqual (c.P0 ,c.P1 ,eps)) continue ; // Checks if P0==P1 and skips the chunk
}
c.type = (v.type ==STBTT_vcurve) ? BezierChunk::TYPE_QUADRATIC : BezierChunk::TYPE_SEGMENT;
if (v.type ==STBTT_vcurve) {
c.C0 = BezierChunk::AssignPoint (v.cx ,v.cy ,scale_x,scale_y,postScaleOffset);
if (AreFuzzyEqual (c.P0 ,c.P1 ,eps)) continue ; // Checks if P0==P1 and skips the chunk
if (AreFuzzyEqual (c.P0 ,c.C0 ,eps) || AreFuzzyEqual (c.C0 ,c.P1 ,eps)) c.type = BezierChunk::TYPE_SEGMENT; // Degrade to STBTT_vline
}
// -------------------------------------------------
// -------------------------------------------------
chunks.push_back (c);
ppc = &chunks[chunks.size ()-1 ];
++cnt;
unsigned int i;
IM_ASSERT (c.P0 .x ==ppc->P0 .x && c.P0 .y ==ppc->P0 .y && c.P1 .x ==ppc->P1 .x && c.P1 .y ==ppc->P1 .y && c.type ==ppc->type );
}
if (cnt>0 ) {
numChunksPerContour.push_back (cnt);
cnt=0 ;ppc=NULL ;
}
// find minimimum and maximum values
double img_min = DBL_MAX,img_max = DBL_MIN,v;
IM_ASSERT (chunks.size ()>0 && numChunksPerContour.size ()>0 );
BezierChunk::Init (&chunks[0 ],chunks.size (),flatness_in_pixels);
BezierChunk::RasterizeGlyphShapeSDF (result->pixels ,result->w ,result->h ,result->stride ,
&chunks[0 ],chunks.size (),&numChunksPerContour[0 ],numChunksPerContour.size (),
sdExtraParams,eps
);
for ( i=0 ; i<area; ++i) {
data[i] = v = img[i];
if (v > img_max) img_max = v;
if (v < img_min) img_min = v;
}
return true ;
}
#endif // __STB_INCLUDE_STB_TRUETYPE_H__
// Map values from 0 - 255 to 0.0 - 1.0
for ( i=0 ; i<area; ++i) data[i] = (img[i]-img_min)/img_max;
} // namespace ImGuiSdfGenerator
data = make_distance_mapd (data, width, height);
// map values from 0.0 - 1.0 to 0 - 255
for ( i=0 ; i<area; ++i) imgOutSameSize[i] = (unsigned char )(255 *(1 -data[i]));
#ifdef __STB_INCLUDE_STB_TRUETYPE_H__
extern " C" {
// These methods are mostly juat adapted from the ones inside stb_truetype.h
STBTT_DEF void stbtt_RasterizeSD (stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off,const ImGuiSdfGenerator::SdfExtraParams& sdExtraParams, void *userdata)
{
IM_ASSERT (shift_x==0 && shift_y==0 ); // We don't use them
ImGuiSdfGenerator::stbtt_RasterizeSD (result,flatness_in_pixels,vertices,num_verts,scale_x,scale_y,x_off,y_off,sdExtraParams,userdata);
return imgOutSameSize;
}
STBTT_DEF void stbtt_MakeSDGlyphBitmapSubpixel (const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph,const ImGuiSdfGenerator::SdfExtraParams& sdExtraParams,void * userdata)
{
int ix0,iy0;
stbtt_vertex *vertices;
int num_verts = stbtt_GetGlyphShape (info, glyph, &vertices);
stbtt__bitmap gbm;
} // ImGuiEdtaaHelper
stbtt_GetGlyphBitmapBoxSubpixel (info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0 ,0 );
gbm.pixels = output;
gbm.w = out_w;
gbm.h = out_h;
gbm.stride = out_stride;
if (gbm.w && gbm.h ) stbtt_RasterizeSD (&gbm, sdExtraParams.sdFlatnessInPixels , vertices, num_verts, scale_x, scale_y,shift_x,shift_y, ix0,iy0, sdExtraParams,info->userdata );
STBTT_free (vertices, info->userdata );
namespace ImGui {
void ProcessAlpha8ImageForSignedDistanceFontEffect (unsigned char * pixels,int w,int h) {
IM_ASSERT (pixels && w>0 && h>0 );
ImVector<unsigned char > tmpData;tmpData.resize (w*h);
ImGuiEdtaaHelper::make_distance_mapb (pixels,w,h,&tmpData[0 ]);
ImGuiEdtaaHelper::FreeTmpMemory ();
memcpy (pixels,&tmpData[0 ],w*h);
}
// rects array must be big enough to accommodate all characters in the given ranges
STBTT_DEF int stbtt_PackSDFontRangesRenderIntoRects (stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects,const ImGuiSdfGenerator::SdfExtraParams& sdExtraParams)
{
int i,j,k, return_value = 1 ;
// save current values
int old_h_over = spc->h_oversample ;
int old_v_over = spc->v_oversample ;
k = 0 ;
for (i=0 ; i < num_ranges; ++i) {
float fh = ranges[i].font_size ;
float scale = fh > 0 ? stbtt_ScaleForPixelHeight (info, fh) : stbtt_ScaleForMappingEmToPixels (info, -fh);
float recip_h,recip_v,sub_x,sub_y;
spc->h_oversample = ranges[i].h_oversample ;
spc->v_oversample = ranges[i].v_oversample ;
recip_h = 1 .0f / spc->h_oversample ;
recip_v = 1 .0f / spc->v_oversample ;
sub_x = stbtt__oversample_shift (spc->h_oversample );
sub_y = stbtt__oversample_shift (spc->v_oversample );
for (j=0 ; j < ranges[i].num_chars ; ++j) {
stbrp_rect *r = &rects[k];
if (r->was_packed ) {
stbtt_packedchar *bc = &ranges[i].chardata_for_range [j];
int advance, lsb, x0,y0,x1,y1;
int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints [j];
int glyph = stbtt_FindGlyphIndex (info, codepoint);
stbrp_coord pad = (stbrp_coord) spc->padding ;
// pad on left and top
r->x += pad;
r->y += pad;
r->w -= pad;
r->h -= pad;
stbtt_GetGlyphHMetrics (info, glyph, &advance, &lsb);
stbtt_GetGlyphBitmapBox (info, glyph,
scale * spc->h_oversample ,
scale * spc->v_oversample ,
&x0,&y0,&x1,&y1);
// new code -------------------------------------------------------------
stbtt_MakeSDGlyphBitmapSubpixel (info,
spc->pixels + r->x + r->y *spc->stride_in_bytes ,
r->w - spc->h_oversample +1 ,
r->h - spc->v_oversample +1 ,
spc->stride_in_bytes ,
scale * spc->h_oversample ,
scale * spc->v_oversample ,
0 ,0 ,
glyph,
sdExtraParams,
NULL
);
// -------------------------------------------------------------------------
if (spc->h_oversample > 1 )
stbtt__h_prefilter (spc->pixels + r->x + r->y *spc->stride_in_bytes ,
r->w , r->h , spc->stride_in_bytes ,
spc->h_oversample );
if (spc->v_oversample > 1 )
stbtt__v_prefilter (spc->pixels + r->x + r->y *spc->stride_in_bytes ,
r->w , r->h , spc->stride_in_bytes ,
spc->v_oversample );
bc->x0 = (stbtt_int16) r->x ;
bc->y0 = (stbtt_int16) r->y ;
bc->x1 = (stbtt_int16) (r->x + r->w );
bc->y1 = (stbtt_int16) (r->y + r->h );
bc->xadvance = scale * advance;
bc->xoff = (float ) x0 * recip_h + sub_x;
bc->yoff = (float ) y0 * recip_v + sub_y;
bc->xoff2 = (x0 + r->w ) * recip_h + sub_x;
bc->yoff2 = (y0 + r->h ) * recip_v + sub_y;
} else {
return_value = 0 ; // if any fail, report failure
}
++k;
}
}
// restore original values
spc->h_oversample = old_h_over;
spc->v_oversample = old_v_over;
return return_value;
}
void PostBuildForSignedDistanceFontEffect (ImFontAtlas* atlas) {
if (!atlas->TexPixelsAlpha8 ) atlas->GetTexDataAsAlpha8 (&atlas->TexPixelsAlpha8 ,NULL ,NULL );
ProcessAlpha8ImageForSignedDistanceFontEffect (atlas->TexPixelsAlpha8 ,atlas->TexWidth ,atlas->TexHeight );
}
#endif // __STB_INCLUDE_STB_TRUETYPE_H__
// This is ImFontAtlas::Build() with just one line removed and two more added (but now the atlas is an argument...)
bool BuildSignedDistanceFontAtlas (ImFontAtlas* atlas)
{
IM_ASSERT (atlas->ConfigData .Size > 0 );
atlas->TexID = NULL ;
atlas->TexWidth = atlas->TexHeight = 0 ;
atlas->TexUvWhitePixel = ImVec2 (0 , 0 );
atlas->ClearTexData ();
struct ImFontTempBuildData
{
stbtt_fontinfo FontInfo;
stbrp_rect* Rects;
stbtt_pack_range* Ranges;
int RangesCount;
};
ImFontTempBuildData* tmp_array = (ImFontTempBuildData*)ImGui::MemAlloc ((size_t )atlas->ConfigData .Size * sizeof (ImFontTempBuildData));
// Initialize font information early (so we can error without any cleanup) + count glyphs
int total_glyph_count = 0 ;
int total_glyph_range_count = 0 ;
for (int input_i = 0 ; input_i < atlas->ConfigData .Size ; input_i++)
{
ImFontConfig& cfg = atlas->ConfigData [input_i];
ImFontTempBuildData& tmp = tmp_array[input_i];
IM_ASSERT (cfg.DstFont && (!cfg.DstFont ->IsLoaded () || cfg.DstFont ->ContainerAtlas == atlas));
const int font_offset = stbtt_GetFontOffsetForIndex ((unsigned char *)cfg.FontData , cfg.FontNo );
IM_ASSERT (font_offset >= 0 );
if (!stbtt_InitFont (&tmp.FontInfo , (unsigned char *)cfg.FontData , font_offset))
return false ;
// Count glyphs
if (!cfg.GlyphRanges )
cfg.GlyphRanges = atlas->GetGlyphRangesDefault ();
for (const ImWchar* in_range = cfg.GlyphRanges ; in_range[0 ] && in_range[1 ]; in_range += 2 )
{
total_glyph_count += (in_range[1 ] - in_range[0 ]) + 1 ;
total_glyph_range_count++;
}
}
// Start packing. We need a known width for the skyline algorithm. Using a cheap heuristic here to decide of width. User can override TexDesiredWidth if they wish.
// After packing is done, width shouldn't matter much, but some API/GPU have texture size limitations and increasing width can decrease height.
atlas->TexWidth = (atlas->TexDesiredWidth > 0 ) ? atlas->TexDesiredWidth : (total_glyph_count > 4000 ) ? 4096 : (total_glyph_count > 2000 ) ? 2048 : (total_glyph_count > 1000 ) ? 1024 : 512 ;
atlas->TexHeight = 0 ;
const int max_tex_height = 1024 *32 ;
stbtt_pack_context spc;
stbtt_PackBegin (&spc, NULL , atlas->TexWidth , max_tex_height, 0 , 1 , NULL );
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
ImVector<stbrp_rect> extra_rects;
atlas->RenderCustomTexData (0 , &extra_rects);
stbtt_PackSetOversampling (&spc, 1 , 1 );
stbrp_pack_rects ((stbrp_context*)spc.pack_info , &extra_rects[0 ], extra_rects.Size );
for (int i = 0 ; i < extra_rects.Size ; i++)
if (extra_rects[i].was_packed )
atlas->TexHeight = ImMax (atlas->TexHeight , extra_rects[i].y + extra_rects[i].h );
// Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0)
int buf_packedchars_n = 0 , buf_rects_n = 0 , buf_ranges_n = 0 ;
stbtt_packedchar* buf_packedchars = (stbtt_packedchar*)ImGui::MemAlloc (total_glyph_count * sizeof (stbtt_packedchar));
stbrp_rect* buf_rects = (stbrp_rect*)ImGui::MemAlloc (total_glyph_count * sizeof (stbrp_rect));
stbtt_pack_range* buf_ranges = (stbtt_pack_range*)ImGui::MemAlloc (total_glyph_range_count * sizeof (stbtt_pack_range));
memset (buf_packedchars, 0 , total_glyph_count * sizeof (stbtt_packedchar));
memset (buf_rects, 0 , total_glyph_count * sizeof (stbrp_rect)); // Unnecessary but let's clear this for the sake of sanity.
memset (buf_ranges, 0 , total_glyph_range_count * sizeof (stbtt_pack_range));
// First font pass: pack all glyphs (no rendering at this point, we are working with rectangles in an infinitely tall texture at this point)
for (int input_i = 0 ; input_i < atlas->ConfigData .Size ; input_i++)
{
ImFontConfig& cfg = atlas->ConfigData [input_i];
ImFontTempBuildData& tmp = tmp_array[input_i];
// Setup ranges
int glyph_count = 0 ;
int glyph_ranges_count = 0 ;
for (const ImWchar* in_range = cfg.GlyphRanges ; in_range[0 ] && in_range[1 ]; in_range += 2 )
{
glyph_count += (in_range[1 ] - in_range[0 ]) + 1 ;
glyph_ranges_count++;
}
tmp.Ranges = buf_ranges + buf_ranges_n;
tmp.RangesCount = glyph_ranges_count;
buf_ranges_n += glyph_ranges_count;
for (int i = 0 ; i < glyph_ranges_count; i++)
{
const ImWchar* in_range = &cfg.GlyphRanges [i * 2 ];
stbtt_pack_range& range = tmp.Ranges [i];
range.font_size = cfg.SizePixels ;
range.first_unicode_codepoint_in_range = in_range[0 ];
range.num_chars = (in_range[1 ] - in_range[0 ]) + 1 ;
range.chardata_for_range = buf_packedchars + buf_packedchars_n;
buf_packedchars_n += range.num_chars ;
}
// Pack
tmp.Rects = buf_rects + buf_rects_n;
buf_rects_n += glyph_count;
stbtt_PackSetOversampling (&spc, cfg.OversampleH , cfg.OversampleV );
int n = stbtt_PackFontRangesGatherRects (&spc, &tmp.FontInfo , tmp.Ranges , tmp.RangesCount , tmp.Rects );
stbrp_pack_rects ((stbrp_context*)spc.pack_info , tmp.Rects , n);
// Extend texture height
for (int i = 0 ; i < n; i++)
if (tmp.Rects [i].was_packed )
atlas->TexHeight = ImMax (atlas->TexHeight , tmp.Rects [i].y + tmp.Rects [i].h );
}
IM_ASSERT (buf_rects_n == total_glyph_count);
IM_ASSERT (buf_packedchars_n == total_glyph_count);
IM_ASSERT (buf_ranges_n == total_glyph_range_count);
// Create texture
atlas->TexHeight = ImUpperPowerOfTwo (atlas->TexHeight );
atlas->TexPixelsAlpha8 = (unsigned char *)ImGui::MemAlloc (atlas->TexWidth * atlas->TexHeight );
memset (atlas->TexPixelsAlpha8 , 0 , atlas->TexWidth * atlas->TexHeight );
spc.pixels = atlas->TexPixelsAlpha8 ;
spc.height = atlas->TexHeight ;
// Second pass: render characters
for (int input_i = 0 ; input_i < atlas->ConfigData .Size ; input_i++)
{
ImFontConfig& cfg = atlas->ConfigData [input_i];
ImFontTempBuildData& tmp = tmp_array[input_i];
stbtt_PackSetOversampling (&spc, cfg.OversampleH , cfg.OversampleV );
// ------------------------------------------------------------------
// old code (1 line)
// stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects);
// new code
ImGuiSdfGenerator::SdfExtraParams sdExtraParams (&cfg);
# ifdef __STB_INCLUDE_STB_TRUETYPE_H__
stbtt_PackSDFontRangesRenderIntoRects (&spc, &tmp.FontInfo , tmp.Ranges , tmp.RangesCount , tmp.Rects , sdExtraParams);
# else // __STB_INCLUDE_STB_TRUETYPE_H__
# error only stb_truetype is supported ATM
# endif // __STB_INCLUDE_STB_TRUETYPE_H__
// ------------------------------------------------------------------
tmp.Rects = NULL ;
}
// End packing
stbtt_PackEnd (&spc);
ImGui::MemFree (buf_rects);
buf_rects = NULL ;
// Third pass: setup ImFont and glyphs for runtime
for (int input_i = 0 ; input_i < atlas->ConfigData .Size ; input_i++)
{
ImFontConfig& cfg = atlas->ConfigData [input_i];
ImFontTempBuildData& tmp = tmp_array[input_i];
ImFont* dst_font = cfg.DstFont ;
float font_scale = stbtt_ScaleForPixelHeight (&tmp.FontInfo , cfg.SizePixels );
int unscaled_ascent, unscaled_descent, unscaled_line_gap;
stbtt_GetFontVMetrics (&tmp.FontInfo , &unscaled_ascent, &unscaled_descent, &unscaled_line_gap);
float ascent = unscaled_ascent * font_scale;
float descent = unscaled_descent * font_scale;
if (!cfg.MergeMode )
{
dst_font->ContainerAtlas = atlas;
dst_font->ConfigData = &cfg;
dst_font->ConfigDataCount = 0 ;
dst_font->FontSize = cfg.SizePixels ;
dst_font->Ascent = ascent;
dst_font->Descent = descent;
dst_font->Glyphs .resize (0 );
}
dst_font->ConfigDataCount ++;
float off_y = (cfg.MergeMode && cfg.MergeGlyphCenterV ) ? (ascent - dst_font->Ascent ) * 0 .5f : 0 .0f ;
dst_font->FallbackGlyph = NULL ; // Always clear fallback so FindGlyph can return NULL. It will be set again in BuildLookupTable()
for (int i = 0 ; i < tmp.RangesCount ; i++)
{
stbtt_pack_range& range = tmp.Ranges [i];
for (int char_idx = 0 ; char_idx < range.num_chars ; char_idx += 1 )
{
const stbtt_packedchar& pc = range.chardata_for_range [char_idx];
if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1 )
continue ;
const int codepoint = range.first_unicode_codepoint_in_range + char_idx;
if (cfg.MergeMode && dst_font->FindGlyph ((unsigned short )codepoint))
continue ;
stbtt_aligned_quad q;
float dummy_x = 0 .0f , dummy_y = 0 .0f ;
stbtt_GetPackedQuad (range.chardata_for_range , atlas->TexWidth , atlas->TexHeight , char_idx, &dummy_x, &dummy_y, &q, 0 );
dst_font->Glyphs .resize (dst_font->Glyphs .Size + 1 );
ImFont::Glyph& glyph = dst_font->Glyphs .back ();
glyph.Codepoint = (ImWchar)codepoint;
glyph.X0 = q.x0 ; glyph.Y0 = q.y0 ; glyph.X1 = q.x1 ; glyph.Y1 = q.y1 ;
glyph.U0 = q.s0 ; glyph.V0 = q.t0 ; glyph.U1 = q.s1 ; glyph.V1 = q.t1 ;
glyph.Y0 += (float )(int )(dst_font->Ascent + off_y + 0 .5f );
glyph.Y1 += (float )(int )(dst_font->Ascent + off_y + 0 .5f );
glyph.XAdvance = (pc.xadvance + cfg.GlyphExtraSpacing .x ); // Bake spacing into XAdvance
if (cfg.PixelSnapH )
glyph.XAdvance = (float )(int )(glyph.XAdvance + 0 .5f );
}
}
cfg.DstFont ->BuildLookupTable ();
}
// Cleanup temporaries
ImGui::MemFree (buf_packedchars);
ImGui::MemFree (buf_ranges);
ImGui::MemFree (tmp_array);
// Render into our custom data block
atlas->RenderCustomTexData (1 , &extra_rects);
return true ;
}
}