static inline void removeFromCacheAndInvalidateDependencies(RenderElement& renderer, bool needsLayout) { if (auto* resources = SVGResourcesCache::cachedResourcesForRenderer(renderer)) { if (RenderSVGResourceFilter* filter = resources->filter()) filter->removeClientFromCache(renderer); if (RenderSVGResourceMasker* masker = resources->masker()) masker->removeClientFromCache(renderer); if (RenderSVGResourceClipper* clipper = resources->clipper()) clipper->removeClientFromCache(renderer); } if (!renderer.element() || !renderer.element()->isSVGElement()) return; HashSet<SVGElement*>* dependencies = renderer.document().accessSVGExtensions().setOfElementsReferencingTarget(downcast<SVGElement>(renderer.element())); if (!dependencies) return; // We allow cycles in SVGDocumentExtensions reference sets in order to avoid expensive // reference graph adjustments on changes, so we need to break possible cycles here. static NeverDestroyed<HashSet<SVGElement*>> invalidatingDependencies; for (auto* element : *dependencies) { if (auto* renderer = element->renderer()) { if (UNLIKELY(!invalidatingDependencies.get().add(element).isNewEntry)) { // Reference cycle: we are in process of invalidating this dependant. continue; } RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer, needsLayout); invalidatingDependencies.get().remove(element); } } }
void SVGRenderSupport::applyStrokeStyleToContext(GraphicsContext* context, const RenderStyle& style, const RenderElement& renderer) { ASSERT(context); ASSERT(renderer.element()); ASSERT(renderer.element()->isSVGElement()); const SVGRenderStyle& svgStyle = style.svgStyle(); SVGLengthContext lengthContext(toSVGElement(renderer.element())); context->setStrokeThickness(svgStyle.strokeWidth().value(lengthContext)); context->setLineCap(svgStyle.capStyle()); context->setLineJoin(svgStyle.joinStyle()); if (svgStyle.joinStyle() == MiterJoin) context->setMiterLimit(svgStyle.strokeMiterLimit()); const Vector<SVGLength>& dashes = svgStyle.strokeDashArray(); if (dashes.isEmpty()) context->setStrokeStyle(SolidStroke); else { DashArray dashArray; dashArray.reserveInitialCapacity(dashes.size()); for (unsigned i = 0, size = dashes.size(); i < size; ++i) dashArray.uncheckedAppend(dashes[i].value(lengthContext)); context->setLineDash(dashArray, svgStyle.strokeDashOffset().value(lengthContext)); } }
static bool shouldCreateRenderer(const Element& element, const RenderElement& parentRenderer) { if (!parentRenderer.canHaveChildren() && !(element.isPseudoElement() && parentRenderer.canHaveGeneratedChildren())) return false; if (parentRenderer.element() && !parentRenderer.element()->childShouldCreateRenderer(element)) return false; return true; }
void SVGRenderSupport::styleChanged(RenderElement& renderer, const RenderStyle* oldStyle) { auto parent = renderer.parent(); SVGRenderSupport::setRendererHasSVGShadow(renderer, (parent && SVGRenderSupport::rendererHasSVGShadow(*parent)) || renderer.style().svgStyle().shadow()); #if ENABLE(CSS_COMPOSITING) if (renderer.element() && renderer.element()->isSVGElement() && (!oldStyle || renderer.style().hasBlendMode() != oldStyle->hasBlendMode())) SVGRenderSupport::updateMaskedAncestorShouldIsolateBlending(renderer); #else UNUSED_PARAM(oldStyle); #endif }
bool AnimationControllerPrivate::clear(RenderElement& renderer) { LOG(Animations, "AnimationControllerPrivate %p clear: %p", this, &renderer); ASSERT(renderer.isCSSAnimating()); ASSERT(m_compositeAnimations.contains(&renderer)); Element* element = renderer.element(); m_eventsToDispatch.removeAllMatching([element] (const EventToDispatch& info) { return info.element == element; }); m_elementChangesToDispatch.removeAllMatching([element](auto& currentElement) { return currentElement.ptr() == element; }); // Return false if we didn't do anything OR we are suspended (so we don't try to // do a setNeedsStyleRecalc() when suspended). RefPtr<CompositeAnimation> animation = m_compositeAnimations.take(&renderer); ASSERT(animation); renderer.setIsCSSAnimating(false); animation->clearRenderer(); return animation->isSuspended(); }
bool AnimationController::updateAnimations(RenderElement& renderer, const RenderStyle& newStyle, std::unique_ptr<RenderStyle>& animatedStyle) { auto* oldStyle = renderer.hasInitializedStyle() ? &renderer.style() : nullptr; if ((!oldStyle || (!oldStyle->animations() && !oldStyle->transitions())) && (!newStyle.animations() && !newStyle.transitions())) return false; if (renderer.document().pageCacheState() != Document::NotInPageCache) return false; // Don't run transitions when printing. if (renderer.view().printing()) return false; // Fetch our current set of implicit animations from a hashtable. We then compare them // against the animations in the style and make sure we're in sync. If destination values // have changed, we reset the animation. We then do a blend to get new values and we return // a new style. // We don't support anonymous pseudo elements like :first-line or :first-letter. ASSERT(renderer.element()); CompositeAnimation& rendererAnimations = m_data->ensureCompositeAnimation(renderer); bool animationStateChanged = rendererAnimations.animate(renderer, oldStyle, newStyle, animatedStyle); if (renderer.parent() || newStyle.animations() || (oldStyle && oldStyle->animations())) { m_data->updateAnimationTimerForRenderer(renderer); #if ENABLE(REQUEST_ANIMATION_FRAME) renderer.view().frameView().scheduleAnimation(); #endif } return animationStateChanged; }
void SVGRenderSupport::updateMaskedAncestorShouldIsolateBlending(const RenderElement& renderer) { ASSERT(renderer.element()); ASSERT(renderer.element()->isSVGElement()); bool maskedAncestorShouldIsolateBlending = renderer.style().hasBlendMode(); for (auto* ancestor = renderer.element()->parentElement(); ancestor && ancestor->isSVGElement(); ancestor = ancestor->parentElement()) { if (!toSVGElement(ancestor)->isSVGGraphicsElement() || !isolatesBlending(*ancestor->computedStyle())) continue; if (ancestor->computedStyle()->svgStyle().hasMasker()) toSVGGraphicsElement(ancestor)->setShouldIsolateBlending(maskedAncestorShouldIsolateBlending); return; } }
static bool planCounter(RenderElement& renderer, const AtomicString& identifier, bool& isReset, int& value) { // We must have a generating node or else we cannot have a counter. Element* generatingElement = renderer.generatingElement(); if (!generatingElement) return false; const RenderStyle& style = renderer.style(); switch (style.styleType()) { case NOPSEUDO: // Sometimes elements have more then one renderer. Only the first one gets the counter // LayoutTests/http/tests/css/counter-crash.html if (generatingElement->renderer() != &renderer) return false; break; case BEFORE: case AFTER: break; default: return false; // Counters are forbidden from all other pseudo elements. } const CounterDirectives directives = style.getCounterDirectives(identifier); if (directives.isDefined()) { value = directives.combinedValue(); isReset = directives.isReset(); return true; } if (identifier == "list-item") { if (is<RenderListItem>(renderer)) { if (downcast<RenderListItem>(renderer).hasExplicitValue()) { value = downcast<RenderListItem>(renderer).explicitValue(); isReset = true; return true; } value = 1; isReset = false; return true; } if (Element* element = renderer.element()) { if (is<HTMLOListElement>(*element)) { value = downcast<HTMLOListElement>(*element).start(); isReset = true; return true; } if (element->hasTagName(ulTag) || element->hasTagName(menuTag) || element->hasTagName(dirTag)) { value = 0; isReset = true; return true; } } } return false; }
std::unique_ptr<RenderStyle> RenderNamedFlowFragment::computeStyleInRegion(RenderElement& renderer, const RenderStyle& parentStyle) const { ASSERT(!renderer.isAnonymous()); // FIXME: Region styling fails for pseudo-elements because the renderers don't have a node. auto renderObjectRegionStyle = renderer.element()->styleResolver().styleForElement(*renderer.element(), &parentStyle, MatchAllRules, this).renderStyle; return renderObjectRegionStyle; }
void AnimationController::cancelAnimations(RenderElement& renderer) { if (!renderer.isCSSAnimating()) return; if (!m_data->clear(renderer)) return; Element* element = renderer.element(); ASSERT(!element || !element->document().inPageCache()); if (element) element->setNeedsStyleRecalc(SyntheticStyleChange); }
void SVGResourcesCache::clientStyleChanged(RenderElement& renderer, StyleDifference diff, const RenderStyle& newStyle) { if (diff == StyleDifferenceEqual || !renderer.parent()) return; // In this case the proper SVGFE*Element will decide whether the modified CSS properties require a relayout or repaint. if (renderer.isSVGResourceFilterPrimitive() && (diff == StyleDifferenceRepaint || diff == StyleDifferenceRepaintIfTextOrBorderOrOutline)) return; // Dynamic changes of CSS properties like 'clip-path' may require us to recompute the associated resources for a renderer. // FIXME: Avoid passing in a useless StyleDifference, but instead compare oldStyle/newStyle to see which resources changed // to be able to selectively rebuild individual resources, instead of all of them. if (rendererCanHaveResources(renderer)) { SVGResourcesCache* cache = resourcesCacheFromRenderObject(renderer); cache->removeResourcesFromRenderer(renderer); cache->addResourcesFromRenderer(renderer, newStyle); } RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer, false); if (renderer.element() && !renderer.element()->isSVGElement()) renderer.element()->setNeedsStyleRecalc(SyntheticStyleChange); }
void AnimationController::cancelAnimations(RenderElement& renderer) { if (!renderer.isCSSAnimating()) return; if (!m_data->clear(renderer)) return; Element* element = renderer.element(); if (!element || element->document().renderTreeBeingDestroyed()) return; ASSERT(element->document().pageCacheState() == Document::NotInPageCache); element->invalidateStyleAndLayerComposition(); }
void RenderNamedFlowThread::addFlowChild(RenderElement& newChild) { // The child list is used to sort the flow thread's children render objects // based on their corresponding nodes DOM order. The list is needed to avoid searching the whole DOM. if (newChild.isAnonymous()) return; auto* beforeChild = nextRendererForElement(*newChild.element()); if (beforeChild) m_flowThreadChildList.insertBefore(beforeChild, &newChild); else m_flowThreadChildList.add(&newChild); }
void FEImage::platformApplySoftware() { RenderElement* renderer = referencedRenderer(); if (!m_image && !renderer) return; ImageBuffer* resultImage = createImageBufferResult(); if (!resultImage) return; SVGFilter* svgFilter = toSVGFilter(filter()); FloatRect destRect = svgFilter->absoluteTransform().mapRect(filterPrimitiveSubregion()); FloatRect srcRect; if (renderer) srcRect = svgFilter->absoluteTransform().mapRect(renderer->repaintRectInLocalCoordinates()); else { srcRect = FloatRect(FloatPoint(), m_image->size()); m_preserveAspectRatio.transformRect(destRect, srcRect); } IntPoint paintLocation = absolutePaintRect().location(); destRect.move(-paintLocation.x(), -paintLocation.y()); // FEImage results are always in ColorSpaceDeviceRGB setResultColorSpace(ColorSpaceDeviceRGB); if (renderer) { const AffineTransform& absoluteTransform = svgFilter->absoluteTransform(); resultImage->context()->concatCTM(absoluteTransform); SVGElement* contextNode = toSVGElement(renderer->element()); if (contextNode->hasRelativeLengths()) { SVGLengthContext lengthContext(contextNode); FloatSize viewportSize; // If we're referencing an element with percentage units, eg. <rect with="30%"> those values were resolved against the viewport. // Build up a transformation that maps from the viewport space to the filter primitive subregion. if (lengthContext.determineViewport(viewportSize)) resultImage->context()->concatCTM(makeMapBetweenRects(FloatRect(FloatPoint(), viewportSize), destRect)); } AffineTransform contentTransformation; SVGRenderingContext::renderSubtreeToImageBuffer(resultImage, *renderer, contentTransformation); return; } resultImage->context()->drawImage(m_image.get(), ColorSpaceDeviceRGB, destRect, srcRect); }
void RenderCounter::rendererSubtreeAttached(RenderElement& renderer) { if (!renderer.view().hasRenderCounters()) return; Element* element = renderer.element(); if (element && !element->isPseudoElement()) element = element->parentElement(); else element = renderer.generatingElement(); if (element && !element->renderer()) return; // No need to update if the parent is not attached yet for (RenderObject* descendant = &renderer; descendant; descendant = descendant->nextInPreOrder(&renderer)) { if (is<RenderElement>(*descendant)) updateCounters(downcast<RenderElement>(*descendant)); } }
PassRef<RenderStyle> AnimationController::updateAnimations(RenderElement& renderer, PassRef<RenderStyle> newStyle) { // Don't do anything if we're in the cache if (renderer.document().inPageCache()) return newStyle; RenderStyle* oldStyle = renderer.hasInitializedStyle() ? &renderer.style() : nullptr; if ((!oldStyle || (!oldStyle->animations() && !oldStyle->transitions())) && (!newStyle.get().animations() && !newStyle.get().transitions())) return newStyle; // Don't run transitions when printing. if (renderer.view().printing()) return newStyle; // Fetch our current set of implicit animations from a hashtable. We then compare them // against the animations in the style and make sure we're in sync. If destination values // have changed, we reset the animation. We then do a blend to get new values and we return // a new style. // We don't support anonymous pseudo elements like :first-line or :first-letter. ASSERT(renderer.element()); Ref<RenderStyle> newStyleBeforeAnimation(std::move(newStyle)); CompositeAnimation& rendererAnimations = m_data->ensureCompositeAnimation(&renderer); auto blendedStyle = rendererAnimations.animate(renderer, oldStyle, newStyleBeforeAnimation.get()); if (renderer.parent() || newStyleBeforeAnimation->animations() || (oldStyle && oldStyle->animations())) { m_data->updateAnimationTimerForRenderer(&renderer); #if ENABLE(REQUEST_ANIMATION_FRAME) renderer.view().frameView().scheduleAnimation(); #endif } if (&blendedStyle.get() != &newStyleBeforeAnimation.get()) { // If the animations/transitions change opacity or transform, we need to update // the style to impose the stacking rules. Note that this is also // done in StyleResolver::adjustRenderStyle(). if (blendedStyle.get().hasAutoZIndex() && (blendedStyle.get().opacity() < 1.0f || blendedStyle.get().hasTransform())) blendedStyle.get().setZIndex(0); } return blendedStyle; }
void SVGRenderingContext::prepareToRenderSVGContent(RenderElement& renderer, PaintInfo& paintInfo, NeedsGraphicsContextSave needsGraphicsContextSave) { #ifndef NDEBUG // This function must not be called twice! ASSERT(!(m_renderingFlags & PrepareToRenderSVGContentWasCalled)); m_renderingFlags |= PrepareToRenderSVGContentWasCalled; #endif m_renderer = &renderer; m_paintInfo = &paintInfo; m_filter = 0; // We need to save / restore the context even if the initialization failed. if (needsGraphicsContextSave == SaveGraphicsContext) { m_paintInfo->context().save(); m_renderingFlags |= RestoreGraphicsContext; } auto& style = m_renderer->style(); const SVGRenderStyle& svgStyle = style.svgStyle(); // Setup transparency layers before setting up SVG resources! bool isRenderingMask = isRenderingMaskImage(*m_renderer); // RenderLayer takes care of root opacity. float opacity = (renderer.isSVGRoot() || isRenderingMask) ? 1 : style.opacity(); const ShadowData* shadow = svgStyle.shadow(); bool hasBlendMode = style.hasBlendMode(); bool hasIsolation = style.hasIsolation(); bool isolateMaskForBlending = false; #if ENABLE(CSS_COMPOSITING) if (svgStyle.hasMasker() && is<SVGGraphicsElement>(downcast<SVGElement>(*renderer.element()))) { SVGGraphicsElement& graphicsElement = downcast<SVGGraphicsElement>(*renderer.element()); isolateMaskForBlending = graphicsElement.shouldIsolateBlending(); } #endif if (opacity < 1 || shadow || hasBlendMode || isolateMaskForBlending || hasIsolation) { FloatRect repaintRect = m_renderer->repaintRectInLocalCoordinates(); m_paintInfo->context().clip(repaintRect); if (opacity < 1 || hasBlendMode || isolateMaskForBlending || hasIsolation) { if (hasBlendMode) m_paintInfo->context().setCompositeOperation(m_paintInfo->context().compositeOperation(), style.blendMode()); m_paintInfo->context().beginTransparencyLayer(opacity); if (hasBlendMode) m_paintInfo->context().setCompositeOperation(m_paintInfo->context().compositeOperation(), BlendModeNormal); m_renderingFlags |= EndOpacityLayer; } if (shadow) { m_paintInfo->context().setShadow(IntSize(roundToInt(shadow->x()), roundToInt(shadow->y())), shadow->radius(), shadow->color()); m_paintInfo->context().beginTransparencyLayer(1); m_renderingFlags |= EndShadowLayer; } } ClipPathOperation* clipPathOperation = style.clipPath(); if (is<ShapeClipPathOperation>(clipPathOperation)) { auto& clipPath = downcast<ShapeClipPathOperation>(*clipPathOperation); FloatRect referenceBox; if (clipPath.referenceBox() == Stroke) // FIXME: strokeBoundingBox() takes dasharray into account but shouldn't. referenceBox = renderer.strokeBoundingBox(); else if (clipPath.referenceBox() == ViewBox && renderer.element()) { FloatSize viewportSize; SVGLengthContext(downcast<SVGElement>(renderer.element())).determineViewport(viewportSize); referenceBox.setWidth(viewportSize.width()); referenceBox.setHeight(viewportSize.height()); } else referenceBox = renderer.objectBoundingBox(); m_paintInfo->context().clipPath(clipPath.pathForReferenceRect(referenceBox), clipPath.windRule()); } auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*m_renderer); if (!resources) { if (style.hasReferenceFilterOnly()) return; m_renderingFlags |= RenderingPrepared; return; } if (!isRenderingMask) { if (RenderSVGResourceMasker* masker = resources->masker()) { GraphicsContext* contextPtr = &m_paintInfo->context(); bool result = masker->applyResource(*m_renderer, style, contextPtr, ApplyToDefaultMode); m_paintInfo->setContext(*contextPtr); if (!result) return; } } RenderSVGResourceClipper* clipper = resources->clipper(); if (!clipPathOperation && clipper) { GraphicsContext* contextPtr = &m_paintInfo->context(); bool result = clipper->applyResource(*m_renderer, style, contextPtr, ApplyToDefaultMode); m_paintInfo->setContext(*contextPtr); if (!result) return; } if (!isRenderingMask) { m_filter = resources->filter(); if (m_filter) { m_savedContext = &m_paintInfo->context(); m_savedPaintRect = m_paintInfo->rect; // Return with false here may mean that we don't need to draw the content // (because it was either drawn before or empty) but we still need to apply the filter. m_renderingFlags |= EndFilterLayer; GraphicsContext* contextPtr = &m_paintInfo->context(); bool result = m_filter->applyResource(*m_renderer, style, contextPtr, ApplyToDefaultMode); m_paintInfo->setContext(*contextPtr); if (!result) return; // Since we're caching the resulting bitmap and do not invalidate it on repaint rect // changes, we need to paint the whole filter region. Otherwise, elements not visible // at the time of the initial paint (due to scrolling, window size, etc.) will never // be drawn. m_paintInfo->rect = IntRect(m_filter->drawingRegion(m_renderer)); } } m_renderingFlags |= RenderingPrepared; }
bool SVGResources::buildCachedResources(const RenderElement& renderer, const RenderStyle& style) { ASSERT(renderer.element()); ASSERT_WITH_SECURITY_IMPLICATION(renderer.element()->isSVGElement()); if (!renderer.element()) return false; auto& element = downcast<SVGElement>(*renderer.element()); Document& document = element.document(); SVGDocumentExtensions& extensions = document.accessSVGExtensions(); const AtomicString& tagName = element.localName(); if (tagName.isNull()) return false; const SVGRenderStyle& svgStyle = style.svgStyle(); bool foundResources = false; if (clipperFilterMaskerTags().contains(tagName)) { if (svgStyle.hasClipper()) { AtomicString id(svgStyle.clipperResource()); if (setClipper(getRenderSVGResourceById<RenderSVGResourceClipper>(document, id))) foundResources = true; else registerPendingResource(extensions, id, element); } if (style.hasFilter()) { const FilterOperations& filterOperations = style.filter(); if (filterOperations.size() == 1) { const FilterOperation& filterOperation = *filterOperations.at(0); if (filterOperation.type() == FilterOperation::REFERENCE) { const auto& referenceFilterOperation = downcast<ReferenceFilterOperation>(filterOperation); AtomicString id = SVGURIReference::fragmentIdentifierFromIRIString(referenceFilterOperation.url(), element.document()); if (setFilter(getRenderSVGResourceById<RenderSVGResourceFilter>(document, id))) foundResources = true; else registerPendingResource(extensions, id, element); } } } if (svgStyle.hasMasker()) { AtomicString id(svgStyle.maskerResource()); if (setMasker(getRenderSVGResourceById<RenderSVGResourceMasker>(document, id))) foundResources = true; else registerPendingResource(extensions, id, element); } } if (markerTags().contains(tagName) && svgStyle.hasMarkers()) { AtomicString markerStartId(svgStyle.markerStartResource()); if (setMarkerStart(getRenderSVGResourceById<RenderSVGResourceMarker>(document, markerStartId))) foundResources = true; else registerPendingResource(extensions, markerStartId, element); AtomicString markerMidId(svgStyle.markerMidResource()); if (setMarkerMid(getRenderSVGResourceById<RenderSVGResourceMarker>(document, markerMidId))) foundResources = true; else registerPendingResource(extensions, markerMidId, element); AtomicString markerEndId(svgStyle.markerEndResource()); if (setMarkerEnd(getRenderSVGResourceById<RenderSVGResourceMarker>(document, markerEndId))) foundResources = true; else registerPendingResource(extensions, markerEndId, element); } if (fillAndStrokeTags().contains(tagName)) { if (svgStyle.hasFill()) { bool hasPendingResource = false; AtomicString id; if (setFill(paintingResourceFromSVGPaint(document, svgStyle.fillPaintType(), svgStyle.fillPaintUri(), id, hasPendingResource))) foundResources = true; else if (hasPendingResource) registerPendingResource(extensions, id, element); } if (svgStyle.hasStroke()) { bool hasPendingResource = false; AtomicString id; if (setStroke(paintingResourceFromSVGPaint(document, svgStyle.strokePaintType(), svgStyle.strokePaintUri(), id, hasPendingResource))) foundResources = true; else if (hasPendingResource) registerPendingResource(extensions, id, element); } } if (chainableResourceTags().contains(tagName)) { AtomicString id(targetReferenceFromResource(element)); if (setLinkedResource(getRenderSVGResourceContainerById(document, id))) foundResources = true; else registerPendingResource(extensions, id, element); } return foundResources; }
bool SVGFontData::applySVGGlyphSelection(WidthIterator& iterator, GlyphData& glyphData, bool mirror, unsigned currentCharacter, unsigned& advanceLength) const { const TextRun& run = iterator.run(); Vector<SVGGlyph::ArabicForm>& arabicForms = iterator.arabicForms(); ASSERT(run.charactersLength() >= currentCharacter); // Associate text with arabic forms, if needed. String remainingTextInRun; if (run.is8Bit()) { remainingTextInRun = String(run.data8(currentCharacter), run.charactersLength() - currentCharacter); remainingTextInRun = Font::normalizeSpaces(remainingTextInRun.characters8(), remainingTextInRun.length()); } else { remainingTextInRun = String(run.data16(currentCharacter), run.charactersLength() - currentCharacter); remainingTextInRun = Font::normalizeSpaces(remainingTextInRun.characters16(), remainingTextInRun.length()); } if (mirror) remainingTextInRun = createStringWithMirroredCharacters(remainingTextInRun); if (!currentCharacter && arabicForms.isEmpty()) arabicForms = charactersWithArabicForm(remainingTextInRun, mirror); SVGFontFaceElement* svgFontFaceElement = this->svgFontFaceElement(); ASSERT(svgFontFaceElement); SVGFontElement* associatedFontElement = svgFontFaceElement->associatedFontElement(); ASSERT(associatedFontElement); RenderObject* renderObject = 0; if (TextRun::RenderingContext* renderingContext = run.renderingContext()) renderObject = &static_cast<SVGTextRunRenderingContext*>(renderingContext)->renderer(); String language; bool isVerticalText = false; Vector<String> altGlyphNames; if (renderObject) { RenderElement* parentRenderer = renderObject->isRenderElement() ? toRenderElement(renderObject) : renderObject->parent(); ASSERT(parentRenderer); isVerticalText = parentRenderer->style().svgStyle().isVerticalWritingMode(); if (Element* parentRendererElement = parentRenderer->element()) { language = parentRendererElement->getAttribute(XMLNames::langAttr); if (isSVGAltGlyphElement(parentRendererElement)) { SVGAltGlyphElement* altGlyph = toSVGAltGlyphElement(parentRendererElement); if (!altGlyph->hasValidGlyphElements(altGlyphNames)) altGlyphNames.clear(); } } } Vector<SVGGlyph> glyphs; size_t altGlyphNamesSize = altGlyphNames.size(); if (altGlyphNamesSize) { for (size_t index = 0; index < altGlyphNamesSize; ++index) associatedFontElement->collectGlyphsForGlyphName(altGlyphNames[index], glyphs); // Assign the unicodeStringLength now that its known. size_t glyphsSize = glyphs.size(); for (size_t i = 0; i < glyphsSize; ++i) glyphs[i].unicodeStringLength = run.length(); // Do not check alt glyphs for compatibility. Just return the first one. // Later code will fail if we do not do this and the glyph is incompatible. if (glyphsSize) { SVGGlyph& svgGlyph = glyphs[0]; iterator.setLastGlyphName(svgGlyph.glyphName); glyphData.glyph = svgGlyph.tableEntry; advanceLength = svgGlyph.unicodeStringLength; return true; } } else associatedFontElement->collectGlyphsForString(remainingTextInRun, glyphs); size_t glyphsSize = glyphs.size(); for (size_t i = 0; i < glyphsSize; ++i) { SVGGlyph& svgGlyph = glyphs[i]; if (svgGlyph.isPartOfLigature) continue; if (!isCompatibleGlyph(svgGlyph, isVerticalText, language, arabicForms, currentCharacter, currentCharacter + svgGlyph.unicodeStringLength)) continue; iterator.setLastGlyphName(svgGlyph.glyphName); glyphData.glyph = svgGlyph.tableEntry; advanceLength = svgGlyph.unicodeStringLength; return true; } iterator.setLastGlyphName(String()); return false; }
void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, RenderSVGInlineText* text, const RenderStyle* style) { if (m_inPathLayout && m_textPath.isEmpty()) return; RenderElement* textParent = text->parent(); ASSERT(textParent); SVGElement* lengthContext = downcast<SVGElement>(textParent->element()); bool definesTextLength = parentDefinesTextLength(textParent); const SVGRenderStyle& svgStyle = style->svgStyle(); m_visualMetricsListOffset = 0; m_visualCharacterOffset = 0; Vector<SVGTextMetrics>& visualMetricsValues = text->layoutAttributes()->textMetricsValues(); ASSERT(!visualMetricsValues.isEmpty()); auto upconvertedCharacters = StringView(text->text()).upconvertedCharacters(); const UChar* characters = upconvertedCharacters; const Font& font = style->font(); SVGTextLayoutEngineSpacing spacingLayout(font); SVGTextLayoutEngineBaseline baselineLayout(font); bool didStartTextFragment = false; bool applySpacingToNextCharacter = false; float lastAngle = 0; float baselineShift = baselineLayout.calculateBaselineShift(&svgStyle, lengthContext); baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, text); // Main layout algorithm. while (true) { // Find the start of the current text box in this list, respecting ligatures. SVGTextMetrics visualMetrics(SVGTextMetrics::SkippedSpaceMetrics); if (!currentVisualCharacterMetrics(textBox, visualMetricsValues, visualMetrics)) break; if (visualMetrics.isEmpty()) { advanceToNextVisualCharacter(visualMetrics); continue; } SVGTextLayoutAttributes* logicalAttributes = 0; if (!currentLogicalCharacterAttributes(logicalAttributes)) break; ASSERT(logicalAttributes); SVGTextMetrics logicalMetrics(SVGTextMetrics::SkippedSpaceMetrics); if (!currentLogicalCharacterMetrics(logicalAttributes, logicalMetrics)) break; SVGCharacterDataMap& characterDataMap = logicalAttributes->characterDataMap(); SVGCharacterData data; SVGCharacterDataMap::iterator it = characterDataMap.find(m_logicalCharacterOffset + 1); if (it != characterDataMap.end()) data = it->value; float x = data.x; float y = data.y; // When we've advanced to the box start offset, determine using the original x/y values, // whether this character starts a new text chunk, before doing any further processing. if (m_visualCharacterOffset == textBox->start()) textBox->setStartsNewTextChunk(logicalAttributes->context().characterStartsNewTextChunk(m_logicalCharacterOffset)); float angle = data.rotate == SVGTextLayoutAttributes::emptyValue() ? 0 : data.rotate; // Calculate glyph orientation angle. const UChar* currentCharacter = characters + m_visualCharacterOffset; float orientationAngle = baselineLayout.calculateGlyphOrientationAngle(m_isVerticalText, &svgStyle, *currentCharacter); // Calculate glyph advance & x/y orientation shifts. float xOrientationShift = 0; float yOrientationShift = 0; float glyphAdvance = baselineLayout.calculateGlyphAdvanceAndOrientation(m_isVerticalText, visualMetrics, orientationAngle, xOrientationShift, yOrientationShift); // Assign current text position to x/y values, if needed. updateCharacerPositionIfNeeded(x, y); // Apply dx/dy value adjustments to current text position, if needed. updateRelativePositionAdjustmentsIfNeeded(data.dx, data.dy); // Calculate SVG Fonts kerning, if needed. float kerning = spacingLayout.calculateSVGKerning(m_isVerticalText, visualMetrics.glyph()); // Calculate CSS 'kerning', 'letter-spacing' and 'word-spacing' for next character, if needed. float spacing = spacingLayout.calculateCSSKerningAndSpacing(&svgStyle, lengthContext, currentCharacter); float textPathOffset = 0; if (m_inPathLayout) { float scaledGlyphAdvance = glyphAdvance * m_textPathScaling; if (m_isVerticalText) { // If there's an absolute y position available, it marks the beginning of a new position along the path. if (y != SVGTextLayoutAttributes::emptyValue()) m_textPathCurrentOffset = y + m_textPathStartOffset; m_textPathCurrentOffset += m_dy - kerning; m_dy = 0; // Apply dx/dy correction and setup translations that move to the glyph midpoint. xOrientationShift += m_dx + baselineShift; yOrientationShift -= scaledGlyphAdvance / 2; } else { // If there's an absolute x position available, it marks the beginning of a new position along the path. if (x != SVGTextLayoutAttributes::emptyValue()) m_textPathCurrentOffset = x + m_textPathStartOffset; m_textPathCurrentOffset += m_dx - kerning; m_dx = 0; // Apply dx/dy correction and setup translations that move to the glyph midpoint. xOrientationShift -= scaledGlyphAdvance / 2; yOrientationShift += m_dy - baselineShift; } // Calculate current offset along path. textPathOffset = m_textPathCurrentOffset + scaledGlyphAdvance / 2; // Move to next character. m_textPathCurrentOffset += scaledGlyphAdvance + m_textPathSpacing + spacing * m_textPathScaling; // Skip character, if we're before the path. if (textPathOffset < 0) { advanceToNextLogicalCharacter(logicalMetrics); advanceToNextVisualCharacter(visualMetrics); continue; } // Stop processing, if the next character lies behind the path. if (textPathOffset > m_textPathLength) break; bool ok = false; FloatPoint point = m_textPath.pointAtLength(textPathOffset, ok); ASSERT(ok); x = point.x(); y = point.y(); angle = m_textPath.normalAngleAtLength(textPathOffset, ok); ASSERT(ok); // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle! if (m_isVerticalText) angle -= 90; } else { // Apply all previously calculated shift values. if (m_isVerticalText) { x += baselineShift; y -= kerning; } else { x -= kerning; y -= baselineShift; } x += m_dx; y += m_dy; } // Determine whether we have to start a new fragment. bool shouldStartNewFragment = m_dx || m_dy || m_isVerticalText || m_inPathLayout || angle || angle != lastAngle || orientationAngle || kerning || applySpacingToNextCharacter || definesTextLength; // If we already started a fragment, close it now. if (didStartTextFragment && shouldStartNewFragment) { applySpacingToNextCharacter = false; recordTextFragment(textBox, visualMetricsValues); } // Eventually start a new fragment, if not yet done. if (!didStartTextFragment || shouldStartNewFragment) { ASSERT(!m_currentTextFragment.characterOffset); ASSERT(!m_currentTextFragment.length); didStartTextFragment = true; m_currentTextFragment.characterOffset = m_visualCharacterOffset; m_currentTextFragment.metricsListOffset = m_visualMetricsListOffset; m_currentTextFragment.x = x; m_currentTextFragment.y = y; // Build fragment transformation. if (angle) m_currentTextFragment.transform.rotate(angle); if (xOrientationShift || yOrientationShift) m_currentTextFragment.transform.translate(xOrientationShift, yOrientationShift); if (orientationAngle) m_currentTextFragment.transform.rotate(orientationAngle); m_currentTextFragment.isTextOnPath = m_inPathLayout && m_textPathScaling != 1; if (m_currentTextFragment.isTextOnPath) { if (m_isVerticalText) m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(1, m_textPathScaling); else m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(m_textPathScaling, 1); } } // Update current text position, after processing of the current character finished. if (m_inPathLayout) updateCurrentTextPosition(x, y, glyphAdvance); else { // Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed. if (spacing) applySpacingToNextCharacter = true; float xNew = x - m_dx; float yNew = y - m_dy; if (m_isVerticalText) xNew -= baselineShift; else yNew += baselineShift; updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing); } advanceToNextLogicalCharacter(logicalMetrics); advanceToNextVisualCharacter(visualMetrics); lastAngle = angle; } if (!didStartTextFragment) return; // Close last open fragment, if needed. recordTextFragment(textBox, visualMetricsValues); }
// One of the element types that can cause graphics to be drawn onto the target canvas. Specifically: circle, ellipse, // image, line, path, polygon, polyline, rect, text and use. static bool isGraphicsElement(const RenderElement& renderer) { return renderer.isSVGShape() || renderer.isSVGText() || renderer.isSVGImage() || renderer.element()->hasTagName(SVGNames::useTag); }
PassRefPtr<RenderStyle> RenderNamedFlowFragment::computeStyleInRegion(RenderElement& renderer, RenderStyle& parentStyle) { ASSERT(!renderer.isAnonymous()); // FIXME: Region styling fails for pseudo-elements because the renderers don't have a node. RefPtr<RenderStyle> renderObjectRegionStyle = renderer.view().document().ensureStyleResolver().styleForElement(renderer.element(), &parentStyle, DisallowStyleSharing, MatchAllRules, this); return renderObjectRegionStyle.release(); }