//
// Created by Matthew on 2024/1/4.
//
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_OUTLINE_H
#include FT_STROKER_H
#include FT_IMAGE_H
#include FT_BBOX_H

#include <locale>
#include <codecvt>
#include "CvText.h"
#include <opencv2/imgproc.hpp>

namespace cv {
	namespace ft {

		using namespace std;


#if (('1234' >> 24) == '1')
#elif (('4321' >> 24) == '1')
#define BIG_ENDIAN
#else
#error "Couldn't determine the endianness!"
#endif

		struct Vec2
		{
			Vec2() { }
			Vec2(float a, float b)
				: x(a), y(b) { }

			float x, y;
		};

		struct Rect
		{
			Rect() { }
			Rect(float left, float top, float right, float bottom)
				: xmin(left), xmax(right), ymin(top), ymax(bottom) { }

			void Include(const Vec2 &r)
			{
				xmin = MIN(xmin, r.x);
				ymin = MIN(ymin, r.y);
				xmax = MAX(xmax, r.x);
				ymax = MAX(ymax, r.y);
			}

			float Width() const { return xmax - xmin + 1; }
			float Height() const { return ymax - ymin + 1; }

			float xmin, xmax, ymin, ymax;
		};

		// A horizontal pixel span generated by the FreeType renderer.

		struct Span
		{
			Span() { }
			Span(int _x, int _y, int _width, int _coverage)
				: x(_x), y(_y), width(_width), coverage(_coverage) { }

			int x, y, width, coverage;
		};

		typedef std::vector<Span> Spans;

		// Each time the renderer calls us back we just push another span entry on
		// our list.
		void RasterCallback(const int y, const int count, const FT_Span * const spans, void * const user)
		{
			Spans *sptr = (Spans *)user;
			for (int i = 0; i < count; ++i)
				sptr->push_back(Span(spans[i].x, y, spans[i].len, spans[i].coverage));
		}

		// Set up the raster parameters and render the outline.
		void RenderSpans(FT_Library &library, FT_Outline * const outline, Spans *spans)
		{
			FT_Raster_Params params;
			memset(&params, 0, sizeof(params));
			params.flags = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT;
			params.gray_spans = RasterCallback;
			params.user = spans;

			FT_Outline_Render(library, outline, &params);
		}

		class FreeType2Impl : public FreeType2
		{
		public:
			FreeType2Impl();
			~FreeType2Impl();
			void loadFontData(String fontFileName, int idx);
			void setSplitNumber(int num);
			void putText(
				InputOutputArray img, const String& text, Point org,
				int fontHeight, Scalar color,
				int thickness, int line_type, bool bottomLeftOrigin, bool usingStroker
			);
			Size getTextSize(const String& text, int fontHeight, int thickness, CV_OUT int* baseLine);

		private:
			FT_Library       mLibrary;
			FT_Face          mFace;
			FT_Outline_Funcs mFn;

			bool             mIsFaceAvailable;
			int              mCtoL;

			void putTextBitmapMono(
				InputOutputArray img, const String& text, Point org,
				int fontHeight, Scalar color,
				int thickness, int line_type, bool bottomLeftOrigin
			);

			void putTextBitmapBlend(
				InputOutputArray img, const String& text, Point org,
				int fontHeight, Scalar color,
				int thickness, int line_type, bool bottomLeftOrigin
			);

			void putTextOutline(
				InputOutputArray img, const String& text, Point org,
				int fontHeight, Scalar color,
				int thickness, int line_type, bool bottomLeftOrigin
			);

			void putTextStroker(
				InputOutputArray img, const String& text, Point org,
				int fontHeight, Scalar color,
				int thickness, int line_type, bool bottomLeftOrigin
			);

			typedef void (putPixel_mono_fn)(Mat& _dst, const int _py, const int _px, const uint8_t *_col);
			putPixel_mono_fn putPixel_8UC1_mono;
			putPixel_mono_fn putPixel_8UC3_mono;
			putPixel_mono_fn putPixel_8UC4_mono;

			typedef void (putPixel_blend_fn)(Mat& _dst, const int _py, const int _px, const uint8_t *_col, const uint8_t alpha);
			putPixel_blend_fn putPixel_8UC1_blend;
			putPixel_blend_fn putPixel_8UC3_blend;
			putPixel_blend_fn putPixel_8UC4_blend;

			static int mvFn(const FT_Vector *to, void * user);
			static int lnFn(const FT_Vector *to, void * user);
			static int coFn(const FT_Vector *cnt, const FT_Vector *to, void * user);
			static int cuFn(const FT_Vector *cnt1, const FT_Vector *cnt2, const FT_Vector *to, void * user);

			/**
			 * Convert from FT_F26Dot6 to int(coodinate of OpenCV)
			 * (FT_F26Dot6 is signed 26.6 real)
			 */
			static int ftd(FT_F26Dot6 fixedInt) {
				if (fixedInt > 0) {
					return (fixedInt + 32) / 64;
				}
				else {
					return (fixedInt - 32) / 64;
				}
			}

