コード例 #1
0
ファイル: ioscanvas.cpp プロジェクト: huangzongwu/touchvg
bool GiCanvasIos::drawImage(CGImageRef image, const Point2d& centerM, bool autoScale)
{
    CGContextRef context = m_draw->getContext();
    bool ret = false;
    
    if (context && image) {
        Point2d ptD = centerM * m_draw->xf().modelToDisplay();
        float w = CGImageGetWidth(image);
        float h = CGImageGetHeight(image);
        
        if (autoScale) {
            w *= m_draw->xf().getViewScale();
            h *= m_draw->xf().getViewScale();
        }
        
        CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, m_draw->height());
        af = CGAffineTransformTranslate(af, ptD.x - w * 0.5f, 
                                        m_draw->height() - (ptD.y + h * 0.5f));
        
        CGContextConcatCTM(context, af);
        CGContextDrawImage(context, CGRectMake(0, 0, w, h), image);
        CGContextConcatCTM(context, CGAffineTransformInvert(af));
        ret = true;
    }
    
    return ret;
}
コード例 #2
0
ファイル: ioscanvas.cpp プロジェクト: huangzongwu/touchvg
void GiCanvasIos::endPaint(bool draw)
{
    if (m_draw->getContext())
    {
        if (draw && m_draw->_buffctx && m_draw->_context) {
            CGContextRef context = m_draw->_context;
            CGImageRef image = CGBitmapContextCreateImage(m_draw->_buffctx);
            CGRect rect = CGRectMake(0, 0, m_draw->width(), m_draw->height()); // 逻辑宽高点数
            
            if (image) {
                CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, m_draw->height());
                CGContextConcatCTM(context, af);    // 图像是朝上的,上下文坐标系朝下,上下颠倒显示
                
                CGInterpolationQuality old = CGContextGetInterpolationQuality(context);
                CGContextSetInterpolationQuality(context, kCGInterpolationNone);
                CGContextDrawImage(context, rect, image);
                CGContextSetInterpolationQuality(context, old);
                
                CGContextConcatCTM(context, CGAffineTransformInvert(af));   // 恢复成坐标系朝下
                CGImageRelease(image);
            }
        }
        if (m_draw->_buffctx) {
            CGContextRelease(m_draw->_buffctx);
            m_draw->_buffctx = NULL;
        }
        m_draw->_context = NULL;
        if (owner())
            owner()->_endPaint();
    }
}
コード例 #3
0
ファイル: ioscanvas.cpp プロジェクト: huangzongwu/touchvg
bool GiCanvasIos::drawCachedBitmap2(const GiCanvas* p, float x, float y, bool secondBmp)
{
    bool ret = false;
    
    if (p && p->getCanvasType() == getCanvasType()) {
        GiCanvasIos* gs = (GiCanvasIos*)p;
        int index = secondBmp ? 1 : 0;
        CGImageRef image = gs->m_draw->_cacheserr[index] ? NULL : gs->m_draw->_caches[index];
        CGContextRef context = m_draw->getContext();
        
        if (context && image) {
            CGRect rect = CGRectMake(x, y, m_draw->width(), m_draw->height());
            CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, m_draw->height());
            
            CGContextConcatCTM(context, af);
            
            CGInterpolationQuality oldQuality = CGContextGetInterpolationQuality(context);
            CGContextSetInterpolationQuality(context, kCGInterpolationNone);
            CGContextDrawImage(context, rect, image);
            CGContextSetInterpolationQuality(context, oldQuality);
            
            CGContextConcatCTM(context, CGAffineTransformInvert(af));
            ret = true;
        }
    }
    
    return ret;
}
コード例 #4
0
ファイル: ioscanvas.cpp プロジェクト: huangzongwu/touchvg
CGImageRef GiCanvasIos::cachedBitmap(bool invert)
{
    CGImageRef image = m_draw->_caches[0];
    if (!image || !invert)
        return image;                       // 调用者不能释放图像
    
    size_t w = CGImageGetWidth(image);      // 图像宽度,像素单位,不是点单位
    size_t h = CGImageGetHeight(image);
    CGImageRef newimg = NULL;
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, w * 4,
                                                 colorSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);
    
    if (context) {
        CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, h);
        CGContextConcatCTM(context, af);    // 图像是朝上的,上下文坐标系朝下,上下颠倒显示
        CGContextDrawImage(context, CGRectMake(0, 0, w, h), image);
        CGContextConcatCTM(context, CGAffineTransformInvert(af));
    
        newimg = CGBitmapContextCreateImage(context);   // 得到上下颠倒的新图像
        CGContextRelease(context);
    }
    
    return newimg;                          // 由调用者释放图像, CGImageRelease
}
コード例 #5
0
void wkSetPatternPhaseInUserSpace(CGContextRef context, CGPoint phasePoint)
{
    CGAffineTransform userToBase = CGAffineTransformConcat(CGContextGetCTM(context),
        CGAffineTransformInvert(CGContextGetBaseCTM(context)));
    CGPoint phase = CGPointApplyAffineTransform(phasePoint, userToBase);
            
    CGContextSetPatternPhase(context, CGSizeMake(phase.x, phase.y));
}
コード例 #6
0
ファイル: os_custom.c プロジェクト: andreyvit/yoursway-swt
JNIEXPORT void JNICALL OS_NATIVE(CGAffineTransformInvert)
	(JNIEnv *env, jclass that, jfloatArray arg0, jfloatArray arg1)
{
	jfloat *lparg0=NULL;
	jfloat *lparg1=NULL;
	OS_NATIVE_ENTER(env, that, CGAffineTransformInvert_FUNC);
	if (arg0) if ((lparg0 = (*env)->GetFloatArrayElements(env, arg0, NULL)) == NULL) goto fail;
	if (arg1) if ((lparg1 = (*env)->GetFloatArrayElements(env, arg1, NULL)) == NULL) goto fail;
	*(CGAffineTransform *)lparg1 = CGAffineTransformInvert(*(CGAffineTransform *)lparg0);
fail:
	if (arg1 && lparg1) (*env)->ReleaseFloatArrayElements(env, arg1, lparg1, 0);
	if (arg0 && lparg0) (*env)->ReleaseFloatArrayElements(env, arg0, lparg0, JNI_ABORT);
	OS_NATIVE_EXIT(env, that, CGAffineTransformInvert_FUNC);
}
コード例 #7
0
ファイル: fx_quartz_device.cpp プロジェクト: 151706061/PDFium
CFX_QuartzDeviceDriver::CFX_QuartzDeviceDriver(CGContextRef context, FX_INT32 deviceClass)
{
    m_saveCount = 0;
    _context		= context;
    _deviceClass	= deviceClass;
    CGContextRetain(_context);
    CGRect r = CGContextGetClipBoundingBox(context);
    _width	= FXSYS_round(r.size.width);
    _height	= FXSYS_round(r.size.height);
    _renderCaps = FXRC_SOFT_CLIP | FXRC_BLEND_MODE |
                  FXRC_ALPHA_PATH | FXRC_ALPHA_IMAGE |
                  FXRC_BIT_MASK | FXRC_ALPHA_MASK;
    if (_deviceClass != FXDC_DISPLAY) {
    } else {
        CGImageRef image = CGBitmapContextCreateImage(_context);
        if (image) {
            _renderCaps |= FXRC_GET_BITS;
            _width = CGImageGetWidth(image);
            _height = CGImageGetHeight(image);
            CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(image);
            if (kCGImageAlphaPremultipliedFirst == alphaInfo ||
                    kCGImageAlphaPremultipliedLast == alphaInfo ||
                    kCGImageAlphaOnly == alphaInfo) {
                _renderCaps |= FXRC_ALPHA_OUTPUT;
            }
        }
        CGImageRelease(image);
    }
    CGAffineTransform ctm = CGContextGetCTM(_context);
    CGContextSaveGState(_context);
    m_saveCount++;
    if (ctm.d >= 0) {
        CGFloat offset_x, offset_y;
        offset_x = ctm.tx;
        offset_y = ctm.ty;
        CGContextTranslateCTM(_context, -offset_x, -offset_y);
        CGContextConcatCTM(_context, CGAffineTransformMake(1, 0, 0, -1, offset_x, _height + offset_y));
    }
    _foxitDevice2User = CGAffineTransformIdentity;
    _user2FoxitDevice = CGAffineTransformInvert(_foxitDevice2User);
}
コード例 #8
0
ファイル: ioscanvas.cpp プロジェクト: huangzongwu/touchvg
bool GiCanvasIos::drawImage(CGImageRef image, const Box2d& rectM)
{
    CGContextRef context = m_draw->getContext();
    bool ret = false;
    
    if (context && image) {
        Point2d ptD = rectM.center() * m_draw->xf().modelToDisplay();
        Box2d rect = rectM * m_draw->xf().modelToDisplay();
        
        CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, m_draw->height());
        af = CGAffineTransformTranslate(af, ptD.x - rect.width() * 0.5f, 
                                        m_draw->height() - (ptD.y + rect.height() * 0.5f));
        
        CGContextConcatCTM(context, af);
        CGContextDrawImage(context, CGRectMake(0, 0, rect.width(), rect.height()), image);
        CGContextConcatCTM(context, CGAffineTransformInvert(af));
        ret = true;
    }
    
    return ret;
}
コード例 #9
0
ファイル: ioscanvas.cpp プロジェクト: huangzongwu/touchvg
bool GiCanvasIos::drawCachedBitmap(float x, float y, bool secondBmp)
{
    int index = secondBmp ? 1 : 0;
    CGImageRef image = m_draw->_cacheserr[index] ? NULL : m_draw->_caches[index];
    CGContextRef context = m_draw->getContext();
    bool ret = false;
    
    if (context && image) {
        CGRect rect = CGRectMake(x, y, m_draw->width(), m_draw->height());
        CGAffineTransform af = CGAffineTransformMake(1, 0, 0, -1, 0, m_draw->height());
        
        CGContextConcatCTM(context, af);    // 图像是朝上的,上下文坐标系朝下,上下颠倒显示
        
        CGInterpolationQuality oldQuality = CGContextGetInterpolationQuality(context);
        CGContextSetInterpolationQuality(context, kCGInterpolationNone);
        CGContextDrawImage(context, rect, image);
        CGContextSetInterpolationQuality(context, oldQuality);
        
        CGContextConcatCTM(context, CGAffineTransformInvert(af));   // 恢复成坐标系朝下
        ret = true;
    }
    
    return ret;
}
コード例 #10
0
ファイル: EJPath.cpp プロジェクト: bagobor/Ejecta-X
void EJPath::arcTo(float x1, float y1, float x2, float y2, float radius) {
    // Lifted from http://code.google.com/p/fxcanvas/
    // I have no idea what this code is doing, but it seems to work.

    // get untransformed currentPos
    EJVector2 cp = EJVector2ApplyTransform(currentPos, CGAffineTransformInvert(transform));

    float a1 = cp.y - y1;
    float b1 = cp.x - x1;
    float a2 = y2   - y1;
    float b2 = x2   - x1;
    float mm = fabsf(a1 * b2 - b1 * a2);

    if( mm < 1.0e-8 || radius == 0 ) {
        lineTo(x1, y1);
    }
    else {
        float dd = a1 * a1 + b1 * b1;
        float cc = a2 * a2 + b2 * b2;
        float tt = a1 * a2 + b1 * b2;
        float k1 = radius * sqrtf(dd) / mm;
        float k2 = radius * sqrtf(cc) / mm;
        float j1 = k1 * tt / dd;
        float j2 = k2 * tt / cc;
        float cx = k1 * b2 + k2 * b1;
        float cy = k1 * a2 + k2 * a1;
        float px = b1 * (k2 + j1);
        float py = a1 * (k2 + j1);
        float qx = b2 * (k1 + j2);
        float qy = a2 * (k1 + j2);
        float startAngle = atan2f(py - cy, px - cx);
        float endAngle = atan2f(qy - cy, qx - cx);

        arc(cx + x1, cy + y1, radius, startAngle, endAngle, (b1 * a2 > b2 * a1));
    }
}
コード例 #11
0
ファイル: AffineTransformCG.cpp プロジェクト: oroisec/ios
AffineTransform AffineTransform::invert() const
{
    if (isInvertible())
        return AffineTransform(CGAffineTransformInvert(m_transform));
    return AffineTransform();
}
コード例 #12
0
ファイル: types.cpp プロジェクト: decarbonization/gfx
 Transform2D Transform2D::invert() const
 {
     return CGAffineTransformInvert(*this);
 }
