// load a single glyph
static void LoadGlyph(GFont2D& Font, const FT_Face Face, const GInt32 GlyphIndex, const GReal Scale) {

	GInt32 loadFlags;
	GFontChar2D *c;
	GGlyphMetrics tmpMetrics;
	FT_Error error;
	GInt32 i, j, k0, k1, numVert;
	FT_SubGlyphRec subGlyph;
	GPoint2 p;
	GReal x, y;
	GVect<GReal, 2> v;

	#define Fixed1616ToReal(a) ((GReal)a / (GReal)65536)

	// we ignore transformation, scaling and we specify to not recursively load subglyphs
	loadFlags = FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM |
				FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_RECURSE | FT_LOAD_LINEAR_DESIGN;
	error = FT_Load_Glyph(Face, GlyphIndex, loadFlags);
	if (error)
		return;

	// check for a good vector format
	if ((Face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) && (Face->glyph->format != FT_GLYPH_FORMAT_PLOTTER) &&
		(Face->glyph->format != FT_GLYPH_FORMAT_COMPOSITE))
		return;

	// composite glyph, we have to load subglyphs
	if (Face->glyph->num_subglyphs > 0) {

		GDynArray<GSubChar2D> subChars(Face->glyph->num_subglyphs);

		for (i = 0; i < (GInt32)Face->glyph->num_subglyphs; i++) {
			subGlyph = Face->glyph->subglyphs[i];
			subChars[i].GlyphIndex = subGlyph.index;
			subChars[i].Flags = subGlyph.flags;
			// rotation and scale
			subChars[i].Transformation[G_X][G_X] = Fixed1616ToReal(subGlyph.transform.xx);
			subChars[i].Transformation[G_X][G_Y] = Fixed1616ToReal(subGlyph.transform.xy);
			// x position
			subChars[i].Transformation[G_X][G_Z] = subGlyph.arg1 * Scale;
			// rotation and scale
			subChars[i].Transformation[G_Y][G_X] = Fixed1616ToReal(subGlyph.transform.yx);
			subChars[i].Transformation[G_Y][G_Y] = Fixed1616ToReal(subGlyph.transform.yy);
			// y position
			subChars[i].Transformation[G_Y][G_Z] = subGlyph.arg2 * Scale;
			// last row is an identity
			subChars[i].Transformation[G_Z][G_X] = 0;
			subChars[i].Transformation[G_Z][G_Y] = 0;
			subChars[i].Transformation[G_Z][G_Z] = 1;
		}
		c = Font.AddChar(subChars);
	}
	// simple glyph, lets load contours
	else {
		GDynArray<GPoint2> tmpPoints;
		GDynArray<GInt32> tmpPointsFlags;
		GDynArray<GFontCharContour2D> tmpContours;

		k0 = 0;
		for (i = 0; i < Face->glyph->outline.n_contours; i++) {
			k1 = Face->glyph->outline.contours[i];
			// take into account number of vertices
			numVert = k1 - k0 + 1;
			// we discard a bad contour
			if (numVert < 3)
				continue;
			tmpPoints.resize(numVert);
			tmpPointsFlags.resize(numVert);
			// read i-th segment
			for (j = k0; j <= k1; j++) {
				x = (GReal)Face->glyph->outline.points[j].x;
				y = (GReal)Face->glyph->outline.points[j].y;
				tmpPoints[j - k0].Set(x * Scale, y * Scale);
				tmpPointsFlags[j - k0] = Face->glyph->outline.tags[j];
			}
			// create new contour class
			GFontCharContour2D newContour(tmpPoints, tmpPointsFlags);
			// if there was some 0-length segments, they are not inserted; if at the end we have
			// a bad contour, we can discard it
			if (newContour.PointsCount() >= 3)
				tmpContours.push_back(newContour);
			// next segment
			k0 = k1 + 1;
		}
		c = Font.AddChar(tmpContours);
	}

	if (c) {
		// copy metrics
		tmpMetrics.Height = (GReal)Face->glyph->metrics.height * Scale;
		tmpMetrics.Width = (GReal)Face->glyph->metrics.width * Scale;
		tmpMetrics.HoriAdvance = (GReal)Face->glyph->metrics.horiAdvance * Scale;
		tmpMetrics.HoriBearingX = (GReal)Face->glyph->metrics.horiBearingX * Scale;
		tmpMetrics.HoriBearingY = (GReal)Face->glyph->metrics.horiBearingY * Scale;
		tmpMetrics.VertAdvance = (GReal)Face->glyph->metrics.vertAdvance * Scale;
		tmpMetrics.VertBearingX = (GReal)Face->glyph->metrics.vertBearingX * Scale;
		tmpMetrics.VertBearingY = (GReal)Face->glyph->metrics.vertBearingY * Scale;
		c->SetMetrics(tmpMetrics);
		// load some basic informations
		c->SetLinearHoriAdvance((GReal)Face->glyph->linearHoriAdvance * Scale);
		c->SetLinearVertAdvance((GReal)Face->glyph->linearVertAdvance * Scale);
		v.Set((GReal)Face->glyph->advance.x * Scale, (GReal)Face->glyph->advance.y * Scale);
		c->SetAdvance(v);
		c->SetLSBDelta((GReal)Face->glyph->lsb_delta * Scale);
		c->SetRSBDelta((GReal)Face->glyph->rsb_delta * Scale);
		c->SetEvenOddFill((Face->glyph->outline.flags & FT_OUTLINE_EVEN_ODD_FILL) != 0);
	}
}
/**
- FUNCIÓ: ComponentLabeling
- FUNCIONALITAT: Calcula els components binaris (blobs) d'una imatge amb connectivitat a 8
- PARÀMETRES:
	- inputImage: image to segment (pixel values different than blobColor are treated as background)
	- maskImage: if not NULL, all the pixels equal to 0 in mask are skipped in input image
	- backgroundColor: color of background (ignored pixels)
	- blobs: blob vector destination
- RESULTAT:
	- 
- RESTRICCIONS:
	- 
- AUTOR: rborras
- DATA DE CREACIÓ: 2008/04/21
- MODIFICACIÓ: Data. Autor. Descripció.
- NOTA: Algorithm based on "A linear-time component labeling algorithm using contour tracing technique", 
		F.Chang et al
*/
bool ComponentLabeling(	IplImage* inputImage,
						IplImage* maskImage,
						unsigned char backgroundColor,
						Blob_vector &blobs )
{
	int i,j;
	// row major vector with visited points 
	bool *visitedPoints, *pVisitedPoints, internalContour, externalContour;
	unsigned char *pInputImage, *pMask, *pAboveInputImage, *pBelowInputImage,
				  *pAboveMask, *pBelowMask;
	int imageWidth, imageHeight, currentLabel, contourLabel;
	// row major vector with labelled image 
	t_labelType *labelledImage, *pLabels;
	//! current blob pointer
	CBlob *currentBlob;
	CvSize imageSizes;
	CvPoint currentPoint;

	// verify input image
	if( !CV_IS_IMAGE( inputImage ) )
		return false;

	// verify that input image and mask image has same size
	if( maskImage )
	{
		if( !CV_IS_IMAGE(maskImage) || 
			maskImage->width != inputImage->width || 
			maskImage->height != inputImage->height )
		return false;
	}
	else
	{
		pMask = NULL;
		pAboveMask = NULL;
		pBelowMask = NULL;
	}

	imageSizes = cvSize(inputImage->width,inputImage->height);
	
	imageWidth = inputImage->width;
	imageHeight = inputImage->height;

	// create auxiliary buffers
	labelledImage = (t_labelType*) malloc( inputImage->width * inputImage->height * sizeof(t_labelType) );
	visitedPoints = (bool*) malloc( inputImage->width * inputImage->height * sizeof(bool) );

	// initialize it to 0
	memset(labelledImage, 0, inputImage->width * inputImage->height * sizeof(t_labelType) ) ;
	memset(visitedPoints, false, inputImage->width * inputImage->height * sizeof(bool) ) ;

	// initialize pointers and label counter
	pLabels = labelledImage;
	pVisitedPoints = visitedPoints;
	currentLabel = 1;

	for (j = 0; j < imageHeight; j++ )
	{
		// don't verify if we area on first or last row, it will verified on pointer access
		pAboveInputImage = (unsigned char*) inputImage->imageData + (j-1) * inputImage->widthStep;
		pBelowInputImage = (unsigned char*) inputImage->imageData + (j+1) * inputImage->widthStep;
	
		pInputImage = (unsigned char*) inputImage->imageData + j * inputImage->widthStep;

		if( maskImage )
		{
			pMask = (unsigned char*) maskImage->imageData + j * maskImage->widthStep;
			// don't verify if we area on first or last row, it will verified on pointer access
			pAboveMask = (unsigned char*) maskImage->imageData + (j-1) * maskImage->widthStep;
			pBelowMask = (unsigned char*) maskImage->imageData + (j+1) * maskImage->widthStep;

		}
		
		for (i = 0; i < imageWidth; i++, pInputImage++, pMask++, pAboveInputImage++, pBelowInputImage++,
										 pAboveMask++, pBelowMask++ )
		{
			// ignore background pixels or 0 pixels in mask
			if ( (*pInputImage == backgroundColor) || (maskImage && *pMask == 0 ))
			{
				pLabels++;
				pVisitedPoints++;
				continue;
			}
			
			// new external contour: current label == 0 and above pixel is background
			if( j > 0 )
			{
				externalContour = ((*pAboveInputImage == backgroundColor) || 
								  (maskImage && *pAboveMask == 0)) && 
								  (*pLabels == 0);
			}
			else
				externalContour = (*pLabels == 0);

			// new internal contour: below pixel is background and not visited
			if( !externalContour && j < imageHeight - 1 )
			{
				internalContour = *pBelowInputImage == backgroundColor &&
								  !GET_BELOW_VISITEDPIXEL( pVisitedPoints, imageWidth);
			}
			else
			{
				internalContour = false;
			}
			
			
			if( externalContour )
			{
				currentPoint = cvPoint(i,j);
				// assign label to labelled image
				*pLabels = currentLabel;
				
				// create new blob
				currentBlob = new CBlob(currentLabel, currentPoint, imageSizes );

				// contour tracing with currentLabel
				contourTracing( inputImage, maskImage, currentPoint, 
								labelledImage, visitedPoints, 
								currentLabel, false, backgroundColor, currentBlob->GetExternalContour() );

				// add new created blob
				blobs.push_back(currentBlob);

				currentLabel++;
			}
			else 
			{
				if( internalContour )
				{
					currentPoint = cvPoint(i,j);

					if( *pLabels == 0 )
					{
						// take left neightbour value as current
						if( i > 0 )
							contourLabel = *(pLabels - 1);
					}
					else
					{
						contourLabel = *pLabels;
					}

					if(contourLabel>0)
					{
						currentBlob = blobs[contourLabel-1];
						CBlobContour newContour(currentPoint, currentBlob->GetStorage());
						

						// contour tracing with contourLabel
						contourTracing( inputImage, maskImage, currentPoint, labelledImage, visitedPoints,
										contourLabel, true, backgroundColor, &newContour ); 

						currentBlob->AddInternalContour( newContour );
					}
				}
				// neither internal nor external contour
				else
				{
					// take left neightbour value as current if it is not labelled
					if( i > 0 && *pLabels == 0 )
						*pLabels = *(pLabels - 1);
				}

			}
			
			pLabels++;
			pVisitedPoints++;

		}
	}


	// free auxiliary buffers
	free( labelledImage );
	free( visitedPoints );

	return true;
}
示例#3
0
文件: cff-parser.c 项目: caryll/otfcc
void cff_parseOutline(uint8_t *data, uint32_t len, cff_Index gsubr, cff_Index lsubr, cff_Stack *stack, void *outline,
                      cff_IOutlineBuilder methods, const otfcc_Options *options) {
	uint16_t gsubr_bias = compute_subr_bias(gsubr.count);
	uint16_t lsubr_bias = compute_subr_bias(lsubr.count);
	uint8_t *start = data;
	uint32_t advance, i, cnt_bezier;
	cff_Value val;

	void (*setWidth)(void *context, double width) = methods.setWidth;
	void (*newContour)(void *context) = methods.newContour;
	void (*lineTo)(void *context, double x1, double y1) = methods.lineTo;
	void (*curveTo)(void *context, double x1, double y1, double x2, double y2, double x3, double y3) = methods.curveTo;
	void (*setHint)(void *context, bool isVertical, double position, double width) = methods.setHint;
	void (*setMask)(void *context, bool isContourMask, bool *mask) = methods.setMask;
	double (*getrand)(void *context) = methods.getrand;

	if (!setWidth) setWidth = callback_nopSetWidth;
	if (!newContour) newContour = callback_nopNewContour;
	if (!lineTo) lineTo = callback_nopLineTo;
	if (!curveTo) curveTo = callback_nopCurveTo;
	if (!setHint) setHint = callback_nopsetHint;
	if (!setMask) setMask = callback_nopsetMask;
	if (!getrand) getrand = callback_nopgetrand;

	while (start < data + len) {
		advance = cff_decodeCS2Token(start, &val);

		switch (val.t) {
			case CS2_OPERATOR:
				switch (val.i) {
					case op_hstem:
					case op_vstem:
					case op_hstemhm:
					case op_vstemhm:
						if (stack->index % 2) setWidth(outline, stack->stack[0].d);
						stack->stem += stack->index >> 1;
						double hintBase = 0;
						for (uint16_t j = stack->index % 2; j < stack->index; j += 2) {
							double pos = stack->stack[j].d;
							double width = stack->stack[j + 1].d;
							setHint(outline, (val.i == op_vstem || val.i == op_vstemhm), pos + hintBase, width);
							hintBase += pos + width;
						}
						stack->index = 0;
						break;
					case op_hintmask:
					case op_cntrmask: {
						if (stack->index % 2) setWidth(outline, stack->stack[0].d);
						bool isVertical = stack->stem > 0;
						stack->stem += stack->index >> 1;
						double hintBase = 0;
						for (uint16_t j = stack->index % 2; j < stack->index; j += 2) {
							double pos = stack->stack[j].d;
							double width = stack->stack[j + 1].d;
							setHint(outline, isVertical, pos + hintBase, width);
							hintBase += pos + width;
						}
						uint32_t maskLength = (stack->stem + 7) >> 3;
						bool *mask;
						NEW(mask, stack->stem + 7);
						for (uint32_t byte = 0; byte < maskLength; byte++) {
							uint8_t maskByte = start[advance + byte];
							mask[(byte << 3) + 0] = maskByte >> 7 & 1;
							mask[(byte << 3) + 1] = maskByte >> 6 & 1;
							mask[(byte << 3) + 2] = maskByte >> 5 & 1;
							mask[(byte << 3) + 3] = maskByte >> 4 & 1;
							mask[(byte << 3) + 4] = maskByte >> 3 & 1;
							mask[(byte << 3) + 5] = maskByte >> 2 & 1;
							mask[(byte << 3) + 6] = maskByte >> 1 & 1;
							mask[(byte << 3) + 7] = maskByte >> 0 & 1;
						}
						setMask(outline, (val.i == op_cntrmask), mask);
						advance += maskLength;
						stack->index = 0;
						break;
					}

					case op_vmoveto: {
						CHECK_STACK_TOP(op_vmoveto, 1);
						if (stack->index > 1) setWidth(outline, stack->stack[stack->index - 2].d);
						newContour(outline);
						lineTo(outline, 0.0, stack->stack[stack->index - 1].d);
						stack->index = 0;
						break;
					}
					case op_rmoveto: {
						CHECK_STACK_TOP(op_rmoveto, 2);
						if (stack->index > 2) setWidth(outline, stack->stack[stack->index - 3].d);
						newContour(outline);
						lineTo(outline, stack->stack[stack->index - 2].d, stack->stack[stack->index - 1].d);
						stack->index = 0;
						break;
					}
					case op_hmoveto: {
						CHECK_STACK_TOP(op_hmoveto, 1);
						if (stack->index > 1) setWidth(outline, stack->stack[stack->index - 2].d);
						newContour(outline);
						lineTo(outline, stack->stack[stack->index - 1].d, 0.0);
						stack->index = 0;
						break;
					}
					case op_endchar: {
						if (stack->index > 0) setWidth(outline, stack->stack[stack->index - 1].d);
						break;
					}
					case op_rlineto: {
						for (i = 0; i < stack->index; i += 2)
							lineTo(outline, stack->stack[i].d, stack->stack[i + 1].d);
						stack->index = 0;
						break;
					}
					case op_vlineto: {
						if (stack->index % 2 == 1) {
							lineTo(outline, 0.0, stack->stack[0].d);
							for (i = 1; i < stack->index; i += 2) {
								lineTo(outline, stack->stack[i].d, 0.0);
								lineTo(outline, 0.0, stack->stack[i + 1].d);
							}
						} else {
							for (i = 0; i < stack->index; i += 2) {
								lineTo(outline, 0.0, stack->stack[i].d);
								lineTo(outline, stack->stack[i + 1].d, 0.0);
							}
						}
						stack->index = 0;
						break;
					}
					case op_hlineto: {
						if (stack->index % 2 == 1) {
							lineTo(outline, stack->stack[0].d, 0.0);
							for (i = 1; i < stack->index; i += 2) {
								lineTo(outline, 0.0, stack->stack[i].d);
								lineTo(outline, stack->stack[i + 1].d, 0.0);
							}
						} else {
							for (i = 0; i < stack->index; i += 2) {
								lineTo(outline, stack->stack[i].d, 0.0);
								lineTo(outline, 0.0, stack->stack[i + 1].d);
							}
						}
						stack->index = 0;
						break;
					}
					case op_rrcurveto: {
						for (i = 0; i < stack->index; i += 6)
							curveTo(outline, stack->stack[i].d, stack->stack[i + 1].d, stack->stack[i + 2].d,
							        stack->stack[i + 3].d, stack->stack[i + 4].d, stack->stack[i + 5].d);
						stack->index = 0;
						break;
					}
					case op_rcurveline: {
						for (i = 0; i < stack->index - 2; i += 6)
							curveTo(outline, stack->stack[i].d, stack->stack[i + 1].d, stack->stack[i + 2].d,
							        stack->stack[i + 3].d, stack->stack[i + 4].d, stack->stack[i + 5].d);
						lineTo(outline, stack->stack[stack->index - 2].d, stack->stack[stack->index - 1].d);
						stack->index = 0;
						break;
					}
					case op_rlinecurve: {
						for (i = 0; i < stack->index - 6; i += 2)
							lineTo(outline, stack->stack[i].d, stack->stack[i + 1].d);
						curveTo(outline, stack->stack[stack->index - 6].d, stack->stack[stack->index - 5].d,
						        stack->stack[stack->index - 4].d, stack->stack[stack->index - 3].d,
						        stack->stack[stack->index - 2].d, stack->stack[stack->index - 1].d);
						stack->index = 0;
						break;
					}
					case op_vvcurveto: {
						if (stack->index % 4 == 1) {
							curveTo(outline, stack->stack[0].d, stack->stack[1].d, stack->stack[2].d, stack->stack[3].d,
							        0.0, stack->stack[4].d);
							for (i = 5; i < stack->index; i += 4)
								curveTo(outline, 0.0, stack->stack[i].d, stack->stack[i + 1].d, stack->stack[i + 2].d,
								        0.0, stack->stack[i + 3].d);
						} else {
							for (i = 0; i < stack->index; i += 4)
								curveTo(outline, 0.0, stack->stack[i].d, stack->stack[i + 1].d, stack->stack[i + 2].d,
								        0.0, stack->stack[i + 3].d);
						}
						stack->index = 0;
						break;
					}
					case op_hhcurveto: {
						if (stack->index % 4 == 1) {
							curveTo(outline, stack->stack[1].d, stack->stack[0].d, stack->stack[2].d, stack->stack[3].d,
							        stack->stack[4].d, 0.0);
							for (i = 5; i < stack->index; i += 4)
								curveTo(outline, stack->stack[i].d, 0.0, stack->stack[i + 1].d, stack->stack[i + 2].d,
								        stack->stack[i + 3].d, 0.0);
						} else {
							for (i = 0; i < stack->index; i += 4)
								curveTo(outline, stack->stack[i].d, 0.0, stack->stack[i + 1].d, stack->stack[i + 2].d,
								        stack->stack[i + 3].d, 0.0);
						}
						stack->index = 0;
						break;
					}
					case op_vhcurveto: {
						if (stack->index % 4 == 1)
							cnt_bezier = (stack->index - 5) / 4;
						else
							cnt_bezier = (stack->index / 4);

						for (i = 0; i < 4 * cnt_bezier; i += 4) {
							if ((i / 4) % 2 == 0)
								curveTo(outline, 0.0, stack->stack[i].d, stack->stack[i + 1].d, stack->stack[i + 2].d,
								        stack->stack[i + 3].d, 0.0);
							else
								curveTo(outline, stack->stack[i].d, 0.0, stack->stack[i + 1].d, stack->stack[i + 2].d,
								        0.0, stack->stack[i + 3].d);
						}
						if (stack->index % 8 == 5) {
							curveTo(outline, 0.0, stack->stack[stack->index - 5].d, stack->stack[stack->index - 4].d,
							        stack->stack[stack->index - 3].d, stack->stack[stack->index - 2].d,
							        stack->stack[stack->index - 1].d);
						}
						if (stack->index % 8 == 1) {
							curveTo(outline, stack->stack[stack->index - 5].d, 0.0, stack->stack[stack->index - 4].d,
							        stack->stack[stack->index - 3].d, stack->stack[stack->index - 1].d,
							        stack->stack[stack->index - 2].d);
						}
						stack->index = 0;
						break;
					}
					case op_hvcurveto: {
						if (stack->index % 4 == 1)
							cnt_bezier = (stack->index - 5) / 4;
						else
							cnt_bezier = (stack->index / 4);

						for (i = 0; i < 4 * cnt_bezier; i += 4) {
							if ((i / 4) % 2 == 0)
								curveTo(outline, stack->stack[i].d, 0.0, stack->stack[i + 1].d, stack->stack[i + 2].d,
								        0.0, stack->stack[i + 3].d);
							else
								curveTo(outline, 0.0, stack->stack[i].d, stack->stack[i + 1].d, stack->stack[i + 2].d,
								        stack->stack[i + 3].d, 0.0);
						}

						if (stack->index % 8 == 5) {
							curveTo(outline, stack->stack[stack->index - 5].d, 0.0, stack->stack[stack->index - 4].d,
							        stack->stack[stack->index - 3].d, stack->stack[stack->index - 1].d,
							        stack->stack[stack->index - 2].d);
						}
						if (stack->index % 8 == 1) {
							curveTo(outline, 0.0, stack->stack[stack->index - 5].d, stack->stack[stack->index - 4].d,
							        stack->stack[stack->index - 3].d, stack->stack[stack->index - 2].d,
							        stack->stack[stack->index - 1].d);
						}
						stack->index = 0;
						break;
					}
					case op_hflex: {
						CHECK_STACK_TOP(op_hflex, 7);
						curveTo(outline, stack->stack[0].d, 0.0, stack->stack[1].d, stack->stack[2].d,
						        stack->stack[3].d, 0.0);
						curveTo(outline, stack->stack[4].d, 0.0, stack->stack[5].d, -stack->stack[2].d,
						        stack->stack[6].d, 0.0);
						stack->index = 0;
						break;
					}
					case op_flex: {
						CHECK_STACK_TOP(op_flex, 12);
						curveTo(outline, stack->stack[0].d, stack->stack[1].d, stack->stack[2].d, stack->stack[3].d,
						        stack->stack[4].d, stack->stack[5].d);
						curveTo(outline, stack->stack[6].d, stack->stack[7].d, stack->stack[8].d, stack->stack[9].d,
						        stack->stack[10].d, stack->stack[11].d);
						stack->index = 0;
						break;
					}
					case op_hflex1: {
						CHECK_STACK_TOP(op_hflex1, 9);
						curveTo(outline, stack->stack[0].d, stack->stack[1].d, stack->stack[2].d, stack->stack[3].d,
						        stack->stack[4].d, 0.0);
						curveTo(outline, stack->stack[5].d, 0.0, stack->stack[6].d, stack->stack[7].d,
						        stack->stack[8].d, -(stack->stack[1].d + stack->stack[3].d + stack->stack[7].d));
						stack->index = 0;
						break;
					}
					case op_flex1: {
						CHECK_STACK_TOP(op_flex1, 11);
						double dx = stack->stack[0].d + stack->stack[2].d + stack->stack[4].d + stack->stack[6].d +
						            stack->stack[8].d;
						double dy = stack->stack[1].d + stack->stack[3].d + stack->stack[5].d + stack->stack[7].d +
						            stack->stack[9].d;
						if (fabs(dx) > fabs(dy)) {
							dx = stack->stack[10].d;
							dy = -dy;
						} else {
							dx = -dx;
							dy = stack->stack[10].d;
						}
						curveTo(outline, stack->stack[0].d, stack->stack[1].d, stack->stack[2].d, stack->stack[3].d,
						        stack->stack[4].d, stack->stack[5].d);
						curveTo(outline, stack->stack[6].d, stack->stack[7].d, stack->stack[8].d, stack->stack[9].d, dx,
						        dy);
						stack->index = 0;
						break;
					}
					case op_and: {
						CHECK_STACK_TOP(op_and, 2);
						double num1 = stack->stack[stack->index - 1].d;
						double num2 = stack->stack[stack->index - 2].d;
						stack->stack[stack->index - 2].d = (num1 && num2) ? 1.0 : 0.0;
						stack->index -= 1;
						break;
					}
					case op_or: {
						CHECK_STACK_TOP(op_or, 2);
						double num1 = stack->stack[stack->index - 1].d;
						double num2 = stack->stack[stack->index - 2].d;
						stack->stack[stack->index - 2].d = (num1 || num2) ? 1.0 : 0.0;
						stack->index -= 1;
						break;
					}
					case op_not: {
						CHECK_STACK_TOP(op_not, 1);
						double num = stack->stack[stack->index - 1].d;
						stack->stack[stack->index - 1].d = num ? 0.0 : 1.0;
						break;
					}
					case op_abs: {
						CHECK_STACK_TOP(op_abs, 1);
						double num = stack->stack[stack->index - 1].d;
						stack->stack[stack->index - 1].d = (num < 0.0) ? -num : num;
						break;
					}
					case op_add: {
						CHECK_STACK_TOP(op_add, 2);
						double num1 = stack->stack[stack->index - 1].d;
						double num2 = stack->stack[stack->index - 2].d;
						stack->stack[stack->index - 2].d = num1 + num2;
						stack->index -= 1;
						break;
					}
					case op_sub: {
						CHECK_STACK_TOP(op_sub, 2);
						double num1 = stack->stack[stack->index - 2].d;
						double num2 = stack->stack[stack->index - 1].d;
						stack->stack[stack->index - 2].d = num1 - num2;
						stack->index -= 1;
						break;
					}
					case op_div: {
						CHECK_STACK_TOP(op_div, 2);
						double num1 = stack->stack[stack->index - 2].d;
						double num2 = stack->stack[stack->index - 1].d;
						stack->stack[stack->index - 2].d = num1 / num2;
						stack->index -= 1;
						break;
					}
					case op_neg: {
						CHECK_STACK_TOP(op_neg, 1);
						double num = stack->stack[stack->index - 1].d;
						stack->stack[stack->index - 1].d = -num;
						break;
					}
					case op_eq: {
						CHECK_STACK_TOP(op_eq, 2);
						double num1 = stack->stack[stack->index - 1].d;
						double num2 = stack->stack[stack->index - 2].d;
						stack->stack[stack->index - 2].d = (num1 == num2) ? 1.0 : 0.0;
						stack->index -= 1;
						break;
					}
					case op_drop: {
						CHECK_STACK_TOP(op_drop, 1);
						stack->index -= 1;
						break;
					}
					case op_put: {
						CHECK_STACK_TOP(op_put, 2);
						double val = stack->stack[stack->index - 2].d;
						int32_t i = (int32_t)stack->stack[stack->index - 1].d;
						stack->transient[i % type2_transient_array].d = val;
						stack->index -= 2;
						break;
					}
					case op_get: {
						CHECK_STACK_TOP(op_get, 1);
						int32_t i = (int32_t)stack->stack[stack->index - 1].d;
						stack->stack[stack->index - 1].d = stack->transient[i % type2_transient_array].d;
						break;
					}
					case op_ifelse: {
						CHECK_STACK_TOP(op_ifelse, 4);
						double v2 = stack->stack[stack->index - 1].d;
						double v1 = stack->stack[stack->index - 2].d;
						double s2 = stack->stack[stack->index - 3].d;
						double s1 = stack->stack[stack->index - 4].d;
						stack->stack[stack->index - 4].d = (v1 <= v2) ? s1 : s2;
						stack->index -= 3;
						break;
					}
					case op_random: {
						// Chosen from a fair dice
						// TODO: use a real randomizer
						stack->stack[stack->index].t = cff_DOUBLE;
						stack->stack[stack->index].d = getrand(outline);
						stack->index += 1;
						break;
					}
					case op_mul: {
						CHECK_STACK_TOP(op_mul, 2);
						double num1 = stack->stack[stack->index - 1].d;
						double num2 = stack->stack[stack->index - 2].d;
						stack->stack[stack->index - 2].d = num1 * num2;
						stack->index -= 1;
						break;
					}
					case op_sqrt: {
						CHECK_STACK_TOP(op_sqrt, 1);
						double num = stack->stack[stack->index - 1].d;
						stack->stack[stack->index - 1].d = sqrt(num);
						break;
					}
					case op_dup: {
						CHECK_STACK_TOP(op_dup, 1);
						stack->stack[stack->index] = stack->stack[stack->index - 1];
						stack->index += 1;
						break;
					}
					case op_exch: {
						CHECK_STACK_TOP(op_exch, 2);
						double num1 = stack->stack[stack->index - 1].d;
						double num2 = stack->stack[stack->index - 2].d;
						stack->stack[stack->index - 1].d = num2;
						stack->stack[stack->index - 2].d = num1;
						break;
					}
					case op_index: {
						CHECK_STACK_TOP(op_index, 2);
						uint8_t n = stack->index - 1;
						uint8_t j = n - 1 - (uint8_t)(stack->stack[n].d) % n;
						stack->stack[n] = stack->stack[j];
						break;
					}
					case op_roll: {
						CHECK_STACK_TOP(op_roll, 2);
						int32_t j = stack->stack[stack->index - 1].d;
						uint32_t n = stack->stack[stack->index - 2].d;
						CHECK_STACK_TOP(op_roll, 2 + n);
						j = -j % n;
						if (j < 0) j += n;
						if (!j) break;
						uint8_t last = stack->index - 3;
						uint8_t first = stack->index - 2 - n;

						reverseStack(stack, first, last);
						reverseStack(stack, last - j + 1, last);
						reverseStack(stack, first, last - j);
						stack->index -= 2;
						break;
					}
					case op_return:
						return;
					case op_callsubr: {
						CHECK_STACK_TOP(op_callsubr, 1);
						uint32_t subr = (uint32_t)stack->stack[--(stack->index)].d;
						cff_parseOutline(lsubr.data + lsubr.offset[lsubr_bias + subr] - 1,
						                 lsubr.offset[lsubr_bias + subr + 1] - lsubr.offset[lsubr_bias + subr], gsubr,
						                 lsubr, stack, outline, methods, options);
						break;
					}
					case op_callgsubr: {
						CHECK_STACK_TOP(op_callgsubr, 1);
						uint32_t subr = (uint32_t)stack->stack[--(stack->index)].d;
						cff_parseOutline(gsubr.data + gsubr.offset[gsubr_bias + subr] - 1,
						                 gsubr.offset[gsubr_bias + subr + 1] - gsubr.offset[gsubr_bias + subr], gsubr,
						                 lsubr, stack, outline, methods, options);
						break;
					}
				}
				break;
			case CS2_OPERAND:
			case CS2_FRACTION:
				stack->stack[(stack->index)++] = val;
				break;
		}

		start += advance;
	}
}