			class PathUserData {
			private:
			public:
				PathUserData(InputOutputArray _img) : mImg(_img) {};

				InputOutputArray mImg;
				Scalar mColor;
				int    mThickness;
				int    mLine_type;
				FT_Vector        mOldP;
				int              mCtoL;
				std::vector < Point > mPts;
			};
		};

		FreeType2Impl::FreeType2Impl()
		{
			FT_Init_FreeType(&(this->mLibrary));

			mCtoL = 16;
			mFn.shift = 0;
			mFn.delta = 0;
			mFn.move_to = FreeType2Impl::mvFn;
			mFn.line_to = FreeType2Impl::lnFn;
			mFn.cubic_to = FreeType2Impl::cuFn;
			mFn.conic_to = FreeType2Impl::coFn;

			mIsFaceAvailable = false;
		}

		FreeType2Impl::~FreeType2Impl()
		{
			if (mIsFaceAvailable == true)
			{
#if 0
				hb_font_destroy(mHb_font);
#endif
				CV_Assert(!FT_Done_Face(mFace));
				mIsFaceAvailable = false;
			}
			CV_Assert(!FT_Done_FreeType(mLibrary));
		}

		void FreeType2Impl::loadFontData(String fontFileName, int idx)
		{
			CV_Assert(idx >= 0);
			if (mIsFaceAvailable == true)
			{
#if 0
				hb_font_destroy(mHb_font);
#endif
				CV_Assert(!FT_Done_Face(mFace));
			}

			mIsFaceAvailable = false;
			CV_Assert(!FT_New_Face(mLibrary, fontFileName.c_str(), static_cast<FT_Long>(idx), &(mFace)));

#if 0
			mHb_font = hb_ft_font_create(mFace, NULL);
			if (mHb_font == NULL)
			{
				CV_Assert(!FT_Done_Face(mFace));
				return;
			}
			CV_Assert(mHb_font != NULL);
#endif

			mIsFaceAvailable = true;
		}

		void FreeType2Impl::setSplitNumber(int num) {
			CV_Assert(num > 0);
			mCtoL = num;
		}

		void FreeType2Impl::putText(
			InputOutputArray _img, const String& _text, Point _org,
			int _fontHeight, Scalar _color,
			int _thickness, int _line_type, bool _bottomLeftOrigin, bool usingStroker
		)
		{
			CV_Assert(mIsFaceAvailable == true);
			CV_Assert(_img.empty() == false);
			CV_Assert(_img.isMat() == true);
			CV_Assert(_img.dims() == 2);
			CV_Assert((_img.type() == CV_8UC1) ||
				(_img.type() == CV_8UC3) ||
				(_img.type() == CV_8UC4));
			CV_Assert((_line_type == LINE_AA) ||
				(_line_type == LINE_4) ||
				(_line_type == LINE_8));
			CV_Assert(_fontHeight >= 0);

			if (_text.empty())
			{
				return;
			}
			if (_fontHeight == 0)
			{
				return;
			}

			CV_Assert(!FT_Set_Pixel_Sizes(mFace, _fontHeight, _fontHeight));

			if (_thickness < 0) // CV_FILLED
			{
				if (_line_type == LINE_AA) {
					putTextBitmapBlend(_img, _text, _org, _fontHeight, _color,
						_thickness, _line_type, _bottomLeftOrigin);
				}
				else {
					putTextBitmapMono(_img, _text, _org, _fontHeight, _color,
						_thickness, _line_type, _bottomLeftOrigin);
				}
			}
			else {
                if (usingStroker)
                {
                    putTextStroker(_img, _text, _org, _fontHeight, _color, _thickness, _line_type, _bottomLeftOrigin);
                }
                else
                {
                    putTextOutline(_img, _text, _org, _fontHeight, _color, _thickness, _line_type, _bottomLeftOrigin);
                }
			}
		}

		void FreeType2Impl::putTextOutline(
			InputOutputArray _img, const String& _text, Point _org,
			int _fontHeight, Scalar _color,
			int _thickness, int _line_type, bool _bottomLeftOrigin)
		{
#if 0
			hb_buffer_t *hb_buffer = hb_buffer_create();
			CV_Assert(hb_buffer != NULL);

			hb_buffer_add_utf8(hb_buffer, _text.c_str(), -1, 0, -1);
			hb_buffer_guess_segment_properties(hb_buffer);
			hb_shape(mHb_font, hb_buffer, NULL, 0);

			unsigned int textLen = 0;
			hb_glyph_info_t *info =
				hb_buffer_get_glyph_infos(hb_buffer, &textLen);
			CV_Assert(info != NULL);
#else

			std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
			wstring wstr = converter.from_bytes(_text);
#endif

			PathUserData *userData = new PathUserData(_img);
			userData->mColor = _color;
			userData->mCtoL = mCtoL;
			userData->mThickness = _thickness;
			userData->mLine_type = _line_type;

			// Initilize currentPosition ( in FreeType coordinates)
			FT_Vector currentPos = { 0, 0 };
			currentPos.x = _org.x * 64;
			currentPos.y = _org.y * 64;

			// Update currentPosition with bottomLeftOrigin ( in FreeType coordinates)
			if (_bottomLeftOrigin != true) {
				currentPos.y += _fontHeight * 64;
			}

#if defined(USING_HB)
			for (unsigned int i = 0; i < textLen; i++) {
				CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0));
#else
			for (unsigned int i = 0; i < wstr.size(); i++) {
				wchar_t ch = wstr[i];
				CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), 0));