コード例 #13
0
static cairo_int_status_t
_cairo_quartz_init_glyph_metrics (cairo_quartz_scaled_font_t *font,
				  cairo_scaled_glyph_t *scaled_glyph)
{
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;

    cairo_quartz_font_face_t *font_face = _cairo_quartz_scaled_to_face(font);
    cairo_text_extents_t extents = {0, 0, 0, 0, 0, 0};
    CGGlyph glyph = _cairo_quartz_scaled_glyph_index (scaled_glyph);
    int advance;
    CGRect bbox;
    double emscale = CGFontGetUnitsPerEmPtr (font_face->cgFont);
    double xmin, ymin, xmax, ymax;

    if (glyph == INVALID_GLYPH)
	goto FAIL;

    if (!CGFontGetGlyphAdvancesPtr (font_face->cgFont, &glyph, 1, &advance) ||
	!CGFontGetGlyphBBoxesPtr (font_face->cgFont, &glyph, 1, &bbox))
	goto FAIL;

    /* broken fonts like Al Bayan return incorrect bounds for some null characters,
       see https://bugzilla.mozilla.org/show_bug.cgi?id=534260 */
    if (unlikely (bbox.origin.x == -32767 &&
                  bbox.origin.y == -32767 &&
                  bbox.size.width == 65534 &&
                  bbox.size.height == 65534)) {
        bbox.origin.x = bbox.origin.y = 0;
        bbox.size.width = bbox.size.height = 0;
    }

    bbox = CGRectMake (bbox.origin.x / emscale,
		       bbox.origin.y / emscale,
		       bbox.size.width / emscale,
		       bbox.size.height / emscale);

    /* Should we want to always integer-align glyph extents, we can do so in this way */
#if 0
    {
	CGAffineTransform textMatrix;
	textMatrix = CGAffineTransformMake (font->base.scale.xx,
					    -font->base.scale.yx,
					    -font->base.scale.xy,
					    font->base.scale.yy,
					    0.0f, 0.0f);

	bbox = CGRectApplyAffineTransform (bbox, textMatrix);
	bbox = CGRectIntegral (bbox);
	bbox = CGRectApplyAffineTransform (bbox, CGAffineTransformInvert (textMatrix));
    }
#endif

#if 0
    fprintf (stderr, "[0x%04x] bbox: %f %f %f %f\n", glyph,
	     bbox.origin.x / emscale, bbox.origin.y / emscale,
	     bbox.size.width / emscale, bbox.size.height / emscale);
#endif

    xmin = CGRectGetMinX(bbox);
    ymin = CGRectGetMinY(bbox);
    xmax = CGRectGetMaxX(bbox);
    ymax = CGRectGetMaxY(bbox);

    extents.x_bearing = xmin;
    extents.y_bearing = - ymax;
    extents.width = xmax - xmin;
    extents.height = ymax - ymin;

    extents.x_advance = (double) advance / emscale;
    extents.y_advance = 0.0;

#if 0
    fprintf (stderr, "[0x%04x] extents: bearings: %f %f dim: %f %f adv: %f\n\n", glyph,
	     extents.x_bearing, extents.y_bearing, extents.width, extents.height, extents.x_advance);
#endif

  FAIL:
    _cairo_scaled_glyph_set_metrics (scaled_glyph,
				     &font->base,
				     &extents);

    return status;
}
コード例 #14
0
static cairo_int_status_t
_cairo_quartz_init_glyph_metrics (cairo_quartz_scaled_font_t *font,
				  cairo_scaled_glyph_t *scaled_glyph)
{
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;

    cairo_quartz_font_face_t *font_face = _cairo_quartz_scaled_to_face(font);
    cairo_text_extents_t extents = {0, 0, 0, 0, 0, 0};
    CGAffineTransform textMatrix;
    CGGlyph glyph = _cairo_quartz_scaled_glyph_index (scaled_glyph);
    int advance;
    CGRect bbox;
    double emscale = CGFontGetUnitsPerEmPtr (font_face->cgFont);
    double xscale, yscale;
    double xmin, ymin, xmax, ymax;

    if (glyph == INVALID_GLYPH)
	goto FAIL;

    if (!CGFontGetGlyphAdvancesPtr (font_face->cgFont, &glyph, 1, &advance) ||
	!CGFontGetGlyphBBoxesPtr (font_face->cgFont, &glyph, 1, &bbox))
	goto FAIL;

    status = _cairo_matrix_compute_basis_scale_factors (&font->base.scale,
						  &xscale, &yscale, 1);
    if (status)
	goto FAIL;

    bbox = CGRectMake (bbox.origin.x / emscale,
		       bbox.origin.y / emscale,
		       bbox.size.width / emscale,
		       bbox.size.height / emscale);

    /* Should we want to always integer-align glyph extents, we can do so in this way */
#if 0
    {
	CGAffineTransform textMatrix;
	textMatrix = CGAffineTransformMake (font->base.scale.xx,
					    -font->base.scale.yx,
					    -font->base.scale.xy,
					    font->base.scale.yy,
					    0.0f, 0.0f);

	bbox = CGRectApplyAffineTransform (bbox, textMatrix);
	bbox = CGRectIntegral (bbox);
	bbox = CGRectApplyAffineTransform (bbox, CGAffineTransformInvert (textMatrix));
    }
#endif

#if 0
    fprintf (stderr, "[0x%04x] bbox: %f %f %f %f\n", glyph,
	     bbox.origin.x / emscale, bbox.origin.y / emscale,
	     bbox.size.width / emscale, bbox.size.height / emscale);
#endif

    xmin = CGRectGetMinX(bbox);
    ymin = CGRectGetMinY(bbox);
    xmax = CGRectGetMaxX(bbox);
    ymax = CGRectGetMaxY(bbox);

    extents.x_bearing = xmin;
    extents.y_bearing = - ymax;
    extents.width = xmax - xmin;
    extents.height = ymax - ymin;

    extents.x_advance = (double) advance / emscale;
    extents.y_advance = 0.0;

#if 0
    fprintf (stderr, "[0x%04x] extents: bearings: %f %f dim: %f %f adv: %f\n\n", glyph,
	     extents.x_bearing, extents.y_bearing, extents.width, extents.height, extents.x_advance);
#endif

  FAIL:
    _cairo_scaled_glyph_set_metrics (scaled_glyph,
				     &font->base,
				     &extents);

    return status;
}
コード例 #15
0
ファイル: fx_quartz_device.cpp プロジェクト: 151706061/PDFium
FX_BOOL CFX_QuartzDeviceDriver::CG_DrawGlypRun(int                        nChars,
        const FXTEXT_CHARPOS*      pCharPos,
        CFX_Font*                  pFont,
        CFX_FontCache*             pCache,
        const CFX_AffineMatrix*    pGlyphMatrix,
        const CFX_AffineMatrix*    pObject2Device,
        FX_FLOAT                   font_size,
        FX_DWORD                   argb,
        int                        alpha_flag,
        void*                      pIccTransform)
{
    if (nChars == 0) {
        return TRUE;
    }
    CQuartz2D& quartz2d = ((CApplePlatform *) CFX_GEModule::Get()->GetPlatformData())->_quartz2d;
    if (!pFont->m_pPlatformFont) {
        if (pFont->GetPsName() == CFX_WideString::FromLocal("DFHeiStd-W5")) {
            return FALSE;
        }
        pFont->m_pPlatformFont = quartz2d.CreateFont(pFont->m_pFontData, pFont->m_dwSize);
        if (NULL == pFont->m_pPlatformFont) {
            return FALSE;
        }
    }
    CFX_FixedBufGrow<FX_WORD, 32> glyph_indices(nChars);
    CFX_FixedBufGrow<CGPoint, 32> glyph_positions(nChars);
    for (int i = 0; i < nChars; i++ ) {
        glyph_indices[i] = pCharPos[i].m_ExtGID;
        glyph_positions[i].x = pCharPos[i].m_OriginX;
        glyph_positions[i].y = pCharPos[i].m_OriginY;
    }
    CFX_AffineMatrix text_matrix;
    if (pObject2Device) {
        text_matrix.Concat(*pObject2Device);
    }
    CGAffineTransform matrix_cg = CGAffineTransformMake(text_matrix.a,
                                  text_matrix.b,
                                  text_matrix.c,
                                  text_matrix.d,
                                  text_matrix.e,
                                  text_matrix.f);
    matrix_cg = CGAffineTransformConcat(matrix_cg, _foxitDevice2User);
    CGContextSetTextMatrix(_context, matrix_cg);
    CGContextSetFont(_context, (CGFontRef)pFont->m_pPlatformFont);
    CGContextSetFontSize(_context, FXSYS_fabs(font_size));
    FX_INT32 a, r, g, b;
    ArgbDecode(argb, a, r, g, b);
    CGContextSetRGBFillColor(_context,
                             r / 255.f,
                             g / 255.f,
                             b / 255.f,
                             a / 255.f);
    SaveState();
    if (pGlyphMatrix) {
        CGPoint origin = CGPointMake( glyph_positions[0].x,  glyph_positions[0].y);
        origin = CGPointApplyAffineTransform(origin, matrix_cg);
        CGContextTranslateCTM(_context, origin.x, origin.y);
        CGAffineTransform glyph_matrix = CGAffineTransformMake(pGlyphMatrix->a,
                                         pGlyphMatrix->b,
                                         pGlyphMatrix->c,
                                         pGlyphMatrix->d,
                                         pGlyphMatrix->e,
                                         pGlyphMatrix->f);
        if (_foxitDevice2User.d < 0) {
            glyph_matrix = CGAffineTransformInvert(glyph_matrix);
        }
        CGContextConcatCTM(_context, glyph_matrix);
        CGContextTranslateCTM(_context, -origin.x, -origin.y);
    }
    CGContextShowGlyphsAtPositions(_context,
                                   (CGGlyph*)glyph_indices,
                                   glyph_positions,
                                   nChars);
    RestoreState(FALSE);
    return TRUE;
}
コード例 #16
0
ファイル: EJPath.cpp プロジェクト: bagobor/Ejecta-X
void EJPath::drawLinesToContext(EJCanvasContext * context) {
    endSubPath();

    EJCanvasState * state = context->state;
    EJVector2 vecZero = { 0.0f, 0.0f };

    // Find the width of the line as it is projected onto the screen.
    float projectedLineWidth = CGAffineTransformGetScale( state->transform ) * state->lineWidth;
    context->setTexture(NULL);

    // Figure out if we need to add line caps and set the cap texture coord for square or round caps.
    // For thin lines we disable texturing and line caps.
    float width2 = state->lineWidth/2;
    BOOL addCaps = (projectedLineWidth > 2 && (state->lineCap == kEJLineCapRound || state->lineCap == kEJLineCapSquare));

    // The miter limit is the maximum allowed ratio of the miter length to half the line width.
    BOOL addMiter = (state->lineJoin == kEJLineJoinMiter);
    float miterLimit = (state->miterLimit * width2);

    EJColorRGBA color = state->strokeColor;
    color.rgba.a = (unsigned char)(color.rgba.a * state->globalAlpha);

    // Enable stencil test when drawing transparent lines.
    // Cycle through all bits, so that the stencil buffer only has to be cleared after eight stroke operations
    if( color.rgba.a < 0xff ) {
        context->flushBuffers();
        context->createStencilBufferOnce();

        glEnable(GL_STENCIL_TEST);

        glStencilMask(stencilMask);

        glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
        glStencilFunc(GL_NOTEQUAL, stencilMask, stencilMask);
    }

    // To draw the line correctly with transformations, we need to construct the line
    // vertices from the untransformed points and only apply the transformation in
    // the last step (pushQuad) again.
    CGAffineTransform inverseTransform = CGAffineTransformIsIdentity(transform)
                                         ? transform
                                         : CGAffineTransformInvert(transform);


    // Oh god, I'm so sorry... This code sucks quite a bit. I'd be surprised if I
    // will understand what I've written in 3 days :/
    // Calculating line miters for potentially closed paths is serious business!
    // And it doesn't even handle all the edge cases.

    EJVector2
    *transCurrent, *transNext,	// Pointers to current and next vertices on the line
    current, next,				// Untransformed current and next points
    firstMiter1, firstMiter2,	// First miter vertices (left, right) needed for closed paths
    miter11, miter12,			// Current miter vertices (left, right)
    miter21, miter22,			// Next miter vertices (left, right)
    currentEdge, currentExt,	// Current edge and its normal * width/2
    nextEdge = { 0.0f, 0.0f }, nextExt = { 0.0f, 0.0f };			// Next edge and its normal * width/2

    for( path_t::iterator sp = paths.begin(); sp != paths.end(); ++sp ) {
        BOOL subPathIsClosed = sp->isClosed;
        BOOL ignoreFirstSegment = addMiter && subPathIsClosed;
        BOOL firstInSubPath = true;
        BOOL miterLimitExceeded = false, firstMiterLimitExceeded = false;

        transCurrent = transNext = NULL;

        // If this subpath is closed, initialize the first vertex for the loop ("next")
        // to the last vertex in the subpath. This way, the miter between the last and
        // the first segment will be computed and used to draw the first segment's first
        // miter, as well as the last segment's last miter outside the loop.
        if( addMiter && subPathIsClosed ) {
            transNext = &sp->points.at(sp->points.size()-2);
            next = EJVector2ApplyTransform( *transNext, inverseTransform );
        }

        for( points_t::iterator vertex = sp->points.begin(); vertex != sp->points.end(); ++vertex) {
            transCurrent = transNext;
            transNext = &(*vertex);

            current = next;
            next = EJVector2ApplyTransform( *transNext, inverseTransform );

            if( !transCurrent ) {
                continue;
            }

            currentEdge	= nextEdge;
            currentExt = nextExt;
            nextEdge = EJVector2Normalize(EJVector2Sub(next, current));
            nextExt = EJVector2Make( -nextEdge.y * width2, nextEdge.x * width2 );

            if( firstInSubPath ) {
                firstMiter1 = miter21 = EJVector2Add( current, nextExt );
                firstMiter2 = miter22 = EJVector2Sub( current, nextExt );
                firstInSubPath = false;

                // Start cap
                if( addCaps && !subPathIsClosed ) {
                    if( state->lineCap == kEJLineCapSquare ) {
                        EJVector2 capExt = { -nextExt.y, nextExt.x };
                        EJVector2 cap11 = EJVector2Add( miter21, capExt );
                        EJVector2 cap12 = EJVector2Add( miter22, capExt );

                        context->
                        pushQuad(cap11 ,cap12 ,miter21 ,miter22
                                 ,vecZero ,vecZero ,vecZero ,vecZero
                                 ,color ,transform);
                    }
                    else {
                        drawArcToContext(context, current, miter22, miter21, color);
                    }
                }

                continue;
            }


            miter11 = miter21;
            miter12 = miter22;

            BOOL miterAdded = false;
            if( addMiter ) {
                EJVector2 miterEdge = EJVector2Add( currentEdge, nextEdge );
                float miterExt = (1/EJVector2Dot(miterEdge, miterEdge)) * state->lineWidth;

                if( miterExt < miterLimit ) {
                    miterEdge.x *= miterExt;
                    miterEdge.y *= miterExt;
                    miter21 = EJVector2Make( current.x - miterEdge.y, current.y + miterEdge.x );
                    miter22 = EJVector2Make( current.x + miterEdge.y, current.y - miterEdge.x );

                    miterAdded = true;
                    miterLimitExceeded = false;
                }
                else {
                    miterLimitExceeded = true;
                }
            }

            // No miter added? Calculate the butt for the current segment
            if( !miterAdded ) {
                miter21 = EJVector2Add(current, currentExt);
                miter22 = EJVector2Sub(current, currentExt);
            }

            if( ignoreFirstSegment ) {
                // True when starting from the back vertex of a closed path. This run was just
                // to calculate the first miter.
                firstMiter1 = miter21;
                firstMiter2 = miter22;
                if( !miterAdded ) {
                    // Flip miter21 <> miter22 if it's the butt for the first segment
                    miter21 = firstMiter2;
                    miter22 = firstMiter1;
                }
                firstMiterLimitExceeded = miterLimitExceeded;
                ignoreFirstSegment = false;
                continue;
            }

            if( !addMiter || miterLimitExceeded ) {
                // previous point can be approximated, good enough for distance comparison
                EJVector2 prev = EJVector2Sub(current, currentEdge);
                EJVector2 p1, p2;
                float d1, d2;

                // calculate points to use for bevel
                // two points are possible for each edge - the one farthest away from the other line has to be used

                // calculate point for current edge
                d1 = EJDistanceToLineSegmentSquared(miter21, current, next);
                d2 = EJDistanceToLineSegmentSquared(miter22, current, next);
                p1 = ( d1 > d2 ) ? miter21 : miter22;

                // calculate point for next edge
                d1 = EJDistanceToLineSegmentSquared(EJVector2Add(current, nextExt), current, prev);
                d2 = EJDistanceToLineSegmentSquared(EJVector2Sub(current, nextExt), current, prev);
                p2 = ( d1 > d2 ) ? EJVector2Add(current, nextExt) : EJVector2Sub(current, nextExt);

                if( state->lineJoin==kEJLineJoinRound ) {
                    drawArcToContext(context, current, p1, p2, color);
                }
                else {
                    context->
                    pushTri(p1.x	,p1.y ,current.x,current.y ,p2.x ,p2.y
                            ,color ,transform);
                }
            }

            context->
            pushQuad(miter11 ,miter12 ,miter21 ,miter22
                     ,vecZero ,vecZero ,vecZero ,vecZero
                     ,color ,transform);

            // No miter added? The "miter" for the next segment needs to be the butt for the next segment,
            // not the butt for the current one.
            if( !miterAdded ) {
                miter21 = EJVector2Add(current, nextExt);
                miter22 = EJVector2Sub(current, nextExt);
            }
        } // for each subpath


        // The last segment, not handled in the loop
        if( !firstMiterLimitExceeded && addMiter && subPathIsClosed ) {
            miter11 = firstMiter1;
            miter12 = firstMiter2;
        }
        else {
            EJVector2 untransformedBack = EJVector2ApplyTransform(sp->points.back(), inverseTransform);
            miter11 = EJVector2Add(untransformedBack, nextExt);
            miter12 = EJVector2Sub(untransformedBack, nextExt);
        }

        if( (!addMiter || firstMiterLimitExceeded) && subPathIsClosed ) {
            float d1,d2;
            EJVector2 p1,p2,
                      firstNormal = EJVector2Sub(firstMiter1,firstMiter2),							// unnormalized line normal for first edge
                      second		= EJVector2Add(next,EJVector2Make(firstNormal.y,-firstNormal.x));	// approximated second point

            // calculate points to use for bevel
            // two points are possible for each edge - the one farthest away from the other line has to be used

            // calculate point for current edge
            d1 = EJDistanceToLineSegmentSquared(miter12, next, second);
            d2 = EJDistanceToLineSegmentSquared(miter11, next, second);
            p2 = ( d1 > d2 )?miter12:miter11;

            // calculate point for next edge
            d1 = EJDistanceToLineSegmentSquared(firstMiter1, current, next);
            d2 = EJDistanceToLineSegmentSquared(firstMiter2, current, next);
            p1 = (d1>d2)?firstMiter1:firstMiter2;

            if( state->lineJoin==kEJLineJoinRound ) {
                drawArcToContext(context, next, p1, p2, color);
            }
            else {
                context->
                pushTri(p1.x	,p1.y ,next.x ,next.y ,p2.x,p2.y
                        ,color ,transform);
            }
        }

        context->
        pushQuad(miter11,miter12,miter21,miter22
                 ,vecZero ,vecZero ,vecZero ,vecZero
                 ,color ,transform);

        // End cap
        if( addCaps && !subPathIsClosed ) {
            if( state->lineCap == kEJLineCapSquare ) {
                EJVector2 capExt = { nextExt.y, -nextExt.x };
                EJVector2 cap11 = EJVector2Add( miter11, capExt );
                EJVector2 cap12 = EJVector2Add( miter12, capExt );

                context->
                pushQuad(cap11,cap12,miter11,miter12
                         ,vecZero,vecZero,vecZero ,vecZero
                         ,color ,transform);
            }
            else {
                drawArcToContext(context, next, miter11, miter12, color);
            }
        }
    } // for each path

    // disable stencil test when drawing transparent lines
    if( color.rgba.a < 0xff ) {
        context->flushBuffers();
        glDisable(GL_STENCIL_TEST);

        if( stencilMask == (1<<7) ) {
            stencilMask = (1<<0);

            glStencilMask(0xff);
            glClearStencil(0x0);
            glClear(GL_STENCIL_BUFFER_BIT);
        }
        else {
            stencilMask <<= 1;
        }
    }
}