bool RenderSVGResourceMasker::drawContentIntoMaskImage(MaskerData* maskerData, ColorSpace colorSpace, const SVGMaskElement* maskElement, RenderObject* object) { GraphicsContext* maskImageContext = maskerData->maskImage->context(); ASSERT(maskImageContext); // Eventually adjust the mask image context according to the target objectBoundingBox. AffineTransform maskContentTransformation; if (maskElement->maskContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { FloatRect objectBoundingBox = object->objectBoundingBox(); maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y()); maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); maskImageContext->concatCTM(maskContentTransformation); } // Draw the content into the ImageBuffer. for (Node* node = maskElement->firstChild(); node; node = node->nextSibling()) { RenderObject* renderer = node->renderer(); if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !renderer) continue; if (renderer->needsLayout()) return false; RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; SVGImageBufferTools::renderSubtreeToImageBuffer(maskerData->maskImage.get(), renderer, maskContentTransformation); } #if !USE(CG) maskerData->maskImage->transformColorSpace(ColorSpaceDeviceRGB, colorSpace); #else UNUSED_PARAM(colorSpace); #endif // Create the luminance mask. maskerData->maskImage->convertToLuminanceMask(); return true; }
// FIXME: We should use floats here, like everywhere else! AffineTransform SVGPreserveAspectRatio::getCTM(double logicX, double logicY, double logicWidth, double logicHeight, double physWidth, double physHeight) const { AffineTransform transform; if (m_align == SVG_PRESERVEASPECTRATIO_UNKNOWN) return transform; double logicalRatio = logicWidth / logicHeight; double physRatio = physWidth / physHeight; if (m_align == SVG_PRESERVEASPECTRATIO_NONE) { transform.scaleNonUniform(physWidth / logicWidth, physHeight / logicHeight); transform.translate(-logicX, -logicY); return transform; } if ((logicalRatio < physRatio && (m_meetOrSlice == SVG_MEETORSLICE_MEET)) || (logicalRatio >= physRatio && (m_meetOrSlice == SVG_MEETORSLICE_SLICE))) { transform.scaleNonUniform(physHeight / logicHeight, physHeight / logicHeight); if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMINYMID || m_align == SVG_PRESERVEASPECTRATIO_XMINYMAX) transform.translate(-logicX, -logicY); else if (m_align == SVG_PRESERVEASPECTRATIO_XMIDYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMID || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMAX) transform.translate(-logicX - (logicWidth - physWidth * logicHeight / physHeight) / 2, -logicY); else transform.translate(-logicX - (logicWidth - physWidth * logicHeight / physHeight), -logicY); return transform; } transform.scaleNonUniform(physWidth / logicWidth, physWidth / logicWidth); if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMAXYMIN) transform.translate(-logicX, -logicY); else if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMID || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMID || m_align == SVG_PRESERVEASPECTRATIO_XMAXYMID) transform.translate(-logicX, -logicY - (logicHeight - physHeight * logicWidth / physWidth) / 2); else transform.translate(-logicX, -logicY - (logicHeight - physHeight * logicWidth / physWidth)); return transform; }
AffineTransform RenderSVGContainer::getAspectRatio(const FloatRect& logical, const FloatRect& physical) const { AffineTransform temp; float logicX = logical.x(); float logicY = logical.y(); float logicWidth = logical.width(); float logicHeight = logical.height(); float physWidth = physical.width(); float physHeight = physical.height(); float vpar = logicWidth / logicHeight; float svgar = physWidth / physHeight; if (align() == ALIGN_NONE) { temp.scale(physWidth / logicWidth, physHeight / logicHeight); temp.translate(-logicX, -logicY); } else if ((vpar < svgar && !slice()) || (vpar >= svgar && slice())) { temp.scale(physHeight / logicHeight, physHeight / logicHeight); if (align() == ALIGN_XMINYMIN || align() == ALIGN_XMINYMID || align() == ALIGN_XMINYMAX) temp.translate(-logicX, -logicY); else if (align() == ALIGN_XMIDYMIN || align() == ALIGN_XMIDYMID || align() == ALIGN_XMIDYMAX) temp.translate(-logicX - (logicWidth - physWidth * logicHeight / physHeight) / 2, -logicY); else temp.translate(-logicX - (logicWidth - physWidth * logicHeight / physHeight), -logicY); } else { temp.scale(physWidth / logicWidth, physWidth / logicWidth); if (align() == ALIGN_XMINYMIN || align() == ALIGN_XMIDYMIN || align() == ALIGN_XMAXYMIN) temp.translate(-logicX, -logicY); else if (align() == ALIGN_XMINYMID || align() == ALIGN_XMIDYMID || align() == ALIGN_XMAXYMID) temp.translate(-logicX, -logicY - (logicHeight - physHeight * logicWidth / physWidth) / 2); else temp.translate(-logicX, -logicY - (logicHeight - physHeight * logicWidth / physWidth)); } return temp; }
bool FilterEffectRendererHelper::prepareFilterEffect(RenderLayer* renderLayer, const LayoutRect& filterBoxRect, const LayoutRect& dirtyRect) { ASSERT(m_haveFilterEffect && renderLayer->filterRenderer()); m_renderLayer = renderLayer; m_paintInvalidationRect = dirtyRect; // Prepare a transformation that brings the coordinates into the space // filter coordinates are defined in. AffineTransform absoluteTransform; // FIXME: Should these really be upconverted to doubles and not rounded? crbug.com/350474 absoluteTransform.translate(filterBoxRect.x().toDouble(), filterBoxRect.y().toDouble()); FilterEffectRenderer* filter = renderLayer->filterRenderer(); filter->setAbsoluteTransform(absoluteTransform); IntRect filterSourceRect = pixelSnappedIntRect(filter->computeSourceImageRectForDirtyRect(filterBoxRect, dirtyRect)); if (filterSourceRect.isEmpty()) { // The dirty rect is not in view, just bail out. m_haveFilterEffect = false; return false; } m_filterBoxRect = filterBoxRect; filter->setFilterRegion(filter->mapAbsoluteRectToLocalRect(filterSourceRect)); filter->lastEffect()->determineFilterPrimitiveSubregion(MapRectForward); bool hasUpdatedBackingStore = filter->updateBackingStoreRect(filterSourceRect); if (filter->hasFilterThatMovesPixels()) { if (hasUpdatedBackingStore) m_paintInvalidationRect = filterSourceRect; else m_paintInvalidationRect.intersect(filterSourceRect); } return true; }
void ImageBuffer::drawPattern(GraphicsContext* context, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& dstRect) { if (m_data.m_tiledImage && context != m_context.get()) { FloatRect src = srcRect; if (src.width() == -1) src.setWidth(m_data.m_tiledImage->size().width()); if (src.height() == -1) src.setHeight(m_data.m_tiledImage->size().height()); ASSERT(context->platformContext()->activePainter()); AffineTransform phasedPatternTransform; phasedPatternTransform.translate(phase.x(), phase.y()); phasedPatternTransform.multLeft(patternTransform); PatternOpenVG pattern(m_data.m_tiledImage, src); pattern.setTransformation(phasedPatternTransform); PainterOpenVG* painter = context->platformContext()->activePainter(); PaintOpenVG currentPaint = painter->fillPaint(); CompositeOperator currentOp = painter->compositeOperation(); painter->setCompositeOperation(op); painter->setFillPattern(pattern); painter->drawRect(dstRect, VG_FILL_PATH); painter->setFillPaint(currentPaint); painter->setCompositeOperation(currentOp); return; } RefPtr<Image> imageCopy = copyImage(); imageCopy->drawPattern(context, srcRect, patternTransform, phase, styleColorSpace, op, dstRect); }
void TransparencyWin::setupTransformForKeepTransform(const IntRect& region) { if (!m_validLayer) return; if (m_layerMode != NoLayer) { // Need to save things since we're modifying the transform. m_drawContext->save(); m_savedOnDrawContext = true; // Account for the fact that the layer may be offset from the // original. This only happens when we create a layer that has the // same coordinate space as the parent. AffineTransform xform; xform.translate(-m_transformedSourceRect.x(), -m_transformedSourceRect.y()); // We're making a layer, so apply the old transform to the new one // so it's maintained. We know the new layer has the identity // transform now, we we can just multiply it. xform *= m_orgTransform; m_drawContext->concatCTM(xform); } m_drawRect = m_sourceRect; }
AffineTransform SVGPreserveAspectRatio::getCTM(double logicX, double logicY, double logicWidth, double logicHeight, double /*physX*/, double /*physY*/, double physWidth, double physHeight) { AffineTransform temp; if (align() == SVG_PRESERVEASPECTRATIO_UNKNOWN) return temp; double vpar = logicWidth / logicHeight; double svgar = physWidth / physHeight; if (align() == SVG_PRESERVEASPECTRATIO_NONE) { temp.scale(physWidth / logicWidth, physHeight / logicHeight); temp.translate(-logicX, -logicY); } else if (vpar < svgar && (meetOrSlice() == SVG_MEETORSLICE_MEET) || vpar >= svgar && (meetOrSlice() == SVG_MEETORSLICE_SLICE)) { temp.scale(physHeight / logicHeight, physHeight / logicHeight); if (align() == SVG_PRESERVEASPECTRATIO_XMINYMIN || align() == SVG_PRESERVEASPECTRATIO_XMINYMID || align() == SVG_PRESERVEASPECTRATIO_XMINYMAX) temp.translate(-logicX, -logicY); else if (align() == SVG_PRESERVEASPECTRATIO_XMIDYMIN || align() == SVG_PRESERVEASPECTRATIO_XMIDYMID || align() == SVG_PRESERVEASPECTRATIO_XMIDYMAX) temp.translate(-logicX - (logicWidth - physWidth * logicHeight / physHeight) / 2, -logicY); else temp.translate(-logicX - (logicWidth - physWidth * logicHeight / physHeight), -logicY); } else { temp.scale(physWidth / logicWidth, physWidth / logicWidth); if (align() == SVG_PRESERVEASPECTRATIO_XMINYMIN || align() == SVG_PRESERVEASPECTRATIO_XMIDYMIN || align() == SVG_PRESERVEASPECTRATIO_XMAXYMIN) temp.translate(-logicX, -logicY); else if (align() == SVG_PRESERVEASPECTRATIO_XMINYMID || align() == SVG_PRESERVEASPECTRATIO_XMIDYMID || align() == SVG_PRESERVEASPECTRATIO_XMAXYMID) temp.translate(-logicX, -logicY - (logicHeight - physHeight * logicWidth / physWidth) / 2); else temp.translate(-logicX, -logicY - (logicHeight - physHeight * logicWidth / physWidth)); } return temp; }
void RenderSVGResourceMasker::createMaskImage(MaskerData* maskerData, const SVGMaskElement* maskElement, RenderObject* object) { FloatRect objectBoundingBox = object->objectBoundingBox(); // Mask rect clipped with clippingBoundingBox and filterBoundingBox as long as they are present. maskerData->maskRect = object->repaintRectInLocalCoordinates(); if (maskerData->maskRect.isEmpty()) { maskerData->emptyMask = true; return; } if (m_maskBoundaries.isEmpty()) calculateMaskContentRepaintRect(); FloatRect repaintRect = m_maskBoundaries; AffineTransform contextTransform; // We need to scale repaintRect for objectBoundingBox to get the drawing area. if (maskElement->maskContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { contextTransform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); FloatPoint contextAdjustment = repaintRect.location(); repaintRect = contextTransform.mapRect(repaintRect); repaintRect.move(objectBoundingBox.x(), objectBoundingBox.y()); contextTransform.translate(-contextAdjustment.x(), -contextAdjustment.y()); } repaintRect.intersect(maskerData->maskRect); maskerData->maskRect = repaintRect; IntRect maskImageRect = enclosingIntRect(maskerData->maskRect); maskImageRect.setLocation(IntPoint()); // Don't create ImageBuffers with image size of 0 if (maskImageRect.isEmpty()) { maskerData->emptyMask = true; return; } // FIXME: This changes color space to linearRGB, the default color space // for masking operations in SVG. We need a switch for the other color-space // attribute values sRGB, inherit and auto. maskerData->maskImage = ImageBuffer::create(maskImageRect.size(), LinearRGB); if (!maskerData->maskImage) return; GraphicsContext* maskImageContext = maskerData->maskImage->context(); ASSERT(maskImageContext); maskImageContext->save(); if (maskElement->maskContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) maskImageContext->translate(-maskerData->maskRect.x(), -maskerData->maskRect.y()); maskImageContext->concatCTM(contextTransform); // draw the content into the ImageBuffer for (Node* node = maskElement->firstChild(); node; node = node->nextSibling()) { RenderObject* renderer = node->renderer(); if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !renderer) continue; RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; renderSubtreeToImage(maskerData->maskImage.get(), renderer); } maskImageContext->restore(); // create the luminance mask RefPtr<ImageData> imageData(maskerData->maskImage->getUnmultipliedImageData(maskImageRect)); CanvasPixelArray* srcPixelArray(imageData->data()); for (unsigned pixelOffset = 0; pixelOffset < srcPixelArray->length(); pixelOffset += 4) { unsigned char a = srcPixelArray->get(pixelOffset + 3); if (!a) continue; unsigned char r = srcPixelArray->get(pixelOffset); unsigned char g = srcPixelArray->get(pixelOffset + 1); unsigned char b = srcPixelArray->get(pixelOffset + 2); double luma = (r * 0.2125 + g * 0.7154 + b * 0.0721) * ((double)a / 255.0); srcPixelArray->set(pixelOffset + 3, luma); } maskerData->maskImage->putUnmultipliedImageData(imageData.get(), maskImageRect, IntPoint()); }
void SVGTextRunRenderingContext::drawSVGGlyphs(GraphicsContext* context, const TextRun& run, const SimpleFontData* fontData, const GlyphBuffer& glyphBuffer, int from, int numGlyphs, const FloatPoint& point) const { SVGFontElement* fontElement = 0; SVGFontFaceElement* fontFaceElement = 0; const SVGFontData* svgFontData = svgFontAndFontFaceElementForFontData(fontData, fontFaceElement, fontElement); if (!fontElement || !fontFaceElement) return; // We can only paint SVGFonts if a context is available. RenderSVGResource* activePaintingResource = activePaintingResourceFromRun(run); RenderObject* renderObject = renderObjectFromRun(run); RenderObject* parentRenderObject = firstParentRendererForNonTextNode(renderObject); RenderStyle* parentRenderObjectStyle = 0; ASSERT(renderObject); if (!activePaintingResource) { // TODO: We're only supporting simple filled HTML text so far. RenderSVGResourceSolidColor* solidPaintingResource = RenderSVGResource::sharedSolidPaintingResource(); solidPaintingResource->setColor(context->fillColor()); activePaintingResource = solidPaintingResource; } bool isVerticalText = false; if (parentRenderObject) { parentRenderObjectStyle = parentRenderObject->style(); ASSERT(parentRenderObjectStyle); isVerticalText = parentRenderObjectStyle->svgStyle()->isVerticalWritingMode(); } float scale = scaleEmToUnits(fontData->platformData().size(), fontFaceElement->unitsPerEm()); ASSERT(activePaintingResource); FloatPoint glyphOrigin; glyphOrigin.setX(svgFontData->horizontalOriginX() * scale); glyphOrigin.setY(svgFontData->horizontalOriginY() * scale); FloatPoint currentPoint = point; RenderSVGResourceMode resourceMode = context->textDrawingMode() == TextModeStroke ? ApplyToStrokeMode : ApplyToFillMode; for (int i = 0; i < numGlyphs; ++i) { Glyph glyph = glyphBuffer.glyphAt(from + i); if (!glyph) continue; float advance = glyphBuffer.advanceAt(from + i); SVGGlyph svgGlyph = fontElement->svgGlyphForGlyph(glyph); ASSERT(!svgGlyph.isPartOfLigature); ASSERT(svgGlyph.tableEntry == glyph); SVGGlyphElement::inheritUnspecifiedAttributes(svgGlyph, svgFontData); // FIXME: Support arbitary SVG content as glyph (currently limited to <glyph d="..."> situations). if (svgGlyph.pathData.isEmpty()) { if (isVerticalText) currentPoint.move(0, advance); else currentPoint.move(advance, 0); continue; } context->save(); if (isVerticalText) { glyphOrigin.setX(svgGlyph.verticalOriginX * scale); glyphOrigin.setY(svgGlyph.verticalOriginY * scale); } AffineTransform glyphPathTransform; glyphPathTransform.translate(currentPoint.x() + glyphOrigin.x(), currentPoint.y() + glyphOrigin.y()); glyphPathTransform.scale(scale, -scale); Path glyphPath = svgGlyph.pathData; glyphPath.transform(glyphPathTransform); if (activePaintingResource->applyResource(parentRenderObject, parentRenderObjectStyle, context, resourceMode)) { if (renderObject && renderObject->isSVGInlineText()) { const RenderSVGInlineText* textRenderer = toRenderSVGInlineText(renderObject); context->setStrokeThickness(context->strokeThickness() * textRenderer->scalingFactor()); } activePaintingResource->postApplyResource(parentRenderObject, context, resourceMode, &glyphPath, 0); } context->restore(); if (isVerticalText) currentPoint.move(0, advance); else currentPoint.move(advance, 0); } }
bool RenderSVGResourceGradient::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode) { ASSERT(object); ASSERT(style); ASSERT(context); ASSERT(resourceMode != ApplyToDefaultMode); // Be sure to synchronize all SVG properties on the gradientElement _before_ processing any further. // Otherwhise the call to collectGradientAttributes() in createTileImage(), may cause the SVG DOM property // synchronization to kick in, which causes invalidateClients() to be called, which in turn deletes our // GradientData object! Leaving out the line below will cause svg/dynamic-updates/SVG*GradientElement-svgdom* to crash. SVGGradientElement* gradientElement = static_cast<SVGGradientElement*>(node()); if (!gradientElement) return false; gradientElement->updateAnimatedSVGAttribute(anyQName()); if (!m_gradient.contains(object)) m_gradient.set(object, new GradientData); GradientData* gradientData = m_gradient.get(object); // Create gradient object if (!gradientData->gradient) buildGradient(gradientData, gradientElement); if (!gradientData->gradient) return false; // Draw gradient context->save(); bool isPaintingText = resourceMode & ApplyToTextMode; if (isPaintingText) { #if PLATFORM(CG) if (!createMaskAndSwapContextForTextGradient(context, m_savedContext, m_imageBuffer, object)) { context->restore(); return false; } #endif context->setTextDrawingMode(resourceMode & ApplyToFillMode ? cTextFill : cTextStroke); } AffineTransform transform; // CG platforms will handle the gradient space transform for text after applying the // resource, so don't apply it here. For non-CG platforms, we want the text bounding // box applied to the gradient space transform now, so the gradient shader can use it. #if PLATFORM(CG) if (gradientData->boundingBoxMode && !isPaintingText) { #else if (gradientData->boundingBoxMode) { #endif FloatRect objectBoundingBox = object->objectBoundingBox(); transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); } transform.multiply(gradientData->transform); gradientData->gradient->setGradientSpaceTransform(transform); const SVGRenderStyle* svgStyle = style->svgStyle(); ASSERT(svgStyle); if (resourceMode & ApplyToFillMode) { context->setAlpha(svgStyle->fillOpacity()); context->setFillGradient(gradientData->gradient); context->setFillRule(svgStyle->fillRule()); } else if (resourceMode & ApplyToStrokeMode) { context->setAlpha(svgStyle->strokeOpacity()); context->setStrokeGradient(gradientData->gradient); applyStrokeStyleToContext(context, style, object); } return true; } void RenderSVGResourceGradient::postApplyResource(RenderObject* object, GraphicsContext*& context, unsigned short resourceMode) { ASSERT(context); ASSERT(resourceMode != ApplyToDefaultMode); if (resourceMode & ApplyToTextMode) { #if PLATFORM(CG) // CG requires special handling for gradient on text if (m_savedContext && m_gradient.contains(object)) { GradientData* gradientData = m_gradient.get(object); // Restore on-screen drawing context context = m_savedContext; m_savedContext = 0; gradientData->gradient->setGradientSpaceTransform(clipToTextMask(context, m_imageBuffer, object, gradientData)); context->setFillGradient(gradientData->gradient); const RenderObject* textRootBlock = findTextRootObject(object); context->fillRect(textRootBlock->repaintRectInLocalCoordinates()); m_imageBuffer.clear(); } #else UNUSED_PARAM(object); #endif } else { if (resourceMode & ApplyToFillMode) context->fillPath(); else if (resourceMode & ApplyToStrokeMode) context->strokePath(); } context->restore(); }
PassRefPtr<const SkPicture> LayoutSVGResourceClipper::createContentPicture(AffineTransform& contentTransformation, const FloatRect& targetBoundingBox, GraphicsContext& context) { ASSERT(frame()); if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y()); contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height()); } if (m_clipContentPicture) return m_clipContentPicture; SubtreeContentTransformScope contentTransformScope(contentTransformation); // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinates) to avoid the intersection // with local clips/mask, which may yield incorrect results when mixing objectBoundingBox and // userSpaceOnUse units (http://crbug.com/294900). FloatRect bounds = strokeBoundingBox(); SkPictureBuilder pictureBuilder(bounds, nullptr, &context); for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { LayoutObject* layoutObject = childElement->layoutObject(); if (!layoutObject) continue; const ComputedStyle* style = layoutObject->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; bool isUseElement = isSVGUseElement(*childElement); if (isUseElement) { const SVGGraphicsElement* clippingElement = toSVGUseElement(*childElement).targetGraphicsElementForClipping(); if (!clippingElement) continue; layoutObject = clippingElement->layoutObject(); if (!layoutObject) continue; } // Only shapes, paths and texts are allowed for clipping. if (!layoutObject->isSVGShape() && !layoutObject->isSVGText()) continue; if (isUseElement) layoutObject = childElement->layoutObject(); // Switch to a paint behavior where all children of this <clipPath> will be laid out using special constraints: // - fill-opacity/stroke-opacity/opacity set to 1 // - masker/filter not applied when laying out the children // - fill is set to the initial fill paint server (solid, black) // - stroke is set to the initial stroke paint server (none) PaintInfo info(pictureBuilder.context(), LayoutRect::infiniteIntRect(), PaintPhaseForeground, GlobalPaintNormalPhase, PaintLayerPaintingRenderingClipPathAsMask); layoutObject->paint(info, IntPoint()); } m_clipContentPicture = pictureBuilder.endRecording(); return m_clipContentPicture; }
bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData, const FloatRect& objectBoundingBox) { ASSERT(clipperData); ASSERT(clipperData->clipMaskImage); GraphicsContext* maskContext = clipperData->clipMaskImage->context(); ASSERT(maskContext); AffineTransform maskContentTransformation; SVGClipPathElement* clipPath = static_cast<SVGClipPathElement*>(node()); if (clipPath->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y()); maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); maskContext->concatCTM(maskContentTransformation); } // Draw all clipPath children into a global mask. for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { RenderObject* renderer = childNode->renderer(); if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer) continue; RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; WindRule newClipRule = style->svgStyle()->clipRule(); bool isUseElement = renderer->isSVGShadowTreeRootContainer(); if (isUseElement) { SVGUseElement* useElement = static_cast<SVGUseElement*>(childNode); renderer = useElement->rendererClipChild(); if (!renderer) continue; if (!useElement->hasAttribute(SVGNames::clip_ruleAttr)) newClipRule = renderer->style()->svgStyle()->clipRule(); } // Only shapes, paths and texts are allowed for clipping. if (!renderer->isSVGPath() && !renderer->isSVGText()) continue; // Save the old RenderStyle of the current object for restoring after drawing // it to the MaskImage. The new intermediate RenderStyle needs to inherit from // the old one. RefPtr<RenderStyle> oldRenderStyle = renderer->style(); RefPtr<RenderStyle> newRenderStyle = RenderStyle::clone(oldRenderStyle.get()); SVGRenderStyle* svgStyle = newRenderStyle.get()->accessSVGStyle(); svgStyle->setFillPaint(SVGPaint::defaultFill()); svgStyle->setStrokePaint(SVGPaint::defaultStroke()); svgStyle->setFillRule(newClipRule); newRenderStyle.get()->setOpacity(1.0f); svgStyle->setFillOpacity(1.0f); svgStyle->setStrokeOpacity(1.0f); svgStyle->setFilterResource(String()); svgStyle->setMaskerResource(String()); // The setStyle() call results in a styleDidChange() call, which in turn invalidations the resources. // As we're mutating the resource on purpose, block updates until we've resetted the style again. m_invalidationBlocked = true; renderer->setStyle(newRenderStyle.release()); // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule. // We have to pass the <use> renderer itself to renderSubtreeToImageBuffer() to apply it's x/y/transform/etc. values when rendering. // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above. SVGImageBufferTools::renderSubtreeToImageBuffer(clipperData->clipMaskImage.get(), isUseElement ? childNode->renderer() : renderer, maskContentTransformation); renderer->setStyle(oldRenderStyle.release()); m_invalidationBlocked = false; } return true; }
void SVGTextRunRenderingContext::drawSVGGlyphs(GraphicsContext* context, const TextRun& run, const SimpleFontData* fontData, const GlyphBuffer& glyphBuffer, int from, int numGlyphs, const FloatPoint& point) const { SVGFontElement* fontElement = 0; SVGFontFaceElement* fontFaceElement = 0; const SVGFontData* svgFontData = svgFontAndFontFaceElementForFontData(fontData, fontFaceElement, fontElement); if (!fontElement || !fontFaceElement) return; // We can only paint SVGFonts if a context is available. RenderObject* renderObject = renderObjectFromRun(run); ASSERT(renderObject); bool isVerticalText = false; if (RenderObject* parentRenderObject = firstParentRendererForNonTextNode(renderObject)) { RenderStyle* parentRenderObjectStyle = parentRenderObject->style(); ASSERT(parentRenderObjectStyle); isVerticalText = parentRenderObjectStyle->svgStyle().isVerticalWritingMode(); } float scale = scaleEmToUnits(fontData->platformData().size(), fontFaceElement->unitsPerEm()); FloatPoint glyphOrigin; glyphOrigin.setX(svgFontData->horizontalOriginX() * scale); glyphOrigin.setY(svgFontData->horizontalOriginY() * scale); unsigned short resourceMode = context->textDrawingMode() == TextModeStroke ? ApplyToStrokeMode : ApplyToFillMode; FloatPoint currentPoint = point; for (int i = 0; i < numGlyphs; ++i) { Glyph glyph = glyphBuffer.glyphAt(from + i); if (!glyph) continue; float advance = glyphBuffer.advanceAt(from + i); SVGGlyph svgGlyph = fontElement->svgGlyphForGlyph(glyph); ASSERT(!svgGlyph.isPartOfLigature); ASSERT(svgGlyph.tableEntry == glyph); SVGGlyphElement::inheritUnspecifiedAttributes(svgGlyph, svgFontData); // FIXME: Support arbitary SVG content as glyph (currently limited to <glyph d="..."> situations). if (svgGlyph.pathData.isEmpty()) { if (isVerticalText) currentPoint.move(0, advance); else currentPoint.move(advance, 0); continue; } if (isVerticalText) { glyphOrigin.setX(svgGlyph.verticalOriginX * scale); glyphOrigin.setY(svgGlyph.verticalOriginY * scale); } AffineTransform glyphPathTransform; glyphPathTransform.translate(currentPoint.x() + glyphOrigin.x(), currentPoint.y() + glyphOrigin.y()); glyphPathTransform.scale(scale, -scale); Path glyphPath = svgGlyph.pathData; glyphPath.transform(glyphPathTransform); SVGRenderSupport::fillOrStrokePath(context, resourceMode, glyphPath); if (isVerticalText) currentPoint.move(0, advance); else currentPoint.move(advance, 0); } }
void Path::translate(const FloatSize& size) { AffineTransform transformation; transformation.translate(size.width(), size.height()); transform(transformation); }
bool SVGClipPainter::prepareEffect(const LayoutObject& target, const FloatRect& targetBoundingBox, const FloatRect& paintInvalidationRect, const FloatPoint& layerPositionOffset, GraphicsContext& context, ClipperState& clipperState) { DCHECK_EQ(clipperState, ClipperState::NotApplied); SECURITY_DCHECK(!m_clip.needsLayout()); m_clip.clearInvalidationMask(); if (paintInvalidationRect.isEmpty() || m_clip.hasCycle()) return false; SVGClipExpansionCycleHelper inClipExpansionChange(m_clip); AffineTransform animatedLocalTransform = toSVGClipPathElement(m_clip.element())->calculateAnimatedLocalTransform(); // When drawing a clip for non-SVG elements, the CTM does not include the zoom // factor. In this case, we need to apply the zoom scale explicitly - but // only for clips with userSpaceOnUse units (the zoom is accounted for // objectBoundingBox-resolved lengths). if (!target.isSVG() && m_clip.clipPathUnits() == SVGUnitTypes::kSvgUnitTypeUserspaceonuse) { DCHECK(m_clip.style()); animatedLocalTransform.scale(m_clip.style()->effectiveZoom()); } // First, try to apply the clip as a clipPath. Path clipPath; if (m_clip.asPath(animatedLocalTransform, targetBoundingBox, clipPath)) { AffineTransform positionTransform; positionTransform.translate(layerPositionOffset.x(), layerPositionOffset.y()); clipPath.transform(positionTransform); clipperState = ClipperState::AppliedPath; context.getPaintController().createAndAppend<BeginClipPathDisplayItem>( target, clipPath); return true; } // Fall back to masking. clipperState = ClipperState::AppliedMask; // Begin compositing the clip mask. CompositingRecorder::beginCompositing( context, target, SkXfermode::kSrcOver_Mode, 1, &paintInvalidationRect); { if (!drawClipAsMask(context, target, targetBoundingBox, paintInvalidationRect, animatedLocalTransform, layerPositionOffset)) { // End the clip mask's compositor. CompositingRecorder::endCompositing(context, target); return false; } } // Masked content layer start. CompositingRecorder::beginCompositing( context, target, SkXfermode::kSrcIn_Mode, 1, &paintInvalidationRect); return true; }
bool RenderSVGResourceClipper::tryPathOnlyClipping(GraphicsContext* context, const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox) { // If the current clip-path gets clipped itself, we have to fallback to masking. if (!style()->svgStyle().clipperResource().isEmpty()) return false; WindRule clipRule = RULE_NONZERO; Path clipPath = Path(); for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { RenderObject* renderer = childElement->renderer(); if (!renderer) continue; // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts. if (renderer->isSVGText()) return false; if (!childElement->isSVGGraphicsElement()) continue; SVGGraphicsElement* styled = toSVGGraphicsElement(childElement); RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; const SVGRenderStyle& svgStyle = style->svgStyle(); // Current shape in clip-path gets clipped too. Fallback to masking. if (!svgStyle.clipperResource().isEmpty()) return false; if (clipPath.isEmpty()) { // First clip shape. styled->toClipPath(clipPath); clipRule = svgStyle.clipRule(); clipPath.setWindRule(clipRule); continue; } if (RuntimeEnabledFeatures::pathOpsSVGClippingEnabled()) { // Attempt to generate a combined clip path, fall back to masking if not possible. Path subPath; styled->toClipPath(subPath); subPath.setWindRule(svgStyle.clipRule()); if (!clipPath.unionPath(subPath)) return false; } else { return false; } } // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary. if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { AffineTransform transform; transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); clipPath.transform(transform); } // Transform path by animatedLocalTransform. clipPath.transform(animatedLocalTransform); // The SVG specification wants us to clip everything, if clip-path doesn't have a child. if (clipPath.isEmpty()) clipPath.addRect(FloatRect()); context->clipPath(clipPath, clipRule); return true; }
void BlockPainter::paintObject(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (RuntimeEnabledFeatures::slimmingPaintOffsetCachingEnabled() && m_layoutBlock.childrenInline() && !paintInfo.context.paintController().skippingCache()) { if (m_layoutBlock.paintOffsetChanged(paintOffset)) { LineBoxListPainter(m_layoutBlock.lineBoxes()).invalidateLineBoxPaintOffsets(paintInfo); paintInfo.context.paintController().invalidatePaintOffset(m_layoutBlock); } // Set previousPaintOffset here in case that m_layoutBlock paints nothing and no // LayoutObjectDrawingRecorder updates its previousPaintOffset. // TODO(wangxianzhu): Integrate paint offset checking into new paint invalidation. m_layoutBlock.mutableForPainting().setPreviousPaintOffset(paintOffset); } const PaintPhase paintPhase = paintInfo.phase; if ((paintPhase == PaintPhaseSelfBlockBackground || paintPhase == PaintPhaseBlockBackground) && m_layoutBlock.style()->visibility() == VISIBLE && m_layoutBlock.hasBoxDecorationBackground()) m_layoutBlock.paintBoxDecorationBackground(paintInfo, paintOffset); if (paintPhase == PaintPhaseMask && m_layoutBlock.style()->visibility() == VISIBLE) { m_layoutBlock.paintMask(paintInfo, paintOffset); return; } if (paintPhase == PaintPhaseClippingMask && m_layoutBlock.style()->visibility() == VISIBLE) { BoxPainter(m_layoutBlock).paintClippingMask(paintInfo, paintOffset); return; } // FIXME: When Skia supports annotation rect covering (https://code.google.com/p/skia/issues/detail?id=3872), // this rect may be covered by foreground and descendant drawings. Then we may need a dedicated paint phase. if (paintPhase == PaintPhaseForeground && paintInfo.isPrinting()) ObjectPainter(m_layoutBlock).addPDFURLRectIfNeeded(paintInfo, paintOffset); { Optional<ScrollRecorder> scrollRecorder; Optional<PaintInfo> scrolledPaintInfo; if (m_layoutBlock.hasOverflowClip()) { IntSize scrollOffset = m_layoutBlock.scrolledContentOffset(); if (m_layoutBlock.layer()->scrollsOverflow() || !scrollOffset.isZero()) { scrollRecorder.emplace(paintInfo.context, m_layoutBlock, paintPhase, scrollOffset); scrolledPaintInfo.emplace(paintInfo); AffineTransform transform; transform.translate(-scrollOffset.width(), -scrollOffset.height()); scrolledPaintInfo->updateCullRect(transform); } } // We're done. We don't bother painting any children. if (paintPhase == PaintPhaseSelfBlockBackground || paintInfo.paintRootBackgroundOnly()) return; const PaintInfo& contentsPaintInfo = scrolledPaintInfo ? *scrolledPaintInfo : paintInfo; if (paintPhase != PaintPhaseSelfOutline) paintContents(contentsPaintInfo, paintOffset); if (paintPhase == PaintPhaseForeground && !paintInfo.isPrinting()) m_layoutBlock.paintSelection(contentsPaintInfo, paintOffset); // Fill in gaps in selection on lines and between blocks. if (paintPhase == PaintPhaseFloat || paintPhase == PaintPhaseSelection || paintPhase == PaintPhaseTextClip) m_layoutBlock.paintFloats(contentsPaintInfo, paintOffset); } if ((paintPhase == PaintPhaseOutline || paintPhase == PaintPhaseSelfOutline) && m_layoutBlock.style()->hasOutline() && m_layoutBlock.style()->visibility() == VISIBLE) ObjectPainter(m_layoutBlock).paintOutline(paintInfo, paintOffset); // If the caret's node's layout object's containing block is this block, and the paint action is PaintPhaseForeground, // then paint the caret. if (paintPhase == PaintPhaseForeground && m_layoutBlock.hasCaret() && !LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(paintInfo.context, m_layoutBlock, DisplayItem::Caret, paintOffset)) { LayoutRect bounds = m_layoutBlock.visualOverflowRect(); bounds.moveBy(paintOffset); LayoutObjectDrawingRecorder recorder(paintInfo.context, m_layoutBlock, DisplayItem::Caret, bounds, paintOffset); paintCarets(paintInfo, paintOffset); } }
bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData, const FloatRect& objectBoundingBox) { ASSERT(frame()); ASSERT(clipperData); ASSERT(clipperData->clipMaskImage); GraphicsContext* maskContext = clipperData->clipMaskImage->context(); ASSERT(maskContext); AffineTransform maskContentTransformation; SVGClipPathElement* clipPath = static_cast<SVGClipPathElement*>(node()); if (clipPath->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y()); maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); maskContext->concatCTM(maskContentTransformation); } // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints: // - fill-opacity/stroke-opacity/opacity set to 1 // - masker/filter not applied when rendering the children // - fill is set to the initial fill paint server (solid, black) // - stroke is set to the initial stroke paint server (none) PaintBehavior oldBehavior = frame()->view()->paintBehavior(); frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask); // Draw all clipPath children into a global mask. for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { RenderObject* renderer = childNode->renderer(); if (!childNode->isSVGElement() || !toSVGElement(childNode)->isSVGStyledElement() || !renderer) continue; if (renderer->needsLayout()) { frame()->view()->setPaintBehavior(oldBehavior); return false; } RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; WindRule newClipRule = style->svgStyle()->clipRule(); bool isUseElement = childNode->hasTagName(SVGNames::useTag); if (isUseElement) { SVGUseElement* useElement = static_cast<SVGUseElement*>(childNode); renderer = useElement->rendererClipChild(); if (!renderer) continue; if (!useElement->hasAttribute(SVGNames::clip_ruleAttr)) newClipRule = renderer->style()->svgStyle()->clipRule(); } // Only shapes, paths and texts are allowed for clipping. if (!renderer->isSVGShape() && !renderer->isSVGText()) continue; maskContext->setFillRule(newClipRule); // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule. // We have to pass the <use> renderer itself to renderSubtreeToImageBuffer() to apply it's x/y/transform/etc. values when rendering. // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above. SVGRenderingContext::renderSubtreeToImageBuffer(clipperData->clipMaskImage.get(), isUseElement ? childNode->renderer() : renderer, maskContentTransformation); } frame()->view()->setPaintBehavior(oldBehavior); return true; }
void BlockPainter::paintObject(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { const PaintPhase paintPhase = paintInfo.phase; if (shouldPaintSelfBlockBackground(paintPhase)) { if (m_layoutBlock.style()->visibility() == EVisibility::Visible && m_layoutBlock.hasBoxDecorationBackground()) m_layoutBlock.paintBoxDecorationBackground(paintInfo, paintOffset); // We're done. We don't bother painting any children. if (paintPhase == PaintPhaseSelfBlockBackgroundOnly) return; } if (paintInfo.paintRootBackgroundOnly()) return; if (paintPhase == PaintPhaseMask && m_layoutBlock.style()->visibility() == EVisibility::Visible) { m_layoutBlock.paintMask(paintInfo, paintOffset); return; } if (paintPhase == PaintPhaseClippingMask && m_layoutBlock.style()->visibility() == EVisibility::Visible) { BoxPainter(m_layoutBlock).paintClippingMask(paintInfo, paintOffset); return; } if (paintPhase == PaintPhaseForeground && paintInfo.isPrinting()) ObjectPainter(m_layoutBlock).addPDFURLRectIfNeeded(paintInfo, paintOffset); if (paintPhase != PaintPhaseSelfOutlineOnly) { Optional<ScopedPaintChunkProperties> m_scopedScrollProperty; Optional<ScrollRecorder> scrollRecorder; Optional<PaintInfo> scrolledPaintInfo; if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) { const auto* objectProperties = m_layoutBlock.paintProperties(); if (auto* scroll = objectProperties ? objectProperties->scroll() : nullptr) { PaintChunkProperties properties(paintInfo.context.getPaintController() .currentPaintChunkProperties()); auto* scrollTranslation = objectProperties->scrollTranslation(); DCHECK(scrollTranslation); properties.transform = scrollTranslation; properties.scroll = scroll; m_scopedScrollProperty.emplace( paintInfo.context.getPaintController(), m_layoutBlock, DisplayItem::paintPhaseToDrawingType(paintPhase), properties); scrolledPaintInfo.emplace(paintInfo); scrolledPaintInfo->updateCullRect( scrollTranslation->matrix().toAffineTransform()); } } else if (m_layoutBlock.hasOverflowClip()) { IntSize scrollOffset = m_layoutBlock.scrolledContentOffset(); if (m_layoutBlock.layer()->scrollsOverflow() || !scrollOffset.isZero()) { scrollRecorder.emplace(paintInfo.context, m_layoutBlock, paintPhase, scrollOffset); scrolledPaintInfo.emplace(paintInfo); AffineTransform transform; transform.translate(-scrollOffset.width(), -scrollOffset.height()); scrolledPaintInfo->updateCullRect(transform); } } const PaintInfo& contentsPaintInfo = scrolledPaintInfo ? *scrolledPaintInfo : paintInfo; if (m_layoutBlock.isLayoutBlockFlow()) { BlockFlowPainter blockFlowPainter(toLayoutBlockFlow(m_layoutBlock)); blockFlowPainter.paintContents(contentsPaintInfo, paintOffset); if (paintPhase == PaintPhaseFloat || paintPhase == PaintPhaseSelection || paintPhase == PaintPhaseTextClip) blockFlowPainter.paintFloats(contentsPaintInfo, paintOffset); } else { paintContents(contentsPaintInfo, paintOffset); } } if (shouldPaintSelfOutline(paintPhase)) ObjectPainter(m_layoutBlock).paintOutline(paintInfo, paintOffset); // If the caret's node's layout object's containing block is this block, and // the paint action is PaintPhaseForeground, then paint the caret. if (paintPhase == PaintPhaseForeground && m_layoutBlock.hasCaret()) paintCarets(paintInfo, paintOffset); }
void Font::drawComplexText(GraphicsContext* context, const TextRun& run, const FloatPoint& point, int from, int to) const { const FontPlatformData& fontPlatformData = primaryFont()->platformData(); const float scaleFactor = fontPlatformData.scaleFactor(); Olympia::Platform::Text::Font* font = fontPlatformData.font(); // FIXME: use fallback fonts as well? Olympia::Platform::Text::DrawParam drawParam; String sanitized = setupTextDrawing(this, run, &drawParam); adjustOffsetsForTextDrawing(run, from, to); SurfaceOpenVG* surface = context->platformContext(); surface->makeCurrent(); // Before passing control to Text API, make sure we're ok ourselves. ASSERT_VG_NO_ERROR(); PainterOpenVG* painter = surface->activePainter(); painter->save(); painter->setStateModified(PainterOpenVG::AllStateCategories); Olympia::Platform::Text::GraphicsContext* textContext = FontPlatformData::textGraphicsContext(); #if PLATFORM(EGL) textContext->setDisplay(static_cast<Olympia::Platform::Text::NativeGraphicsDisplay>(surface->eglDisplay())); textContext->setSurface(static_cast<Olympia::Platform::Text::NativeGraphicsSurface>(surface->eglSurface())); textContext->setContext(static_cast<Olympia::Platform::Text::NativeGraphicsContext>(surface->eglContext())); ASSERT_EGL_NO_ERROR(); #endif if (painter->textDrawingMode() & cTextClip) return; // unsupported for every port except CG at the time of writing if (!(painter->textDrawingMode() & cTextFill)) painter->setFillColor(Color::transparent); if (!(painter->textDrawingMode() & cTextStroke)) painter->setStrokeStyle(NoStroke); if (from > 0 || to < sanitized.length()) { // Clip to the from-to region. We need to draw the full text // (and clip) because characters might change appearance when // connected to other characters. double fromX; FontPlatformData::engine()->textPosToX(from, fromX, *font, sanitized.characters(), sanitized.length(), drawParam); ASSERT_VG_NO_ERROR(); fromX *= scaleFactor; double toX; FontPlatformData::engine()->textPosToX(to, toX, *font, sanitized.characters(), sanitized.length(), drawParam); ASSERT_VG_NO_ERROR(); toX *= scaleFactor; // FIXME: Some glyphs can be higher than primary font's height. // We multiply ascent and descent by 4 so the clip rect will be high // enough for all glyphs we know. This is safe because we only apply // the clip rect when drawing partial strings, and we are only interested // in horizontal clip range. const float y = point.y() - primaryFont()->ascent() * 4; const float height = (primaryFont()->ascent() + primaryFont()->descent()) * 4; FloatRect clipRect = (toX > fromX) ? FloatRect(point.x() + fromX, y, toX - fromX, height) : FloatRect(point.x() + toX, y, fromX - toX, height); painter->clipRect(clipRect, PainterOpenVG::IntersectClip); } AffineTransform transformation = painter->transformation(); transformation.translate(point.x(), point.y()); FloatPoint currentTranslation(transformation.e(), transformation.f()); transformation.setE(0); transformation.setF(0); transformation.scale(scaleFactor); transformation.setE(roundf(currentTranslation.x())); transformation.setF(roundf(currentTranslation.y())); painter->setTransformation(transformation); if (painter->shadowEnabled()) { painter->beginDrawShadow(); FontPlatformData::engine()->drawText(textContext, *font, sanitized.characters(), sanitized.length(), 0, 0, 0 /* no wrap */, &drawParam, 0 /* returned metrics */); painter->endDrawShadow(); } FontPlatformData::engine()->drawText(textContext, *font, sanitized.characters(), sanitized.length(), 0, 0, 0 /* no wrap */, &drawParam, 0 /* returned metrics */); #if PLATFORM(EGL) SurfaceOpenVG* sharedSurface = surface->sharedSurface(); textContext->setDisplay(static_cast<Olympia::Platform::Text::NativeGraphicsDisplay>(sharedSurface->eglDisplay())); textContext->setSurface(static_cast<Olympia::Platform::Text::NativeGraphicsSurface>(sharedSurface->eglSurface())); textContext->setContext(static_cast<Olympia::Platform::Text::NativeGraphicsContext>(sharedSurface->eglContext())); ASSERT_EGL_NO_ERROR(); #endif // Let's make sure Text API didn't cause any troubles. ASSERT_VG_NO_ERROR(); painter->restore(); }