#endif
				FT_GlyphSlot slot = mFace->glyph;
				FT_Outline outline = slot->outline;

				// Flip ( in FreeType coordinates )
				FT_Matrix mtx = { 1 << 16 , 0 , 0 , -(1 << 16) };
				FT_Outline_Transform(&outline, &mtx);

				// Move to current position ( in FreeType coordinates )
				FT_Outline_Translate(&outline, currentPos.x, currentPos.y);

				// Draw ( in FreeType coordinates )
				CV_Assert(!FT_Outline_Decompose(&outline, &mFn, (void*)userData));

				// Draw (Last Path) ( in FreeType coordinates )
				mvFn(NULL, (void*)userData);

				// Update current position ( in FreeType coordinates )
				currentPos.x += mFace->glyph->advance.x;
				currentPos.y += mFace->glyph->advance.y;
			}
			delete userData;
#if defined(USING_HB)
			hb_buffer_destroy(hb_buffer);
#endif 0
			}

		// https://freetype.org/freetype2/docs/tutorial/example2.cpp
		void FreeType2Impl::putTextStroker(
			InputOutputArray _img, const String& _text, Point _org,
			int _fontHeight, Scalar _color,
			int _thickness, int _line_type, bool _bottomLeftOrigin)
		{
#if 0
			hb_buffer_t *hb_buffer = hb_buffer_create();
			CV_Assert(hb_buffer != NULL);

			hb_buffer_add_utf8(hb_buffer, _text.c_str(), -1, 0, -1);
			hb_buffer_guess_segment_properties(hb_buffer);
			hb_shape(mHb_font, hb_buffer, NULL, 0);

			unsigned int textLen = 0;
			hb_glyph_info_t *info =
				hb_buffer_get_glyph_infos(hb_buffer, &textLen);
			CV_Assert(info != NULL);
#else
			std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
			wstring wstr = converter.from_bytes(_text);
#endif

			Mat& mat = _img.getMatRef();

			/*
			PathUserData *userData = new PathUserData(_img);
			userData->mColor = _color;
			userData->mCtoL = mCtoL;
			userData->mThickness = _thickness;
			userData->mLine_type = _line_type;
			*/
			int offsetY = 0;
			int imgHeight = mat.rows;
			// Initilize currentPosition ( in FreeType coordinates)
			FT_Vector currentPos = { 0, 0 };
			currentPos.x = _org.x * 64;
			currentPos.y = _org.y * 64;

			// Update currentPosition with bottomLeftOrigin ( in FreeType coordinates)
			if (_bottomLeftOrigin != true)
			{
				 currentPos.y += _fontHeight * 64;
				currentPos.y = imgHeight * 64 - currentPos.y;
			}

			// To Freetype coordinates

			FT_BBox bbox, glyph_bbox;

			FT_Vector pen{ 0, 0 };
			bbox.xMin = bbox.yMin = 32000;
			bbox.xMax = bbox.yMax = -32000;

			// Get some metrics of our image.
			int imgWidth = mat.cols;

			// _color.
			cv::Vec3b outlineColor = cv::Vec3b(255 - (uchar)_color[0], 255 - (uchar)_color[1], 255 - (uchar)_color[2]);
			cv::Vec3b fontColor = cv::Vec3b((uchar)_color[0], (uchar)_color[1], (uchar)_color[2]);

#if defined(USING_HB)
			for (unsigned int i = 0; i < textLen; i++)
			{
				CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0));
#else
			for (unsigned int i = 0; i < wstr.size(); i++)
			{
				FT_Set_Transform(mFace, 0, &pen);

				CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), FT_LOAD_RENDER));
#endif
                FT_GlyphSlot slot = mFace->glyph;

				FT_Glyph glyph = NULL;
				int error = FT_Get_Glyph(mFace->glyph, &glyph);
				if (error)
				{
					printf("FT_Get_Glyph error!\n");
					break;
				}

				FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);

				if (glyph_bbox.xMin < bbox.xMin)
					bbox.xMin = glyph_bbox.xMin;
				if (glyph_bbox.yMin < bbox.yMin)
					bbox.yMin = glyph_bbox.yMin;
				if (glyph_bbox.xMax > bbox.xMax)
					bbox.xMax = glyph_bbox.xMax;
				if (glyph_bbox.yMax > bbox.yMax)
					bbox.yMax = glyph_bbox.yMax;

				pen.x += slot->advance.x;
				pen.y += slot->advance.y;

			}

			currentPos.x -= bbox.xMin * 64;
			currentPos.y -= bbox.yMax * 64;
			int row, col;

#if defined(USING_HB)
			for (unsigned int i = 0; i < textLen; i++)
			{
				CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0));
#else
			for (unsigned int i = 0; i < wstr.size(); i++)
			{
				FT_Set_Transform(mFace, 0, &currentPos);

				CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), FT_LOAD_NO_BITMAP));
#endif
				FT_GlyphSlot slot = mFace->glyph;
				FT_Outline outline = slot->outline;

				// Flip ( in FreeType coordinates )
				FT_Matrix mtx = { 1 << 16 , 0 , 0 , -(1 << 16) };
				// FT_Outline_Transform(&outline, &mtx);

				// Move to current position ( in FreeType coordinates )
				FT_Outline_Translate(&outline, currentPos.x, currentPos.y);

				Spans spans;
				RenderSpans(mLibrary, &outline, &spans);

				// Next we need the spans for the outline.
				Spans outlineSpans;

				// Set up a stroker.
				FT_Stroker stroker = NULL;
				FT_Stroker_New(mLibrary, &stroker);
				FT_Stroker_Set(stroker, (int)(_thickness * 64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);

				FT_Glyph glyph = NULL;
				if (FT_Get_Glyph(slot, &glyph) == 0)
				{
					FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);

					FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1);
					// Again, this needs to be an outline to work.
					if (glyph->format == FT_GLYPH_FORMAT_OUTLINE)
					{
						// Render the outline spans to the span list
						FT_Outline *o = &reinterpret_cast<FT_OutlineGlyph>(glyph)->outline;
						RenderSpans(mLibrary, o, &outlineSpans);
					}

					// Clean up afterwards.
					FT_Stroker_Done(stroker);
					FT_Done_Glyph(glyph);

					// Now we need to put it all together.
					if (!spans.empty())
					{
						// Figure out what the bounding rect is for both the span lists.
						Rect rect(spans.front().x, spans.front().y, spans.front().x, spans.front().y);
						for (Spans::iterator s = spans.begin(); s != spans.end(); ++s)
						{
							rect.Include(Vec2(s->x, s->y));
							rect.Include(Vec2(s->x + s->width - 1, s->y));
						}
						for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s)
						{
							rect.Include(Vec2(s->x, s->y));
							rect.Include(Vec2(s->x + s->width - 1, s->y));
						}

						float bearingX = slot->metrics.horiBearingX >> 6;
						float bearingY = slot->metrics.horiBearingY >> 6;
						// float advance = slot->advance.x >> 6;

						offsetY = bbox.yMax - (glyph_bbox.yMax >> 6);
						// Loop over the outline spans and just draw them into the
						// image.
						for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s)
						{
							row = imgHeight - (s->y - (currentPos.y >> 6)) - bbox.yMax;
							col = s->x - (currentPos.x >> 6);
							if (row < 0 || row >= imgHeight)
							{
								continue;
							}
							for (int w = 0; w < s->width; ++w)
							{
                                col++;
                                if (col < 0 || col >= imgWidth)
                                {
                                    continue;
                                }
								// mat.at<Vec3b>(row, col) = outlineColor;
								putPixel_8UC3_blend(mat, row, col, &(outlineColor[0]), s->coverage);
							}
						}

						// Then loop over the regular glyph spans and blend them into
						// the image.
						for (Spans::iterator s = spans.begin(); s != spans.end(); ++s)
						{
							row = imgHeight - (s->y - (currentPos.y >> 6)) - bbox.yMax;
							col = s->x - (currentPos.x >> 6);
							if (row < 0 || row >= imgHeight)
							{
								continue;
							}
							for (int w = 0; w < s->width; ++w)
							{
                            	col++;
                                if (col < 0 || col >= imgWidth)
                                {
                                    continue;
                                }
								// mat.at<Vec3b>(row, col) = fontColor;
								putPixel_8UC3_blend(mat, row, col, &(fontColor[0]), s->coverage);
#if 0
								Pixel32 &dst =
									pxl[(int)(
									(imgHeight - 1 - (s->y - rect.ymin)) * imgWidth
										+ s->x - rect.xmin + w)];
								Pixel32 src = Pixel32(fontCol.r, fontCol.g, fontCol.b,
									s->coverage);
								dst.r = (int)(dst.r + ((src.r - dst.r) * src.a) / 255.0f);
								dst.g = (int)(dst.g + ((src.g - dst.g) * src.a) / 255.0f);
								dst.b = (int)(dst.b + ((src.b - dst.b) * src.a) / 255.0f);
								dst.a = MIN(255, dst.a + src.a);
#endif
							}
						}

					}
				}

#if 0
				// This is unused in this test but you would need this to draw
					// more than one glyph.
				float bearingX = face->glyph->metrics.horiBearingX >> 6;
				float bearingY = face->glyph->metrics.horiBearingY >> 6;

#endif
				// Update current position ( in FreeType coordinates )

				// float advance = mFace->glyph->advance.x >> 6;
				if (wstr[i] == ' ')
				{
					currentPos.x += mFace->glyph->advance.x << 1;
				}
				else
				{
					currentPos.x += mFace->glyph->advance.x;
				}
				currentPos.y += mFace->glyph->advance.y;
				
			}

#if defined(USING_HB)
			hb_buffer_destroy(hb_buffer);
#endif 0
		}

		Size FreeType2Impl::getTextSize(
			const String& _text,
			int _fontHeight,
			int _thickness,
			CV_OUT int* _baseLine)
		{
			if (_text.empty())
			{
				return Size(0, 0);
			}

			CV_Assert(_fontHeight >= 0);
			if (_fontHeight == 0)
			{
				return Size(0, 0);
			}

			CV_Assert(!FT_Set_Pixel_Sizes(mFace, _fontHeight, _fontHeight));

			FT_UInt       glyph_index;
			FT_Bool       use_kerning;
			FT_UInt       previous;

			use_kerning = FT_HAS_KERNING(mFace);
			previous = 0;

			FT_Vector currentPos = { 0, 0 };
			FT_Set_Transform(mFace, 0, &currentPos);
#if defined(USING_HB)
			hb_buffer_t *hb_buffer = hb_buffer_create();
			CV_Assert(hb_buffer != NULL);

			hb_buffer_add_utf8(hb_buffer, _text.c_str(), -1, 0, -1);
			hb_buffer_guess_segment_properties(hb_buffer);
			hb_shape(mHb_font, hb_buffer, NULL, 0);

			unsigned int textLen = 0;
			hb_glyph_info_t *info =
				hb_buffer_get_glyph_infos(hb_buffer, &textLen);
			CV_Assert(info != NULL);
#else
			std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
			wstring wstr = converter.from_bytes(_text);
#endif
			// Initilize BoundaryBox ( in OpenCV coordinates )
			int xMin = INT_MAX, yMin = INT_MAX;
			int xMax = INT_MIN, yMax = INT_MIN;

#if defined(USING_HB)
			for (unsigned int i = 0; i < textLen; i++) {
				glyph_index = info[i].codepoint;
				CV_Assert(!FT_Load_Glyph(mFace, glyph_index, 0));
#else
			for (unsigned int i = 0; i < wstr.size(); i++) {
				if (wstr[i] == '\r' || wstr[i] == '\n')
				{
					// xMin = cv::min(xMin, ftd(bbox.xMin));
					// xMax = cv::max(xMax, ftd(bbox.xMax));
					// yMin = cv::min(yMin, ftd(bbox.yMin));
					// yMax = cv::max(yMax, currentPos.y + (mFace->glyph->advance.y));
					continue;
				}
				// FT_Set_Transform(mFace, 0, &currentPos);
				glyph_index = FT_Get_Char_Index(mFace, wstr[i]);
				CV_Assert(!FT_Load_Glyph(mFace, glyph_index, 0));
#endif
				/* retrieve kerning distance and move pen position */
				if (use_kerning && previous)
				{
					FT_Vector  delta;
					FT_Get_Kerning(mFace, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
					currentPos.x += delta.x >> 6;
				}

				FT_GlyphSlot slot = mFace->glyph;
				FT_Outline outline = slot->outline;
				FT_BBox bbox;

				// Flip ( in FreeType coordinates )
				// FT_Matrix mtx = { 1 << 16 , 0 , 0 , -(1 << 16) };
				// FT_Outline_Transform(&outline, &mtx);

				// Move to current position ( in FreeType coordinates )
				FT_Outline_Translate(&outline, currentPos.x, currentPos.y);

				// Get BoundaryBox ( in FreeType coordinatrs )
				CV_Assert(!FT_Outline_Get_BBox(&outline, &bbox));

				// If codepoint is space(0x20), it has no glyph.
				// A dummy boundary box is needed when last code is space.
				bool isSpace = false;
				if (
					(bbox.xMin == 0) && (bbox.xMax == 0) &&
					(bbox.yMin == 0) && (bbox.yMax == 0)
					)
				{
					isSpace = true;
					bbox.xMin = currentPos.x;
					bbox.xMax = currentPos.x + ((mFace->glyph->advance.x << 1) + mFace->glyph->metrics.horiBearingX);
					bbox.yMin = yMin;
					bbox.yMax = yMax;
				}

				// Update current position ( in FreeType coordinates )
				currentPos.x += isSpace ? (mFace->glyph->advance.x << 1) : mFace->glyph->advance.x;
				currentPos.y += mFace->glyph->advance.y;

				// Update BoundaryBox ( in OpenCV coordinates )
				xMin = cv::min(xMin, ftd(bbox.xMin));
				xMax = cv::max(xMax, ftd(bbox.xMax));
				yMin = cv::min(yMin, ftd(bbox.yMin));
				yMax = cv::max(yMax, ftd(bbox.yMax));

				previous = glyph_index;
			}

#if defined(USING_HB)
			hb_buffer_destroy(hb_buffer);
#endif

			// Calcurate width/height/baseline ( in OpenCV coordinates )
			int width = xMax - xMin;
			int height = yMax;

			if (_thickness > 0) {
				width = cvRound(width + _thickness * 2);
				height = cvRound(height + _thickness * 1);
			}
			else {
				width = cvRound(width + 1);
				height = cvRound(height + 1);
			}

			if (_baseLine) {
				*_baseLine = -yMin;
			}

			return Size(width, height);
		}


		void FreeType2Impl::putPixel_8UC1_mono(Mat& _dst, const int _py, const int _px, const uint8_t *_col)
		{
			uint8_t* ptr = _dst.ptr<uint8_t>(_py, _px);
			(*ptr) = _col[0];
		}

		void FreeType2Impl::putPixel_8UC3_mono(Mat& _dst, const int _py, const int _px, const uint8_t *_col)
		{
			cv::Vec3b* ptr = _dst.ptr<cv::Vec3b>(_py, _px);
			(*ptr)[0] = _col[0];
			(*ptr)[1] = _col[1];
			(*ptr)[2] = _col[2];
		}

		void FreeType2Impl::putPixel_8UC4_mono(Mat& _dst, const int _py, const int _px, const uint8_t *_col)
		{
			cv::Vec4b* ptr = _dst.ptr<cv::Vec4b>(_py, _px);
			(*ptr)[0] = _col[0];
			(*ptr)[1] = _col[1];
			(*ptr)[2] = _col[2];
			(*ptr)[3] = _col[3];
		}

		void FreeType2Impl::putTextBitmapMono(
			InputOutputArray _img, const String& _text, Point _org,
			int _fontHeight, Scalar _color,
			int _thickness, int _line_type, bool _bottomLeftOrigin)
		{
			CV_Assert(_thickness < 0);
			CV_Assert(_line_type == LINE_4 || _line_type == LINE_8);

			Mat dst = _img.getMat();
#if defined(USING_HB)
			hb_buffer_t *hb_buffer = hb_buffer_create();
			CV_Assert(hb_buffer != NULL);

			hb_buffer_add_utf8(hb_buffer, _text.c_str(), -1, 0, -1);
			hb_buffer_guess_segment_properties(hb_buffer);
			hb_shape(mHb_font, hb_buffer, NULL, 0);

			unsigned int textLen = 0;
			hb_glyph_info_t *info =
				hb_buffer_get_glyph_infos(hb_buffer, &textLen);
			CV_Assert(info != NULL);
#else
			std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
			wstring wstr = converter.from_bytes(_text);
#endif

			_org.y += _fontHeight;
			if (_bottomLeftOrigin == true) {
				_org.y -= _fontHeight;
			}

			const uint8_t _colorUC8n[4] = {
					static_cast<uint8_t>(_color[0]),
					static_cast<uint8_t>(_color[1]),
					static_cast<uint8_t>(_color[2]),
					static_cast<uint8_t>(_color[3]) };

			void (cv::ft::FreeType2Impl::*putPixel)(Mat&, const int, const int, const uint8_t*) =
				(_img.type() == CV_8UC4) ? (&FreeType2Impl::putPixel_8UC4_mono) :
				(_img.type() == CV_8UC3) ? (&FreeType2Impl::putPixel_8UC3_mono) :
				(&FreeType2Impl::putPixel_8UC1_mono);

#if defined(USING_HB)
			for (unsigned int i = 0; i < textLen; i++) {
				CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0));
#else
			for (unsigned int i = 0; i < wstr.size(); i++) {
				CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), 0));
#endif
				CV_Assert(!FT_Render_Glyph(mFace->glyph, FT_RENDER_MODE_MONO));
				FT_Bitmap    *bmp = &(mFace->glyph->bitmap);

				Point gPos = _org;
				gPos.y -= (mFace->glyph->metrics.horiBearingY >> 6);
				gPos.x += (mFace->glyph->metrics.horiBearingX >> 6);

				for (int row = 0; row < (int)bmp->rows; row++) {
					if (gPos.y + row < 0) {
						continue;
					}
					if (gPos.y + row >= dst.rows) {
						break;
					}

					for (int col = 0; col < bmp->pitch; col++) {
						int cl = bmp->buffer[row * bmp->pitch + col];
						if (cl == 0) {
							continue;
						}
						for (int bit = 7; bit >= 0; bit--) {
							if (gPos.x + col * 8 + (7 - bit) < 0)
							{
								continue;
							}
							if (gPos.x + col * 8 + (7 - bit) >= dst.cols)
							{
								break;
							}

							if (((cl >> bit) & 0x01) == 1) {
								(this->*putPixel)(dst, gPos.y + row, gPos.x + col * 8 + (7 - bit), _colorUC8n);
							}
						}
					}
				}

				_org.x += (mFace->glyph->advance.x) >> 6;
				_org.y += (mFace->glyph->advance.y) >> 6;
			}
#if defined(USING_HB)
			hb_buffer_destroy(hb_buffer);
#endif
			}

		// Alpha composite algorithm is porting from imgproc.
		// See https://github.com/opencv/opencv/blob/4.6.0/modules/imgproc/src/drawing.cpp
		// static void LineAA( Mat& img, Point2l pt1, Point2l pt2, const void* color )
		// ICV_PUT_POINT Macro.

		void FreeType2Impl::putPixel_8UC1_blend(Mat& _dst, const int _py, const int _px, const uint8_t *_col, const uint8_t alpha)
		{
			const int a = alpha;
			const int cb = _col[0];
			uint8_t* tptr = _dst.ptr<uint8_t>(_py, _px);

			int _cb = static_cast<int>(tptr[0]);
			_cb += ((cb - _cb)*a + 127) >> 8;
			_cb += ((cb - _cb)*a + 127) >> 8;

			tptr[0] = static_cast<uint8_t>(_cb);
		}

		void FreeType2Impl::putPixel_8UC3_blend(Mat& _dst, const int _py, const int _px, const uint8_t *_col, const uint8_t alpha)
		{
			const int a = alpha;
			const int cb = _col[0];
			const int cg = _col[1];
			const int cr = _col[2];
			uint8_t* tptr = _dst.ptr<uint8_t>(_py, _px);

			int _cb = static_cast<int>(tptr[0]);
			_cb += ((cb - _cb)*a + 127) >> 8;
			_cb += ((cb - _cb)*a + 127) >> 8;

			int _cg = static_cast<int>(tptr[1]);
			_cg += ((cg - _cg)*a + 127) >> 8;
			_cg += ((cg - _cg)*a + 127) >> 8;

			int _cr = static_cast<int>(tptr[2]);
			_cr += ((cr - _cr)*a + 127) >> 8;
			_cr += ((cr - _cr)*a + 127) >> 8;

			tptr[0] = static_cast<uint8_t>(_cb);
			tptr[1] = static_cast<uint8_t>(_cg);
			tptr[2] = static_cast<uint8_t>(_cr);
		}

		void FreeType2Impl::putPixel_8UC4_blend(Mat& _dst, const int _py, const int _px, const uint8_t *_col, const uint8_t alpha)
		{
			const uint8_t a = alpha;
			const int cb = _col[0];
			const int cg = _col[1];
			const int cr = _col[2];
			const int ca = _col[3];
			uint8_t* tptr = _dst.ptr<uint8_t>(_py, _px);

			int _cb = static_cast<int>(tptr[0]);
			_cb += ((cb - _cb)*a + 127) >> 8;
			_cb += ((cb - _cb)*a + 127) >> 8;

			int _cg = static_cast<int>(tptr[1]);
			_cg += ((cg - _cg)*a + 127) >> 8;
			_cg += ((cg - _cg)*a + 127) >> 8;

			int _cr = static_cast<int>(tptr[2]);
			_cr += ((cr - _cr)*a + 127) >> 8;
			_cr += ((cr - _cr)*a + 127) >> 8;

			int _ca = static_cast<int>(tptr[3]);
			_ca += ((ca - _ca)*a + 127) >> 8;
			_ca += ((ca - _ca)*a + 127) >> 8;

			tptr[0] = static_cast<uint8_t>(_cb);
			tptr[1] = static_cast<uint8_t>(_cg);
			tptr[2] = static_cast<uint8_t>(_cr);
			tptr[3] = static_cast<uint8_t>(_ca);
		}

		void FreeType2Impl::putTextBitmapBlend(
			InputOutputArray _img, const String& _text, Point _org,
			int _fontHeight, Scalar _color,
			int _thickness, int _line_type, bool _bottomLeftOrigin)
		{

			CV_Assert(_thickness < 0);
			CV_Assert(_line_type == LINE_AA);

			Mat dst = _img.getMat();
#if defined(USING_HB)
			hb_buffer_t *hb_buffer = hb_buffer_create();
			CV_Assert(hb_buffer != NULL);

			hb_buffer_add_utf8(hb_buffer, _text.c_str(), -1, 0, -1);
			hb_buffer_guess_segment_properties(hb_buffer);
			hb_shape(mHb_font, hb_buffer, NULL, 0);

			unsigned int textLen = 0;
			hb_glyph_info_t *info =
				hb_buffer_get_glyph_infos(hb_buffer, &textLen);
			CV_Assert(info != NULL);
#else
			std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
			wstring wstr = converter.from_bytes(_text);
#endif

			_org.y += _fontHeight;
			if (_bottomLeftOrigin == true) {
				_org.y -= _fontHeight;
			}

			const uint8_t _colorUC8n[4] = {
					static_cast<uint8_t>(_color[0]),
					static_cast<uint8_t>(_color[1]),
					static_cast<uint8_t>(_color[2]),
					static_cast<uint8_t>(_color[3]) };

			void (cv::ft::FreeType2Impl::*putPixel)(Mat&, const int, const int, const uint8_t*, const uint8_t) =
				(_img.type() == CV_8UC4) ? (&FreeType2Impl::putPixel_8UC4_blend) :
				(_img.type() == CV_8UC3) ? (&FreeType2Impl::putPixel_8UC3_blend) :
				(&FreeType2Impl::putPixel_8UC1_blend);

#if defined(USING_HB)
			for (unsigned int i = 0; i < textLen; i++) {
				CV_Assert(!FT_Load_Glyph(mFace, info[i].codepoint, 0));
#else
			for (unsigned int i = 0; i < wstr.size(); i++) {
				CV_Assert(!FT_Load_Glyph(mFace, FT_Get_Char_Index(mFace, wstr[i]), 0));
#endif
				CV_Assert(!FT_Render_Glyph(mFace->glyph, FT_RENDER_MODE_NORMAL));
				FT_Bitmap    *bmp = &(mFace->glyph->bitmap);

				Point gPos = _org;
				gPos.y -= (mFace->glyph->metrics.horiBearingY >> 6);
				gPos.x += (mFace->glyph->metrics.horiBearingX >> 6);

				for (int row = 0; row < (int)bmp->rows; row++) {
					if (gPos.y + row < 0) {
						continue;
					}
					if (gPos.y + row >= dst.rows) {
						break;
					}

					for (int col = 0; col < bmp->pitch; col++) {
						uint8_t cl = bmp->buffer[row * bmp->pitch + col];
						if (cl == 0) {
							continue;
						}
						if (gPos.x + col < 0)
						{
							continue;
						}
						if (gPos.x + col >= dst.cols)
						{
							break;
						}

						(this->*putPixel)(dst, gPos.y + row, gPos.x + col, _colorUC8n, cl);
					}
				}
				_org.x += (mFace->glyph->advance.x) >> 6;
				_org.y += (mFace->glyph->advance.y) >> 6;
			}

#if defined(USING_HB)
			hb_buffer_destroy(hb_buffer);
#endif
		}

		
		int FreeType2Impl::mvFn(const FT_Vector *to, void * user)
		{
			if (user == NULL) { return 1; }
			PathUserData *p = (PathUserData*)user;

			// Draw polylines( in OpenCV coordinates ).
			if (p->mPts.size() > 0) {
				Mat dst = p->mImg.getMat();
				const Point *ptsList[] = { &(p->mPts[0]) };
				int npt[1]; npt[0] = p->mPts.size();
				polylines(
					dst,
					ptsList,
					npt,
					1,
					false,
					p->mColor,
					p->mThickness,
					p->mLine_type,
					0
				);
			}

			p->mPts.clear();

			if (to == NULL) { return 1; }

			// Store points to draw( in OpenCV coordinates ).
			p->mPts.push_back(Point(ftd(to->x), ftd(to->y)));
			p->mOldP = *to;
			return 0;
		}

		int FreeType2Impl::lnFn(const FT_Vector *to, void * user)
		{
			if (to == NULL) { return 1; }
			if (user == NULL) { return 1; }

			PathUserData *p = (PathUserData *)user;

			// Store points to draw( in OpenCV coordinates ).
			p->mPts.push_back(Point(ftd(to->x), ftd(to->y)));
			p->mOldP = *to;
			return 0;
		}

		int FreeType2Impl::coFn(const FT_Vector *cnt,
			const FT_Vector *to,
			void * user)
		{
			if (cnt == NULL) { return 1; }
			if (to == NULL) { return 1; }
			if (user == NULL) { return 1; }

			PathUserData *p = (PathUserData *)user;

			// Bezier to Line
			for (int i = 0; i <= p->mCtoL; i++) {
				// Split Bezier to lines ( in FreeType coordinates ).
				double u = (double)i * 1.0 / (p->mCtoL);
				double nu = 1.0 - u;
				double p0 = nu * nu;
				double p1 = 2.0 * u *        nu;
				double p2 = u * u;

				double X = (p->mOldP.x) * p0 + cnt->x * p1 + to->x * p2;
				double Y = (p->mOldP.y) * p0 + cnt->y * p1 + to->y * p2;

				// Store points to draw( in OpenCV coordinates ).
				p->mPts.push_back(Point(ftd(X), ftd(Y)));
			}
			p->mOldP = *to;
			return 0;
		}

		int FreeType2Impl::cuFn(const FT_Vector *cnt1,
			const FT_Vector *cnt2,
			const FT_Vector *to,
			void * user)
		{
			if (cnt1 == NULL) { return 1; }
			if (cnt2 == NULL) { return 1; }
			if (to == NULL) { return 1; }
			if (user == NULL) { return 1; }

			PathUserData *p = (PathUserData *)user;

			// Bezier to Line
			for (int i = 0; i <= p->mCtoL; i++) {
				// Split Bezier to lines ( in FreeType coordinates ).
				double u = (double)i * 1.0 / (p->mCtoL);
				double nu = 1.0 - u;
				double p0 = nu * nu * nu;
				double p1 = 3.0 * u *        nu * nu;
				double p2 = 3.0 * u * u *    nu;
				double p3 = u * u * u;

				double X = (p->mOldP.x) * p0 + (cnt1->x)    * p1 +
					(cnt2->x) * p2 + (to->x)    * p3;
				double Y = (p->mOldP.y) * p0 + (cnt1->y)    * p1 +
					(cnt2->y) * p2 + (to->y)    * p3;

				// Store points to draw( in OpenCV coordinates ).
				p->mPts.push_back(Point(ftd(X), ftd(Y)));
			}
			p->mOldP = *to;
			return 0;
		}

		CV_EXPORTS_W Ptr<FreeType2> createFreeType2()
		{
			return Ptr<FreeType2Impl>(new FreeType2Impl());
		}


		}
		} // namespace freetype2
