void FloatRect::unite(const FloatRect& other) { // Handle empty special cases first. if (other.isEmpty()) return; if (isEmpty()) { *this = other; return; } float l = min(x(), other.x()); float t = min(y(), other.y()); float r = max(maxX(), other.maxX()); float b = max(maxY(), other.maxY()); setLocationAndSizeFromEdges(l, t, r, b); }
void FloatRect::intersect(const FloatRect& other) { float left = std::max(x(), other.x()); float top = std::max(y(), other.y()); float right = std::min(maxX(), other.maxX()); float bottom = std::min(maxY(), other.maxY()); // Return a clean empty rectangle for non-intersecting cases. if (left >= right || top >= bottom) { left = 0; top = 0; right = 0; bottom = 0; } setLocationAndSizeFromEdges(left, top, right, bottom); }
FloatSize StickyPositionViewportConstraints::computeStickyOffset(const FloatRect& viewportRect) const { FloatRect boxRect = m_absoluteStickyBoxRect; if (hasAnchorEdge(AnchorEdgeRight)) { float rightLimit = viewportRect.maxX() - m_rightOffset; float rightDelta = std::min<float>(0, rightLimit - m_absoluteStickyBoxRect.maxX()); float availableSpace = std::min<float>(0, m_absoluteContainingBlockRect.x() - m_absoluteStickyBoxRect.x()); if (rightDelta < availableSpace) rightDelta = availableSpace; boxRect.move(rightDelta, 0); } if (hasAnchorEdge(AnchorEdgeLeft)) { float leftLimit = viewportRect.x() + m_leftOffset; float leftDelta = std::max<float>(0, leftLimit - m_absoluteStickyBoxRect.x()); float availableSpace = std::max<float>(0, m_absoluteContainingBlockRect.maxX() - m_absoluteStickyBoxRect.maxX()); if (leftDelta > availableSpace) leftDelta = availableSpace; boxRect.move(leftDelta, 0); } if (hasAnchorEdge(AnchorEdgeBottom)) { float bottomLimit = viewportRect.maxY() - m_bottomOffset; float bottomDelta = std::min<float>(0, bottomLimit - m_absoluteStickyBoxRect.maxY()); float availableSpace = std::min<float>(0, m_absoluteContainingBlockRect.y() - m_absoluteStickyBoxRect.y()); if (bottomDelta < availableSpace) bottomDelta = availableSpace; boxRect.move(0, bottomDelta); } if (hasAnchorEdge(AnchorEdgeTop)) { float topLimit = viewportRect.y() + m_topOffset; float topDelta = std::max<float>(0, topLimit - m_absoluteStickyBoxRect.y()); float availableSpace = std::max<float>(0, m_absoluteContainingBlockRect.maxY() - m_absoluteStickyBoxRect.maxY()); if (topDelta > availableSpace) topDelta = availableSpace; boxRect.move(0, topDelta); } return boxRect.location() - m_absoluteStickyBoxRect.location(); }
void FloatRect::uniteIfNonZero(const FloatRect& other) { // Handle empty special cases first. if (!other.width() && !other.height()) return; if (!width() && !height()) { *this = other; return; } float left = min(x(), other.x()); float top = min(y(), other.y()); float right = max(maxX(), other.maxX()); float bottom = max(maxY(), other.maxY()); setLocationAndSizeFromEdges(left, top, right, bottom); }
void FloatRect::intersect(const FloatRect& other) { float l = max(x(), other.x()); float t = max(y(), other.y()); float r = min(maxX(), other.maxX()); float b = min(maxY(), other.maxY()); // Return a clean empty rectangle for non-intersecting cases. if (l >= r || t >= b) { l = 0; t = 0; r = 0; b = 0; } setLocationAndSizeFromEdges(l, t, r, b); }
WindowFeatures::WindowFeatures(const String& dialogFeaturesString, const FloatRect& screenAvailableRect) : widthSet(true) , heightSet(true) , menuBarVisible(false) , toolBarVisible(false) , locationBarVisible(false) , fullscreen(false) , dialog(true) { DialogFeaturesMap features; parseDialogFeatures(dialogFeaturesString, features); const bool trusted = false; // The following features from Microsoft's documentation are not implemented: // - default font settings // - width, height, left, and top specified in units other than "px" // - edge (sunken or raised, default is raised) // - dialogHide: trusted && boolFeature(features, "dialoghide"), makes dialog hide when you print // - help: boolFeature(features, "help", true), makes help icon appear in dialog (what does it do on Windows?) // - unadorned: trusted && boolFeature(features, "unadorned"); width = floatFeature(features, "dialogwidth", 100, screenAvailableRect.width(), 620); // default here came from frame size of dialog in MacIE height = floatFeature(features, "dialogheight", 100, screenAvailableRect.height(), 450); // default here came from frame size of dialog in MacIE x = floatFeature(features, "dialogleft", screenAvailableRect.x(), screenAvailableRect.maxX() - width, -1); xSet = x > 0; y = floatFeature(features, "dialogtop", screenAvailableRect.y(), screenAvailableRect.maxY() - height, -1); ySet = y > 0; if (boolFeature(features, "center", true)) { if (!xSet) { x = screenAvailableRect.x() + (screenAvailableRect.width() - width) / 2; xSet = true; } if (!ySet) { y = screenAvailableRect.y() + (screenAvailableRect.height() - height) / 2; ySet = true; } } resizable = boolFeature(features, "resizable"); scrollbarsVisible = boolFeature(features, "scroll", true); statusBarVisible = boolFeature(features, "status", !trusted); }
void PlatformContextSkia::beginLayerClippedToImage(const FloatRect& rect, const ImageBuffer* imageBuffer) { SkRect bounds = { SkFloatToScalar(rect.x()), SkFloatToScalar(rect.y()), SkFloatToScalar(rect.maxX()), SkFloatToScalar(rect.maxY()) }; if (imageBuffer->internalSize().isEmpty()) { m_canvas->clipRect(bounds); return; } // Skia doesn't support clipping to an image, so we create a layer. The next // time restore is invoked the layer and |imageBuffer| are combined to // create the resulting image. m_state->m_clip = bounds; // Get the absolute coordinates of the stored clipping rectangle to make it // independent of any transform changes. canvas()->getTotalMatrix().mapRect(&m_state->m_clip); SkCanvas::SaveFlags saveFlags = static_cast<SkCanvas::SaveFlags>(SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kFullColorLayer_SaveFlag); saveLayer(&bounds, 0, saveFlags); const SkBitmap* bitmap = imageBuffer->context()->platformContext()->bitmap(); if (m_trackOpaqueRegion) { SkRect opaqueRect = bitmap->isOpaque() ? m_state->m_clip : SkRect::MakeEmpty(); m_opaqueRegion.setImageMask(opaqueRect); } // Copy off the image as |imageBuffer| may be deleted before restore is invoked. if (!bitmap->pixelRef()) { // The bitmap owns it's pixels. This happens when we've allocated the // pixels in some way and assigned them directly to the bitmap (as // happens when we allocate a DIB). In this case the assignment operator // does not copy the pixels, rather the copied bitmap ends up // referencing the same pixels. As the pixels may not live as long as we // need it to, we copy the image. bitmap->copyTo(&m_state->m_imageBufferClip, SkBitmap::kARGB_8888_Config); } else { // If there is a pixel ref, we can safely use the assignment operator. m_state->m_imageBufferClip = *bitmap; } }
WindowFeatures parseDialogFeatures(const String& dialogFeaturesString, const FloatRect& screenAvailableRect) { auto featuresMap = parseDialogFeaturesMap(dialogFeaturesString); // The following features from Microsoft's documentation are not implemented: // - default font settings // - width, height, left, and top specified in units other than "px" // - edge (sunken or raised, default is raised) // - dialogHide: trusted && boolFeature(features, "dialoghide"), makes dialog hide when you print // - help: boolFeature(features, "help", true), makes help icon appear in dialog (what does it do on Windows?) // - unadorned: trusted && boolFeature(features, "unadorned"); WindowFeatures features; features.menuBarVisible = false; features.toolBarVisible = false; features.locationBarVisible = false; features.dialog = true; float width = floatFeature(featuresMap, "dialogwidth", 100, screenAvailableRect.width()).valueOr(620); // default here came from frame size of dialog in MacIE float height = floatFeature(featuresMap, "dialogheight", 100, screenAvailableRect.height()).valueOr(450); // default here came from frame size of dialog in MacIE features.width = width; features.height = height; features.x = floatFeature(featuresMap, "dialogleft", screenAvailableRect.x(), screenAvailableRect.maxX() - width); features.y = floatFeature(featuresMap, "dialogtop", screenAvailableRect.y(), screenAvailableRect.maxY() - height); if (boolFeature(featuresMap, "center").valueOr(true)) { if (!features.x) features.x = screenAvailableRect.x() + (screenAvailableRect.width() - width) / 2; if (!features.y) features.y = screenAvailableRect.y() + (screenAvailableRect.height() - height) / 2; } features.resizable = boolFeature(featuresMap, "resizable").valueOr(false); features.scrollbarsVisible = boolFeature(featuresMap, "scroll").valueOr(true); features.statusBarVisible = boolFeature(featuresMap, "status").valueOr(false); return features; }
static inline FloatPoint rightMostCornerToVector(const FloatRect& rect, const FloatSize& vector) { // Return the corner of the rectangle that if it is to the left of the vector // would mean all of the rectangle is to the left of the vector. // The vector here represents the side between two points in a clockwise convex polygon. // // Q XXX // QQQ XXX If the lower left corner of X is left of the vector that goes from the top corner of Q to // QQQ the right corner of Q, then all of X is left of the vector, and intersection impossible. // Q // FloatPoint point; if (vector.width() >= 0) point.setY(rect.maxY()); else point.setY(rect.y()); if (vector.height() >= 0) point.setX(rect.x()); else point.setX(rect.maxX()); return point; }
void PlatformContextSkia::beginLayerClippedToImage(const FloatRect& rect, const ImageBuffer* imageBuffer) { SkRect bounds = { SkFloatToScalar(rect.x()), SkFloatToScalar(rect.y()), SkFloatToScalar(rect.maxX()), SkFloatToScalar(rect.maxY()) }; if (imageBuffer->internalSize().isEmpty()) { m_canvas->clipRect(bounds); return; } // Skia doesn't support clipping to an image, so we create a layer. The next // time restore is invoked the layer and |imageBuffer| are combined to // create the resulting image. m_state->m_clip = bounds; // Get the absolute coordinates of the stored clipping rectangle to make it // independent of any transform changes. canvas()->getTotalMatrix().mapRect(&m_state->m_clip); SkCanvas::SaveFlags saveFlags = static_cast<SkCanvas::SaveFlags>(SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kFullColorLayer_SaveFlag); saveLayer(&bounds, 0, saveFlags); const SkBitmap* bitmap = imageBuffer->context()->platformContext()->bitmap(); if (m_trackOpaqueRegion) { SkRect opaqueRect = bitmap->isOpaque() ? m_state->m_clip : SkRect::MakeEmpty(); m_opaqueRegion.setImageMask(opaqueRect); } // Copy off the image as |imageBuffer| may be deleted before restore is invoked. if (bitmap->isImmutable()) m_state->m_imageBufferClip = *bitmap; else { // We need to make a deep-copy of the pixels themselves, so they don't // change on us between now and when we want to apply them in restore() bitmap->copyTo(&m_state->m_imageBufferClip, SkBitmap::kARGB_8888_Config); } }
bool RenderThemeWinCE::paintMediaMuteButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) { bool rc = paintButton(o, paintInfo, r); HTMLMediaElement* mediaElement = mediaElementParent(o->node()); bool muted = !mediaElement || mediaElement->muted(); FloatRect imRect = r; imRect.inflate(-2); paintInfo.context->save(); paintInfo.context->setStrokeColor(Color::black); paintInfo.context->setFillColor(Color::black); FloatPoint pts[6] = { FloatPoint(imRect.x() + 1, imRect.y() + imRect.height() / 3.0), FloatPoint(imRect.x() + 1 + imRect.width() / 2.0, imRect.y() + imRect.height() / 3.0), FloatPoint(imRect.maxX() - 1, imRect.y()), FloatPoint(imRect.maxX() - 1, imRect.maxY()), FloatPoint(imRect.x() + 1 + imRect.width() / 2.0, imRect.y() + 2.0 * imRect.height() / 3.0), FloatPoint(imRect.x() + 1, imRect.y() + 2.0 * imRect.height() / 3.0) }; paintInfo.context->drawConvexPolygon(6, pts); if (muted) paintInfo.context->drawLine(IntPoint(imRect.maxX(), imRect.y()), IntPoint(imRect.x(), imRect.maxY())); paintInfo.context->restore(); return rc; }
bool FloatRect::contains(const FloatRect& other) const { return x() <= other.x() && maxX() >= other.maxX() && y() <= other.y() && maxY() >= other.maxY(); }
bool RenderThemeWinCE::paintMediaPlayButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) { bool rc = paintButton(o, paintInfo, r); FloatRect imRect = r; imRect.inflate(-3); paintInfo.context->save(); paintInfo.context->setStrokeColor(Color::black); paintInfo.context->setFillColor(Color::black); HTMLMediaElement* mediaElement = mediaElementParent(o->node()); bool paused = !mediaElement || mediaElement->paused(); if (paused) { float width = imRect.width(); imRect.setWidth(width / 3.0); paintInfo.context->fillRect(imRect); imRect.move(2.0 * width / 3.0, 0); paintInfo.context->fillRect(imRect); } else { FloatPoint pts[3] = { FloatPoint(imRect.x(), imRect.y()), FloatPoint(imRect.maxX(), (imRect.y() + imRect.maxY()) / 2.0), FloatPoint(imRect.x(), imRect.maxY()) }; paintInfo.context->drawConvexPolygon(3, pts); } paintInfo.context->restore(); return rc; }
FloatRect TileController::computeTileCoverageRect(const FloatSize& newSize, const FloatRect& previousVisibleRect, const FloatRect& visibleRect, float contentsScale) const { // If the page is not in a window (for example if it's in a background tab), we limit the tile coverage rect to the visible rect. if (!m_isInWindow) return visibleRect; #if PLATFORM(IOS) // FIXME: unify the iOS and Mac code. UNUSED_PARAM(previousVisibleRect); if (m_tileCoverage == CoverageForVisibleArea || MemoryPressureHandler::singleton().isUnderMemoryPressure()) return visibleRect; double horizontalMargin = tileSize().width() / contentsScale; double verticalMargin = tileSize().height() / contentsScale; double currentTime = monotonicallyIncreasingTime(); double timeDelta = currentTime - m_velocity.lastUpdateTime; FloatRect futureRect = visibleRect; futureRect.setLocation(FloatPoint( futureRect.location().x() + timeDelta * m_velocity.horizontalVelocity, futureRect.location().y() + timeDelta * m_velocity.verticalVelocity)); if (m_velocity.horizontalVelocity) { futureRect.setWidth(futureRect.width() + horizontalMargin); if (m_velocity.horizontalVelocity < 0) futureRect.setX(futureRect.x() - horizontalMargin); } if (m_velocity.verticalVelocity) { futureRect.setHeight(futureRect.height() + verticalMargin); if (m_velocity.verticalVelocity < 0) futureRect.setY(futureRect.y() - verticalMargin); } if (!m_velocity.horizontalVelocity && !m_velocity.verticalVelocity) { if (m_velocity.scaleChangeRate > 0) return visibleRect; futureRect.setWidth(futureRect.width() + horizontalMargin); futureRect.setHeight(futureRect.height() + verticalMargin); futureRect.setX(futureRect.x() - horizontalMargin / 2); futureRect.setY(futureRect.y() - verticalMargin / 2); } // Can't use m_tileCacheLayer->bounds() here, because the size of the underlying platform layer // hasn't been updated for the current commit. IntSize contentSize = expandedIntSize(newSize); if (futureRect.maxX() > contentSize.width()) futureRect.setX(contentSize.width() - futureRect.width()); if (futureRect.maxY() > contentSize.height()) futureRect.setY(contentSize.height() - futureRect.height()); if (futureRect.x() < 0) futureRect.setX(0); if (futureRect.y() < 0) futureRect.setY(0); return futureRect; #else UNUSED_PARAM(contentsScale); // FIXME: look at how far the document can scroll in each dimension. float coverageHorizontalSize = visibleRect.width(); float coverageVerticalSize = visibleRect.height(); bool largeVisibleRectChange = !previousVisibleRect.isEmpty() && !visibleRect.intersects(previousVisibleRect); // Inflate the coverage rect so that it covers 2x of the visible width and 3x of the visible height. // These values were chosen because it's more common to have tall pages and to scroll vertically, // so we keep more tiles above and below the current area. if (m_tileCoverage & CoverageForHorizontalScrolling && !largeVisibleRectChange) coverageHorizontalSize *= 2; if (m_tileCoverage & CoverageForVerticalScrolling && !largeVisibleRectChange) coverageVerticalSize *= 3; coverageVerticalSize += topMarginHeight() + bottomMarginHeight(); coverageHorizontalSize += leftMarginWidth() + rightMarginWidth(); // Can't use m_tileCacheLayer->bounds() here, because the size of the underlying platform layer // hasn't been updated for the current commit. FloatRect coverageBounds = boundsForSize(newSize); float coverageLeft = visibleRect.x() - (coverageHorizontalSize - visibleRect.width()) / 2; coverageLeft = std::min(coverageLeft, coverageBounds.maxX() - coverageHorizontalSize); coverageLeft = std::max(coverageLeft, coverageBounds.x()); float coverageTop = visibleRect.y() - (coverageVerticalSize - visibleRect.height()) / 2; coverageTop = std::min(coverageTop, coverageBounds.maxY() - coverageVerticalSize); coverageTop = std::max(coverageTop, coverageBounds.y()); return FloatRect(coverageLeft, coverageTop, coverageHorizontalSize, coverageVerticalSize); #endif }
bool FloatRect::intersects(const FloatRect& other) const { // Checking emptiness handles negative widths as well as zero. return !isEmpty() && !other.isEmpty() && x() < other.maxX() && other.x() < maxX() && y() < other.maxY() && other.y() < maxY(); }
inline unsigned WidthIterator::advanceInternal(TextIterator& textIterator, GlyphBuffer* glyphBuffer) { bool rtl = m_run.rtl(); bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_expansion) && !m_run.spacingDisabled(); float widthSinceLastRounding = m_runWidthSoFar; m_runWidthSoFar = floorf(m_runWidthSoFar); widthSinceLastRounding -= m_runWidthSoFar; float lastRoundingWidth = m_finalRoundingWidth; FloatRect bounds; const SimpleFontData* primaryFont = m_font->primaryFont(); const SimpleFontData* lastFontData = primaryFont; int lastGlyphCount = glyphBuffer ? glyphBuffer->size() : 0; UChar32 character = 0; unsigned clusterLength = 0; CharactersTreatedAsSpace charactersTreatedAsSpace; while (textIterator.consume(character, clusterLength)) { unsigned advanceLength = clusterLength; const GlyphData& glyphData = glyphDataForCharacter(character, rtl, textIterator.currentCharacter(), advanceLength); Glyph glyph = glyphData.glyph; const SimpleFontData* fontData = glyphData.fontData; ASSERT(fontData); // Now that we have a glyph and font data, get its width. float width; if (character == '\t' && m_run.allowTabs()) width = m_font->tabWidth(*fontData, m_run.tabSize(), m_run.xPos() + m_runWidthSoFar + widthSinceLastRounding); else { width = fontData->widthForGlyph(glyph); #if ENABLE(SVG) // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text. width *= m_run.horizontalGlyphStretch(); #endif // We special case spaces in two ways when applying word rounding. // First, we round spaces to an adjusted width in all fonts. // Second, in fixed-pitch fonts we ensure that all characters that // match the width of the space character have the same width as the space character. if (m_run.applyWordRounding() && width == fontData->spaceWidth() && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph())) width = fontData->adjustedSpaceWidth(); } if (fontData != lastFontData && width) { if (shouldApplyFontTransforms()) m_runWidthSoFar += applyFontTransforms(glyphBuffer, m_run.ltr(), lastGlyphCount, lastFontData, m_typesettingFeatures, charactersTreatedAsSpace); lastFontData = fontData; if (m_fallbackFonts && fontData != primaryFont) { // FIXME: This does a little extra work that could be avoided if // glyphDataForCharacter() returned whether it chose to use a small caps font. if (!m_font->isSmallCaps() || character == toUpper(character)) m_fallbackFonts->add(fontData); else { const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(character), rtl); if (uppercaseGlyphData.fontData != primaryFont) m_fallbackFonts->add(uppercaseGlyphData.fontData); } } } if (hasExtraSpacing) { // Account for letter-spacing. if (width && m_font->letterSpacing()) width += m_font->letterSpacing(); static bool expandAroundIdeographs = Font::canExpandAroundIdeographsInComplexText(); bool treatAsSpace = Font::treatAsSpace(character); if (treatAsSpace || (expandAroundIdeographs && Font::isCJKIdeographOrSymbol(character))) { // Distribute the run's total expansion evenly over all expansion opportunities in the run. if (m_expansion) { float previousExpansion = m_expansion; if (!treatAsSpace && !m_isAfterExpansion) { // Take the expansion opportunity before this ideograph. m_expansion -= m_expansionPerOpportunity; float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion); m_runWidthSoFar += expansionAtThisOpportunity; if (glyphBuffer) { if (glyphBuffer->isEmpty()) glyphBuffer->add(fontData->spaceGlyph(), fontData, expansionAtThisOpportunity); else glyphBuffer->expandLastAdvance(expansionAtThisOpportunity); } previousExpansion = m_expansion; } if (m_run.allowsTrailingExpansion() || (m_run.ltr() && textIterator.currentCharacter() + advanceLength < static_cast<size_t>(m_run.length())) || (m_run.rtl() && textIterator.currentCharacter())) { m_expansion -= m_expansionPerOpportunity; width += !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion); m_isAfterExpansion = true; } } else m_isAfterExpansion = false; // Account for word spacing. // We apply additional space between "words" by adding width to the space character. if (treatAsSpace && (character != '\t' || !m_run.allowTabs()) && textIterator.currentCharacter() && m_font->wordSpacing()) width += m_font->wordSpacing(); } else m_isAfterExpansion = false; } if (shouldApplyFontTransforms() && glyphBuffer && Font::treatAsSpace(character)) charactersTreatedAsSpace.append(make_pair(glyphBuffer->size(), OriginalAdvancesForCharacterTreatedAsSpace(character == ' ', glyphBuffer->size() ? glyphBuffer->advanceAt(glyphBuffer->size() - 1) : 0, width))); if (m_accountForGlyphBounds) { bounds = fontData->boundsForGlyph(glyph); if (!textIterator.currentCharacter()) m_firstGlyphOverflow = max<float>(0, -bounds.x()); } if (m_forTextEmphasis && !Font::canReceiveTextEmphasis(character)) glyph = 0; // Advance past the character we just dealt with. textIterator.advance(advanceLength); float oldWidth = width; // Force characters that are used to determine word boundaries for the rounding hack // to be integer width, so following words will start on an integer boundary. if (m_run.applyWordRounding() && Font::isRoundingHackCharacter(character)) { width = ceilf(width); // Since widthSinceLastRounding can lose precision if we include measurements for // preceding whitespace, we bypass it here. m_runWidthSoFar += width; // Since this is a rounding hack character, we should have reset this sum on the previous // iteration. ASSERT(!widthSinceLastRounding); } else { // Check to see if the next character is a "rounding hack character", if so, adjust // width so that the total run width will be on an integer boundary. if ((m_run.applyWordRounding() && textIterator.currentCharacter() < m_run.length() && Font::isRoundingHackCharacter(*(textIterator.characters()))) || (m_run.applyRunRounding() && textIterator.currentCharacter() >= m_run.length())) { float totalWidth = widthSinceLastRounding + width; widthSinceLastRounding = ceilf(totalWidth); width += widthSinceLastRounding - totalWidth; m_runWidthSoFar += widthSinceLastRounding; widthSinceLastRounding = 0; } else widthSinceLastRounding += width; } if (glyphBuffer) glyphBuffer->add(glyph, fontData, (rtl ? oldWidth + lastRoundingWidth : width)); lastRoundingWidth = width - oldWidth; if (m_accountForGlyphBounds) { m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, bounds.maxY()); m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, bounds.y()); m_lastGlyphOverflow = max<float>(0, bounds.maxX() - width); } } if (shouldApplyFontTransforms()) m_runWidthSoFar += applyFontTransforms(glyphBuffer, m_run.ltr(), lastGlyphCount, lastFontData, m_typesettingFeatures, charactersTreatedAsSpace); unsigned consumedCharacters = textIterator.currentCharacter() - m_currentCharacter; m_currentCharacter = textIterator.currentCharacter(); m_runWidthSoFar += widthSinceLastRounding; m_finalRoundingWidth = lastRoundingWidth; return consumedCharacters; }
IntRect PopupContainer::layoutAndCalculateWidgetRect(int targetControlHeight, const IntPoint& popupInitialCoordinate) { // Reset the max width and height to their default values, they will be recomputed below // if necessary. m_listBox->setMaxHeight(kMaxHeight); m_listBox->setMaxWidth(std::numeric_limits<int>::max()); // Lay everything out to figure out our preferred size, then tell the view's // WidgetClient about it. It should assign us a client. int rtlOffset = layoutAndGetRTLOffset(); bool isRTL = this->isRTL(); int rightOffset = isRTL ? rtlOffset : 0; // Assume m_listBox size is already calculated. IntSize targetSize(m_listBox->width() + kBorderSize * 2, m_listBox->height() + kBorderSize * 2); IntRect widgetRect; ChromeClientChromium* chromeClient = chromeClientChromium(); if (chromeClient) { // If the popup would extend past the bottom of the screen, open upwards // instead. FloatRect screen = screenAvailableRect(m_frameView.get()); // Use popupInitialCoordinate.x() + rightOffset because RTL position // needs to be considered. widgetRect = chromeClient->rootViewToScreen(IntRect(popupInitialCoordinate.x() + rightOffset, popupInitialCoordinate.y(), targetSize.width(), targetSize.height())); // If we have multiple screens and the browser rect is in one screen, we have // to clip the window width to the screen width. // When clipping, we also need to set a maximum width for the list box. FloatRect windowRect = chromeClient->windowRect(); if (windowRect.x() >= screen.x() && windowRect.maxX() <= screen.maxX() && (widgetRect.x() < screen.x() || widgetRect.maxX() > screen.maxX())) { // First, inverse the popup alignment if it does not fit the screen - this might fix things (or make them better). IntRect inverseWidgetRect = chromeClient->rootViewToScreen(IntRect(popupInitialCoordinate.x() + (isRTL ? 0 : rtlOffset), popupInitialCoordinate.y(), targetSize.width(), targetSize.height())); IntRect enclosingScreen = enclosingIntRect(screen); unsigned originalCutoff = max(enclosingScreen.x() - widgetRect.x(), 0) + max(widgetRect.maxX() - enclosingScreen.maxX(), 0); unsigned inverseCutoff = max(enclosingScreen.x() - inverseWidgetRect.x(), 0) + max(inverseWidgetRect.maxX() - enclosingScreen.maxX(), 0); // Accept the inverse popup alignment if the trimmed content gets shorter than that in the original alignment case. if (inverseCutoff < originalCutoff) widgetRect = inverseWidgetRect; if (widgetRect.x() < screen.x()) { unsigned widgetRight = widgetRect.maxX(); widgetRect.setWidth(widgetRect.maxX() - screen.x()); widgetRect.setX(widgetRight - widgetRect.width()); listBox()->setMaxWidthAndLayout(max(widgetRect.width() - kBorderSize * 2, 0)); } else if (widgetRect.maxX() > screen.maxX()) { widgetRect.setWidth(screen.maxX() - widgetRect.x()); listBox()->setMaxWidthAndLayout(max(widgetRect.width() - kBorderSize * 2, 0)); } } // Calculate Y axis size. if (widgetRect.maxY() > static_cast<int>(screen.maxY())) { if (widgetRect.y() - widgetRect.height() - targetControlHeight > 0) { // There is enough room to open upwards. widgetRect.move(0, -(widgetRect.height() + targetControlHeight)); } else { // Figure whether upwards or downwards has more room and set the // maximum number of items. int spaceAbove = widgetRect.y() - targetControlHeight; int spaceBelow = screen.maxY() - widgetRect.y(); if (spaceAbove > spaceBelow) m_listBox->setMaxHeight(spaceAbove); else m_listBox->setMaxHeight(spaceBelow); layoutAndGetRTLOffset(); // Our height has changed, so recompute only Y axis of widgetRect. // We don't have to recompute X axis, so we only replace Y axis // in widgetRect. IntRect frameInScreen = chromeClient->rootViewToScreen(frameRect()); widgetRect.setY(frameInScreen.y()); widgetRect.setHeight(frameInScreen.height()); // And move upwards if necessary. if (spaceAbove > spaceBelow) widgetRect.move(0, -(widgetRect.height() + targetControlHeight)); } } } return widgetRect; }
void GraphicsContextPlatformPrivate::clip(const FloatRect& clipRect) { if (!m_hdc) return; IntersectClipRect(m_hdc, (int)clipRect.x(), (int)clipRect.y(), (int)clipRect.maxX(), (int)clipRect.maxY()); }
void FindIndicator::draw(GraphicsContext& graphicsContext, const IntRect& /*dirtyRect*/) { #if ENABLE(LEGACY_FIND_INDICATOR_STYLE) for (size_t i = 0; i < m_textRectsInSelectionRectCoordinates.size(); ++i) { FloatRect textRect = m_textRectsInSelectionRectCoordinates[i]; textRect.move(leftBorderThickness, topBorderThickness); FloatRect outerPathRect = inflateRect(textRect, horizontalOutsetToCenterOfLightBorder, verticalOutsetToCenterOfLightBorder); FloatRect innerPathRect = inflateRect(textRect, horizontalPaddingInsideLightBorder, verticalPaddingInsideLightBorder); { GraphicsContextStateSaver stateSaver(graphicsContext); graphicsContext.setShadow(FloatSize(shadowOffsetX, shadowOffsetY), shadowBlurRadius, shadowColor(), ColorSpaceSRGB); graphicsContext.setFillColor(lightBorderColor(), ColorSpaceDeviceRGB); graphicsContext.fillPath(pathWithRoundedRect(outerPathRect, cornerRadius)); } { GraphicsContextStateSaver stateSaver(graphicsContext); graphicsContext.clip(pathWithRoundedRect(innerPathRect, cornerRadius)); RefPtr<Gradient> gradient = Gradient::create(FloatPoint(innerPathRect.x(), innerPathRect.y()), FloatPoint(innerPathRect.x(), innerPathRect.maxY())); gradient->addColorStop(0, gradientLightColor()); gradient->addColorStop(1, gradientDarkColor()); graphicsContext.setFillGradient(gradient.releaseNonNull()); graphicsContext.fillRect(outerPathRect); } { GraphicsContextStateSaver stateSaver(graphicsContext); graphicsContext.translate(FloatSize(roundf(leftBorderThickness), roundf(topBorderThickness))); IntRect contentImageRect = enclosingIntRect(m_textRectsInSelectionRectCoordinates[i]); m_contentImage->paint(graphicsContext, m_contentImageScaleFactor, contentImageRect.location(), contentImageRect); } } #else for (auto& textRect : m_textRectsInSelectionRectCoordinates) { FloatRect blurRect = textRect; blurRect.move(flatShadowBlurRadius + flatStyleHorizontalBorder, flatShadowBlurRadius + flatStyleVerticalBorder); FloatRect outerPathRect = inflateRect(blurRect, flatStyleHorizontalBorder, flatStyleVerticalBorder); { GraphicsContextStateSaver stateSaver(graphicsContext); graphicsContext.setShadow(FloatSize(), flatRimShadowBlurRadius, flatRimShadowColor(), ColorSpaceSRGB); graphicsContext.setFillColor(flatHighlightColor(), ColorSpaceSRGB); graphicsContext.fillRect(outerPathRect); graphicsContext.setShadow(FloatSize(flatShadowOffsetX, flatShadowOffsetY), flatShadowBlurRadius, flatDropShadowColor(), ColorSpaceSRGB); graphicsContext.fillRect(outerPathRect); } { GraphicsContextStateSaver stateSaver(graphicsContext); graphicsContext.translate(FloatSize(flatShadowBlurRadius + flatStyleHorizontalBorder, flatShadowBlurRadius + flatStyleVerticalBorder)); IntRect contentImageRect = enclosingIntRect(textRect); m_contentImage->paint(graphicsContext, m_contentImageScaleFactor, contentImageRect.location(), contentImageRect); } } #endif }
void WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer) { if (offset > m_end) offset = m_end; int currentCharacter = m_currentCharacter; if (currentCharacter >= offset) return; const UChar* cp = m_run.data(currentCharacter); bool rtl = m_run.rtl(); bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_expansion) && !m_run.spacingDisabled(); float widthSinceLastRounding = m_runWidthSoFar; m_runWidthSoFar = floorf(m_runWidthSoFar); widthSinceLastRounding -= m_runWidthSoFar; float lastRoundingWidth = m_finalRoundingWidth; FloatRect bounds; const SimpleFontData* primaryFont = m_font->primaryFont(); const SimpleFontData* lastFontData = primaryFont; while (currentCharacter < offset) { UChar32 c = *cp; unsigned clusterLength = 1; if (c >= 0x3041) { if (c <= 0x30FE) { // Deal with Hiragana and Katakana voiced and semi-voiced syllables. // Normalize into composed form, and then look for glyph with base + combined mark. // Check above for character range to minimize performance impact. UChar32 normalized = normalizeVoicingMarks(currentCharacter); if (normalized) { c = normalized; clusterLength = 2; } } else if (U16_IS_SURROGATE(c)) { if (!U16_IS_SURROGATE_LEAD(c)) break; // Do we have a surrogate pair? If so, determine the full Unicode (32 bit) // code point before glyph lookup. // Make sure we have another character and it's a low surrogate. if (currentCharacter + 1 >= m_run.length()) break; UChar low = cp[1]; if (!U16_IS_TRAIL(low)) break; c = U16_GET_SUPPLEMENTARY(c, low); clusterLength = 2; } } const GlyphData& glyphData = m_font->glyphDataForCharacter(c, rtl); Glyph glyph = glyphData.glyph; const SimpleFontData* fontData = glyphData.fontData; ASSERT(fontData); // Now that we have a glyph and font data, get its width. float width; if (c == '\t' && m_run.allowTabs()) { float tabWidth = m_font->tabWidth(*fontData); width = tabWidth - fmodf(m_run.xPos() + m_runWidthSoFar + widthSinceLastRounding, tabWidth); } else { width = fontData->widthForGlyph(glyph); #if ENABLE(SVG) // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text. width *= m_run.horizontalGlyphStretch(); #endif // We special case spaces in two ways when applying word rounding. // First, we round spaces to an adjusted width in all fonts. // Second, in fixed-pitch fonts we ensure that all characters that // match the width of the space character have the same width as the space character. if (m_run.applyWordRounding() && width == fontData->spaceWidth() && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph())) width = fontData->adjustedSpaceWidth(); } if (fontData != lastFontData && width) { lastFontData = fontData; if (m_fallbackFonts && fontData != primaryFont) { // FIXME: This does a little extra work that could be avoided if // glyphDataForCharacter() returned whether it chose to use a small caps font. if (!m_font->isSmallCaps() || c == toUpper(c)) m_fallbackFonts->add(fontData); else { const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(c), rtl); if (uppercaseGlyphData.fontData != primaryFont) m_fallbackFonts->add(uppercaseGlyphData.fontData); } } } if (hasExtraSpacing) { // Account for letter-spacing. if (width && m_font->letterSpacing()) width += m_font->letterSpacing(); static bool expandAroundIdeographs = Font::canExpandAroundIdeographsInComplexText(); bool treatAsSpace = Font::treatAsSpace(c); if (treatAsSpace || (expandAroundIdeographs && Font::isCJKIdeographOrSymbol(c))) { // Distribute the run's total expansion evenly over all expansion opportunities in the run. if (m_expansion) { float previousExpansion = m_expansion; if (!treatAsSpace && !m_isAfterExpansion) { // Take the expansion opportunity before this ideograph. m_expansion -= m_expansionPerOpportunity; float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion); m_runWidthSoFar += expansionAtThisOpportunity; if (glyphBuffer) { if (glyphBuffer->isEmpty()) glyphBuffer->add(fontData->spaceGlyph(), fontData, expansionAtThisOpportunity); else glyphBuffer->expandLastAdvance(expansionAtThisOpportunity); } previousExpansion = m_expansion; } if (m_run.allowsTrailingExpansion() || (m_run.ltr() && currentCharacter + clusterLength < static_cast<size_t>(m_run.length())) || (m_run.rtl() && currentCharacter)) { m_expansion -= m_expansionPerOpportunity; width += !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion); m_isAfterExpansion = true; } } else m_isAfterExpansion = false; // Account for word spacing. // We apply additional space between "words" by adding width to the space character. if (treatAsSpace && currentCharacter && !Font::treatAsSpace(cp[-1]) && m_font->wordSpacing()) width += m_font->wordSpacing(); } else m_isAfterExpansion = false; } if (m_accountForGlyphBounds) { bounds = fontData->boundsForGlyph(glyph); if (!currentCharacter) m_firstGlyphOverflow = max<float>(0, -bounds.x()); } if (m_forTextEmphasis && !Font::canReceiveTextEmphasis(c)) glyph = 0; // Advance past the character we just dealt with. cp += clusterLength; currentCharacter += clusterLength; float oldWidth = width; // Force characters that are used to determine word boundaries for the rounding hack // to be integer width, so following words will start on an integer boundary. if (m_run.applyWordRounding() && Font::isRoundingHackCharacter(c)) { width = ceilf(width); // Since widthSinceLastRounding can lose precision if we include measurements for // preceding whitespace, we bypass it here. m_runWidthSoFar += width; // Since this is a rounding hack character, we should have reset this sum on the previous // iteration. ASSERT(!widthSinceLastRounding); } else { // Check to see if the next character is a "rounding hack character", if so, adjust // width so that the total run width will be on an integer boundary. if ((m_run.applyWordRounding() && currentCharacter < m_run.length() && Font::isRoundingHackCharacter(*cp)) || (m_run.applyRunRounding() && currentCharacter >= m_end)) { float totalWidth = widthSinceLastRounding + width; widthSinceLastRounding = ceilf(totalWidth); width += widthSinceLastRounding - totalWidth; m_runWidthSoFar += widthSinceLastRounding; widthSinceLastRounding = 0; } else widthSinceLastRounding += width; } if (glyphBuffer) glyphBuffer->add(glyph, fontData, (rtl ? oldWidth + lastRoundingWidth : width)); lastRoundingWidth = width - oldWidth; if (m_accountForGlyphBounds) { m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, bounds.maxY()); m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, bounds.y()); m_lastGlyphOverflow = max<float>(0, bounds.maxX() - width); } } m_currentCharacter = currentCharacter; m_runWidthSoFar += widthSinceLastRounding; m_finalRoundingWidth = lastRoundingWidth; }
void SharedBitmap::drawPattern(HDC hdc, const AffineTransform& transform, const FloatRect& tileRectIn, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect, const IntSize& origSourceSize) { if (!m_pixels) return; if (tileRectIn.width() <= 0 || tileRectIn.height() <= 0) return; bool useAlpha = op == CompositeSourceOver && hasAlpha() && is32bit(); int bmpWidth = width(); int bmpHeight = height(); FloatRect tileRect(tileRectIn); if (bmpWidth != origSourceSize.width()) { double rate = static_cast<double>(bmpWidth) / origSourceSize.width(); double temp = tileRect.width() * rate; tileRect.setX(tileRect.x() * rate); tileRect.setWidth(temp); temp = tileRect.height() * rate; tileRect.setY(tileRect.y() * rate); tileRect.setHeight(temp); } OwnPtr<HBITMAP> clippedBmp; if (tileRect.x() || tileRect.y() || tileRect.width() != bmpWidth || tileRect.height() != bmpHeight) { BitmapInfo patternBmpInfo; void* patternPixels; clippedBmp = clipBitmap(IntRect(tileRect), useAlpha, patternBmpInfo, patternPixels); if (!clippedBmp) return; bmpWidth = tileRect.width(); bmpHeight = tileRect.height(); } AffineTransform tf = patternTransform * transform; FloatRect trRect = tf.mapRect(destRect); RECT clipBox; int clipType = GetClipBox(hdc, &clipBox); if (clipType == SIMPLEREGION) trRect.intersect(FloatRect(clipBox.left, clipBox.top, clipBox.right - clipBox.left, clipBox.bottom - clipBox.top)); else if (clipType == COMPLEXREGION) { OwnPtr<HRGN> clipRgn = adoptPtr(CreateRectRgn(0, 0, 0, 0)); if (GetClipRgn(hdc, clipRgn.get()) > 0) { DWORD regionDataSize = GetRegionData(clipRgn.get(), sizeof(RGNDATA), 0); if (regionDataSize) { Vector<RGNDATA> regionData(regionDataSize); GetRegionData(clipRgn.get(), regionDataSize, regionData.data()); RECT* rect = reinterpret_cast<RECT*>(regionData[0].Buffer); for (DWORD i = 0; i < regionData[0].rdh.nCount; ++i, ++rect) trRect.intersect(FloatRect(rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top)); } } } if (trRect.width() <= 0 || trRect.height() <= 0) return; trRect.inflate(1); IntRect visibleDstRect = enclosingIntRect(tf.inverse().mapRect(trRect)); visibleDstRect.intersect(IntRect(destRect)); if (visibleDstRect.width() <= 0 || visibleDstRect.height() <= 0) return; trRect = tf.mapRect(visibleDstRect); RECT dstRectWin = { stableRound(trRect.x()), stableRound(trRect.y()), stableRound(trRect.maxX()), stableRound(trRect.maxY()), }; if (dstRectWin.right <= dstRectWin.left || dstRectWin.bottom <= dstRectWin.top) return; SIZE bmpSize = { bmpWidth, bmpHeight }; // Relative to destination, in bitmap pixels POINT phaseWin = { stableRound(visibleDstRect.x() - phase.x()), stableRound(visibleDstRect.y() - phase.y()) }; phaseWin.x = normalizePhase(phaseWin.x, bmpSize.cx); phaseWin.y = normalizePhase(phaseWin.y, bmpSize.cy); RECT srcRectWin = { 0, 0, stableRound(visibleDstRect.maxX()) - stableRound(visibleDstRect.x()), stableRound(visibleDstRect.maxY()) - stableRound(visibleDstRect.y()) }; if (srcRectWin.right <= 0 || srcRectWin.bottom <= 0) return; BitmapInfo bmpInfo = BitmapInfo::createBottomUp(IntSize(srcRectWin.right, srcRectWin.bottom), useAlpha ? BitmapInfo::BitCount32 : BitmapInfo::BitCount16); void* pixels; OwnPtr<HBITMAP> hbmpTemp = adoptPtr(CreateDIBSection(0, &bmpInfo, DIB_RGB_COLORS, &pixels, 0, 0)); if (!hbmpTemp) return; OwnPtr<HDC> hmemdc = adoptPtr(CreateCompatibleDC(hdc)); HGDIOBJ oldBmp = SelectObject(hmemdc.get(), hbmpTemp.get()); if (clippedBmp) drawPatternSimple(hmemdc.get(), srcRectWin, clippedBmp.get(), phaseWin); else if ((op != CompositeSourceOver || canUseDIBits()) && srcRectWin.right <= bmpSize.cx * 2 && srcRectWin.bottom <= bmpSize.cy * 2) drawPatternSimple(hmemdc.get(), srcRectWin, this, bmpSize, phaseWin); else if (ensureHandle()) drawPatternSimple(hmemdc.get(), srcRectWin, getHandle(), phaseWin); else { void* pixels; BitmapInfo bmpInfo; OwnPtr<HBITMAP> hbmp = createHandle(&pixels, &bmpInfo, -1, false); if (hbmp) drawPatternSimple(hmemdc.get(), srcRectWin, hbmp.get(), phaseWin); else { SelectObject(hmemdc.get(), oldBmp); return; } } if (useAlpha && hasAlphaBlendSupport()) { static const BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; bool success = alphaBlendIfSupported(hdc, dstRectWin.left, dstRectWin.top, dstRectWin.right - dstRectWin.left, dstRectWin.bottom - dstRectWin.top, hmemdc.get(), 0, 0, srcRectWin.right, srcRectWin.bottom, blend); ASSERT_UNUSED(success, success); } else if (useAlpha && !hasAlphaBlendSupport() || op == CompositeSourceOver && usesTransparentColor()) { TransparentBlt(hdc, dstRectWin.left, dstRectWin.top, dstRectWin.right - dstRectWin.left, dstRectWin.bottom - dstRectWin.top, hmemdc.get(), 0, 0, srcRectWin.right, srcRectWin.bottom, transparentColor()); } else { DWORD bmpOp = op == CompositeCopy ? SRCCOPY : op == CompositeSourceOver ? SRCCOPY : op == CompositeXOR ? PATINVERT : op == CompositeClear ? WHITENESS : SRCCOPY; // FIXEME: other types? StretchDIBits(hdc, dstRectWin.left, dstRectWin.top, dstRectWin.right - dstRectWin.left, dstRectWin.bottom - dstRectWin.top, 0, 0, srcRectWin.right, srcRectWin.bottom, pixels, &bmpInfo, DIB_RGB_COLORS, bmpOp); } SelectObject(hmemdc.get(), oldBmp); }
bool RenderThemeWinCE::paintMediaSeekForwardButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r) { bool rc = paintButton(o, paintInfo, r); FloatRect imRect = r; imRect.inflate(-3); FloatPoint pts[3] = { FloatPoint(imRect.x(), imRect.y()), FloatPoint((imRect.x() + imRect.maxX()) / 2.0, (imRect.y() + imRect.maxY()) / 2.0), FloatPoint(imRect.x(), imRect.maxY()) }; FloatPoint pts2[3] = { FloatPoint((imRect.x() + imRect.maxX()) / 2.0, imRect.y()), FloatPoint(imRect.maxX(), (imRect.y() + imRect.maxY()) / 2.0), FloatPoint((imRect.x() + imRect.maxX()) / 2.0, imRect.maxY()) }; paintInfo.context->save(); paintInfo.context->setStrokeColor(Color::black); paintInfo.context->setFillColor(Color::black); paintInfo.context->drawConvexPolygon(3, pts); paintInfo.context->drawConvexPolygon(3, pts2); paintInfo.context->restore(); return rc; }
static inline bool overlapsYRange(const FloatRect& rect, float y1, float y2) { return !rect.isEmpty() && y2 >= y1 && y2 >= rect.y() && y1 <= rect.maxY(); }
void Image::drawTiled(GraphicsContext& ctxt, const FloatRect& destRect, const FloatPoint& srcPoint, const FloatSize& scaledTileSize, const FloatSize& spacing, CompositeOperator op, BlendMode blendMode) { if (mayFillWithSolidColor()) { fillWithSolidColor(ctxt, destRect, solidColor(), op); return; } ASSERT(!isBitmapImage() || notSolidColor()); #if PLATFORM(IOS) FloatSize intrinsicTileSize = originalSize(); #else FloatSize intrinsicTileSize = size(); #endif if (hasRelativeWidth()) intrinsicTileSize.setWidth(scaledTileSize.width()); if (hasRelativeHeight()) intrinsicTileSize.setHeight(scaledTileSize.height()); FloatSize scale(scaledTileSize.width() / intrinsicTileSize.width(), scaledTileSize.height() / intrinsicTileSize.height()); FloatRect oneTileRect; FloatSize actualTileSize(scaledTileSize.width() + spacing.width(), scaledTileSize.height() + spacing.height()); oneTileRect.setX(destRect.x() + fmodf(fmodf(-srcPoint.x(), actualTileSize.width()) - actualTileSize.width(), actualTileSize.width())); oneTileRect.setY(destRect.y() + fmodf(fmodf(-srcPoint.y(), actualTileSize.height()) - actualTileSize.height(), actualTileSize.height())); oneTileRect.setSize(scaledTileSize); // Check and see if a single draw of the image can cover the entire area we are supposed to tile. if (oneTileRect.contains(destRect) && !ctxt.drawLuminanceMask()) { FloatRect visibleSrcRect; visibleSrcRect.setX((destRect.x() - oneTileRect.x()) / scale.width()); visibleSrcRect.setY((destRect.y() - oneTileRect.y()) / scale.height()); visibleSrcRect.setWidth(destRect.width() / scale.width()); visibleSrcRect.setHeight(destRect.height() / scale.height()); draw(ctxt, destRect, visibleSrcRect, op, blendMode, ImageOrientationDescription()); return; } #if PLATFORM(IOS) // When using accelerated drawing on iOS, it's faster to stretch an image than to tile it. if (ctxt.isAcceleratedContext()) { if (size().width() == 1 && intersection(oneTileRect, destRect).height() == destRect.height()) { FloatRect visibleSrcRect; visibleSrcRect.setX(0); visibleSrcRect.setY((destRect.y() - oneTileRect.y()) / scale.height()); visibleSrcRect.setWidth(1); visibleSrcRect.setHeight(destRect.height() / scale.height()); draw(ctxt, destRect, visibleSrcRect, op, BlendModeNormal, ImageOrientationDescription()); return; } if (size().height() == 1 && intersection(oneTileRect, destRect).width() == destRect.width()) { FloatRect visibleSrcRect; visibleSrcRect.setX((destRect.x() - oneTileRect.x()) / scale.width()); visibleSrcRect.setY(0); visibleSrcRect.setWidth(destRect.width() / scale.width()); visibleSrcRect.setHeight(1); draw(ctxt, destRect, visibleSrcRect, op, BlendModeNormal, ImageOrientationDescription()); return; } } #endif // Patterned images and gradients can use lots of memory for caching when the // tile size is large (<rdar://problem/4691859>, <rdar://problem/6239505>). // Memory consumption depends on the transformed tile size which can get // larger than the original tile if user zooms in enough. #if PLATFORM(IOS) const float maxPatternTilePixels = 512 * 512; #else const float maxPatternTilePixels = 2048 * 2048; #endif FloatRect transformedTileSize = ctxt.getCTM().mapRect(FloatRect(FloatPoint(), scaledTileSize)); float transformedTileSizePixels = transformedTileSize.width() * transformedTileSize.height(); FloatRect currentTileRect = oneTileRect; if (transformedTileSizePixels > maxPatternTilePixels) { GraphicsContextStateSaver stateSaver(ctxt); ctxt.clip(destRect); currentTileRect.shiftYEdgeTo(destRect.y()); float toY = currentTileRect.y(); while (toY < destRect.maxY()) { currentTileRect.shiftXEdgeTo(destRect.x()); float toX = currentTileRect.x(); while (toX < destRect.maxX()) { FloatRect toRect(toX, toY, currentTileRect.width(), currentTileRect.height()); FloatRect fromRect(toFloatPoint(currentTileRect.location() - oneTileRect.location()), currentTileRect.size()); fromRect.scale(1 / scale.width(), 1 / scale.height()); draw(ctxt, toRect, fromRect, op, BlendModeNormal, ImageOrientationDescription()); toX += currentTileRect.width(); currentTileRect.shiftXEdgeTo(oneTileRect.x()); } toY += currentTileRect.height(); currentTileRect.shiftYEdgeTo(oneTileRect.y()); } return; } AffineTransform patternTransform = AffineTransform().scaleNonUniform(scale.width(), scale.height()); FloatRect tileRect(FloatPoint(), intrinsicTileSize); drawPattern(ctxt, tileRect, patternTransform, oneTileRect.location(), spacing, op, destRect, blendMode); #if PLATFORM(IOS) startAnimation(DoNotCatchUp); #else startAnimation(); #endif }
void TileController::adjustTileCoverageRect(FloatRect& coverageRect, const FloatSize& newSize, const FloatRect& previousVisibleRect, const FloatRect& visibleRect, float contentsScale) const { // If the page is not in a window (for example if it's in a background tab), we limit the tile coverage rect to the visible rect. if (!m_isInWindow) { coverageRect = visibleRect; return; } #if PLATFORM(IOS) // FIXME: unify the iOS and Mac code. UNUSED_PARAM(previousVisibleRect); if (m_tileCoverage == CoverageForVisibleArea || MemoryPressureHandler::singleton().isUnderMemoryPressure()) { coverageRect = visibleRect; return; } double horizontalMargin = kDefaultTileSize / contentsScale; double verticalMargin = kDefaultTileSize / contentsScale; double currentTime = monotonicallyIncreasingTime(); double timeDelta = currentTime - m_velocity.lastUpdateTime; FloatRect futureRect = visibleRect; futureRect.setLocation(FloatPoint( futureRect.location().x() + timeDelta * m_velocity.horizontalVelocity, futureRect.location().y() + timeDelta * m_velocity.verticalVelocity)); if (m_velocity.horizontalVelocity) { futureRect.setWidth(futureRect.width() + horizontalMargin); if (m_velocity.horizontalVelocity < 0) futureRect.setX(futureRect.x() - horizontalMargin); } if (m_velocity.verticalVelocity) { futureRect.setHeight(futureRect.height() + verticalMargin); if (m_velocity.verticalVelocity < 0) futureRect.setY(futureRect.y() - verticalMargin); } if (!m_velocity.horizontalVelocity && !m_velocity.verticalVelocity) { if (m_velocity.scaleChangeRate > 0) { coverageRect = visibleRect; return; } futureRect.setWidth(futureRect.width() + horizontalMargin); futureRect.setHeight(futureRect.height() + verticalMargin); futureRect.setX(futureRect.x() - horizontalMargin / 2); futureRect.setY(futureRect.y() - verticalMargin / 2); } // Can't use m_tileCacheLayer->bounds() here, because the size of the underlying platform layer // hasn't been updated for the current commit. IntSize contentSize = expandedIntSize(newSize); if (futureRect.maxX() > contentSize.width()) futureRect.setX(contentSize.width() - futureRect.width()); if (futureRect.maxY() > contentSize.height()) futureRect.setY(contentSize.height() - futureRect.height()); if (futureRect.x() < 0) futureRect.setX(0); if (futureRect.y() < 0) futureRect.setY(0); coverageRect.unite(futureRect); return; #else UNUSED_PARAM(contentsScale); // FIXME: look at how far the document can scroll in each dimension. FloatSize coverageSize = visibleRect.size(); bool largeVisibleRectChange = !previousVisibleRect.isEmpty() && !visibleRect.intersects(previousVisibleRect); // Inflate the coverage rect so that it covers 2x of the visible width and 3x of the visible height. // These values were chosen because it's more common to have tall pages and to scroll vertically, // so we keep more tiles above and below the current area. float widthScale = 1; float heightScale = 1; if (m_tileCoverage & CoverageForHorizontalScrolling && !largeVisibleRectChange) widthScale = 2; if (m_tileCoverage & CoverageForVerticalScrolling && !largeVisibleRectChange) heightScale = 3; coverageSize.scale(widthScale, heightScale); FloatRect coverageBounds = boundsForSize(newSize); FloatRect coverage = expandRectWithinRect(visibleRect, coverageSize, coverageBounds); LOG_WITH_STREAM(Scrolling, stream << "TileController::computeTileCoverageRect newSize=" << newSize << " mode " << m_tileCoverage << " expanded to " << coverageSize << " bounds with margin " << coverageBounds << " coverage " << coverage); coverageRect.unite(coverage); #endif }
bool ExclusionPolygon::firstIncludedIntervalLogicalTop(LayoutUnit minLogicalIntervalTop, const LayoutSize& minLogicalIntervalSize, LayoutUnit& result) const { float minIntervalTop = minLogicalIntervalTop; float minIntervalHeight = minLogicalIntervalSize.height(); float minIntervalWidth = minLogicalIntervalSize.width(); const FloatPolygon& polygon = shapePaddingBounds(); const FloatRect boundingBox = polygon.boundingBox(); if (minIntervalWidth > boundingBox.width()) return false; float minY = std::max(boundingBox.y(), minIntervalTop); float maxY = minY + minIntervalHeight; if (maxY > boundingBox.maxY()) return false; Vector<const FloatPolygonEdge*> edges; polygon.overlappingEdges(minIntervalTop, boundingBox.maxY(), edges); float dx = minIntervalWidth / 2; float dy = minIntervalHeight / 2; Vector<OffsetPolygonEdge> offsetEdges; for (unsigned i = 0; i < edges.size(); ++i) { const FloatPolygonEdge& edge = *(edges[i]); const FloatPoint& vertex0 = edge.previousEdge().vertex1(); const FloatPoint& vertex1 = edge.vertex1(); const FloatPoint& vertex2 = edge.vertex2(); Vector<OffsetPolygonEdge> offsetEdgeBuffer; if (vertex2.y() > vertex1.y() ? vertex2.x() >= vertex1.x() : vertex1.x() >= vertex2.x()) { offsetEdgeBuffer.append(OffsetPolygonEdge(edge, FloatSize(dx, -dy))); offsetEdgeBuffer.append(OffsetPolygonEdge(edge, FloatSize(-dx, dy))); } else { offsetEdgeBuffer.append(OffsetPolygonEdge(edge, FloatSize(dx, dy))); offsetEdgeBuffer.append(OffsetPolygonEdge(edge, FloatSize(-dx, -dy))); } if (isReflexVertex(vertex0, vertex1, vertex2)) { if (vertex2.x() <= vertex1.x() && vertex0.x() <= vertex1.x()) offsetEdgeBuffer.append(OffsetPolygonEdge(vertex1, FloatSize(dx, -dy), FloatSize(dx, dy))); else if (vertex2.x() >= vertex1.x() && vertex0.x() >= vertex1.x()) offsetEdgeBuffer.append(OffsetPolygonEdge(vertex1, FloatSize(-dx, -dy), FloatSize(-dx, dy))); if (vertex2.y() <= vertex1.y() && vertex0.y() <= vertex1.y()) offsetEdgeBuffer.append(OffsetPolygonEdge(vertex1, FloatSize(-dx, dy), FloatSize(dx, dy))); else if (vertex2.y() >= vertex1.y() && vertex0.y() >= vertex1.y()) offsetEdgeBuffer.append(OffsetPolygonEdge(vertex1, FloatSize(-dx, -dy), FloatSize(dx, -dy))); } for (unsigned j = 0; j < offsetEdgeBuffer.size(); ++j) if (offsetEdgeBuffer[j].maxY() >= minY) offsetEdges.append(offsetEdgeBuffer[j]); } offsetEdges.append(OffsetPolygonEdge(polygon, minIntervalTop, FloatSize(0, dy))); FloatPoint offsetEdgesIntersection; FloatRect firstFitRect; bool firstFitFound = false; for (unsigned i = 0; i < offsetEdges.size() - 1; ++i) { for (unsigned j = i + 1; j < offsetEdges.size(); ++j) { if (offsetEdges[i].intersection(offsetEdges[j], offsetEdgesIntersection)) { FloatPoint potentialFirstFitLocation(offsetEdgesIntersection.x() - dx, offsetEdgesIntersection.y() - dy); FloatRect potentialFirstFitRect(potentialFirstFitLocation, minLogicalIntervalSize); if ((offsetEdges[i].basis() == OffsetPolygonEdge::LineTop || offsetEdges[j].basis() == OffsetPolygonEdge::LineTop || potentialFirstFitLocation.y() >= minIntervalTop) && (!firstFitFound || aboveOrToTheLeft(potentialFirstFitRect, firstFitRect)) && polygon.contains(offsetEdgesIntersection) && firstFitRectInPolygon(polygon, potentialFirstFitRect, offsetEdges[i].edgeIndex(), offsetEdges[j].edgeIndex())) { firstFitFound = true; firstFitRect = potentialFirstFitRect; } } } } if (firstFitFound) result = LayoutUnit::fromFloatCeil(firstFitRect.y()); return firstFitFound; }
bool UniscribeController::shapeAndPlaceItem(const UChar* cp, unsigned i, const SimpleFontData* fontData, GlyphBuffer* glyphBuffer) { // Determine the string for this item. const UChar* str = cp + m_items[i].iCharPos; int len = m_items[i+1].iCharPos - m_items[i].iCharPos; SCRIPT_ITEM item = m_items[i]; // Set up buffers to hold the results of shaping the item. Vector<WORD> glyphs; Vector<WORD> clusters; Vector<SCRIPT_VISATTR> visualAttributes; clusters.resize(len); // Shape the item. // The recommended size for the glyph buffer is 1.5 * the character length + 16 in the uniscribe docs. // Apparently this is a good size to avoid having to make repeated calls to ScriptShape. glyphs.resize(1.5 * len + 16); visualAttributes.resize(glyphs.size()); if (!shape(str, len, item, fontData, glyphs, clusters, visualAttributes)) return true; // We now have a collection of glyphs. Vector<GOFFSET> offsets; Vector<int> advances; offsets.resize(glyphs.size()); advances.resize(glyphs.size()); int glyphCount = 0; HRESULT placeResult = ScriptPlace(0, fontData->scriptCache(), glyphs.data(), glyphs.size(), visualAttributes.data(), &item.a, advances.data(), offsets.data(), 0); if (placeResult == E_PENDING) { // The script cache isn't primed with enough info yet. We need to select our HFONT into // a DC and pass the DC in to ScriptPlace. HDC hdc = GetDC(0); HFONT hfont = fontData->platformData().hfont(); HFONT oldFont = (HFONT)SelectObject(hdc, hfont); placeResult = ScriptPlace(hdc, fontData->scriptCache(), glyphs.data(), glyphs.size(), visualAttributes.data(), &item.a, advances.data(), offsets.data(), 0); SelectObject(hdc, oldFont); ReleaseDC(0, hdc); } if (FAILED(placeResult) || glyphs.isEmpty()) return true; // Convert all chars that should be treated as spaces to use the space glyph. // We also create a map that allows us to quickly go from space glyphs back to their corresponding characters. Vector<int> spaceCharacters(glyphs.size()); spaceCharacters.fill(-1); const float cLogicalScale = fontData->platformData().useGDI() ? 1.0f : 32.0f; unsigned logicalSpaceWidth = fontData->spaceWidth() * cLogicalScale; float spaceWidth = fontData->spaceWidth(); for (int k = 0; k < len; k++) { UChar ch = *(str + k); bool treatAsSpace = Font::treatAsSpace(ch); bool treatAsZeroWidthSpace = ch == zeroWidthSpace || Font::treatAsZeroWidthSpace(ch); if (treatAsSpace || treatAsZeroWidthSpace) { // Substitute in the space glyph at the appropriate place in the glyphs // array. glyphs[clusters[k]] = fontData->spaceGlyph(); advances[clusters[k]] = treatAsSpace ? logicalSpaceWidth : 0; if (treatAsSpace) spaceCharacters[clusters[k]] = m_currentCharacter + k + item.iCharPos; } } // Populate our glyph buffer with this information. bool hasExtraSpacing = m_font.letterSpacing() || m_font.wordSpacing() || m_padding; float leftEdge = m_runWidthSoFar; for (unsigned k = 0; k < glyphs.size(); k++) { Glyph glyph = glyphs[k]; float advance = advances[k] / cLogicalScale; float offsetX = offsets[k].du / cLogicalScale; float offsetY = offsets[k].dv / cLogicalScale; // Match AppKit's rules for the integer vs. non-integer rendering modes. float roundedAdvance = roundf(advance); if (!m_font.isPrinterFont() && !fontData->isSystemFont()) { advance = roundedAdvance; offsetX = roundf(offsetX); offsetY = roundf(offsetY); } advance += fontData->syntheticBoldOffset(); if (hasExtraSpacing) { // If we're a glyph with an advance, go ahead and add in letter-spacing. // That way we weed out zero width lurkers. This behavior matches the fast text code path. if (advance && m_font.letterSpacing()) advance += m_font.letterSpacing(); // Handle justification and word-spacing. int characterIndex = spaceCharacters[k]; // characterIndex is left at the initial value of -1 for glyphs that do not map back to treated-as-space characters. if (characterIndex != -1) { // Account for padding. WebCore uses space padding to justify text. // We distribute the specified padding over the available spaces in the run. if (m_padding) { // Use leftover padding if not evenly divisible by number of spaces. if (m_padding < m_padPerSpace) { advance += m_padding; m_padding = 0; } else { m_padding -= m_padPerSpace; advance += m_padPerSpace; } } // Account for word-spacing. if (characterIndex > 0 && !Font::treatAsSpace(*m_run.data(characterIndex - 1)) && m_font.wordSpacing()) advance += m_font.wordSpacing(); } } m_runWidthSoFar += advance; // FIXME: We need to take the GOFFSETS for combining glyphs and store them in the glyph buffer // as well, so that when the time comes to draw those glyphs, we can apply the appropriate // translation. if (glyphBuffer) { FloatSize size(offsetX, -offsetY); glyphBuffer->add(glyph, fontData, advance, &size); } FloatRect glyphBounds = fontData->boundsForGlyph(glyph); glyphBounds.move(m_glyphOrigin.x(), m_glyphOrigin.y()); m_minGlyphBoundingBoxX = min(m_minGlyphBoundingBoxX, glyphBounds.x()); m_maxGlyphBoundingBoxX = max(m_maxGlyphBoundingBoxX, glyphBounds.maxX()); m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, glyphBounds.y()); m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, glyphBounds.maxY()); m_glyphOrigin.move(advance + offsetX, -offsetY); // Mutate the glyph array to contain our altered advances. if (m_computingOffsetPosition) advances[k] = advance; } while (m_computingOffsetPosition && m_offsetX >= leftEdge && m_offsetX < m_runWidthSoFar) { // The position is somewhere inside this run. int trailing = 0; ScriptXtoCP(m_offsetX - leftEdge, clusters.size(), glyphs.size(), clusters.data(), visualAttributes.data(), advances.data(), &item.a, &m_offsetPosition, &trailing); if (trailing && m_includePartialGlyphs && m_offsetPosition < len - 1) { m_offsetPosition += m_currentCharacter + m_items[i].iCharPos; m_offsetX += m_run.rtl() ? -trailing : trailing; } else { m_computingOffsetPosition = false; m_offsetPosition += m_currentCharacter + m_items[i].iCharPos; if (trailing && m_includePartialGlyphs) m_offsetPosition++; return false; } } return true; }
void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& destRect, const FloatRect& srcRect, ColorSpace styleColorSpace, CompositeOperator compositeOp) { startAnimation(); RetainPtr<CGImageRef> image = frameAtIndex(m_currentFrame); if (!image) // If it's too early we won't have an image yet. return; if (mayFillWithSolidColor()) { fillWithSolidColor(ctxt, destRect, solidColor(), styleColorSpace, compositeOp); return; } float currHeight = CGImageGetHeight(image.get()); if (currHeight <= srcRect.y()) return; CGContextRef context = ctxt->platformContext(); GraphicsContextStateSaver stateSaver(*ctxt); bool shouldUseSubimage = false; // If the source rect is a subportion of the image, then we compute an inflated destination rect that will hold the entire image // and then set a clip to the portion that we want to display. FloatRect adjustedDestRect = destRect; FloatSize selfSize = currentFrameSize(); if (srcRect.size() != selfSize) { CGInterpolationQuality interpolationQuality = CGContextGetInterpolationQuality(context); // When the image is scaled using high-quality interpolation, we create a temporary CGImage // containing only the portion we want to display. We need to do this because high-quality // interpolation smoothes sharp edges, causing pixels from outside the source rect to bleed // into the destination rect. See <rdar://problem/6112909>. shouldUseSubimage = (interpolationQuality != kCGInterpolationNone) && (srcRect.size() != destRect.size() || !ctxt->getCTM().isIdentityOrTranslationOrFlipped()); float xScale = srcRect.width() / destRect.width(); float yScale = srcRect.height() / destRect.height(); if (shouldUseSubimage) { FloatRect subimageRect = srcRect; float leftPadding = srcRect.x() - floorf(srcRect.x()); float topPadding = srcRect.y() - floorf(srcRect.y()); subimageRect.move(-leftPadding, -topPadding); adjustedDestRect.move(-leftPadding / xScale, -topPadding / yScale); subimageRect.setWidth(ceilf(subimageRect.width() + leftPadding)); adjustedDestRect.setWidth(subimageRect.width() / xScale); subimageRect.setHeight(ceilf(subimageRect.height() + topPadding)); adjustedDestRect.setHeight(subimageRect.height() / yScale); image.adoptCF(CGImageCreateWithImageInRect(image.get(), subimageRect)); if (currHeight < srcRect.maxY()) { ASSERT(CGImageGetHeight(image.get()) == currHeight - CGRectIntegral(srcRect).origin.y); adjustedDestRect.setHeight(CGImageGetHeight(image.get()) / yScale); } } else { adjustedDestRect.setLocation(FloatPoint(destRect.x() - srcRect.x() / xScale, destRect.y() - srcRect.y() / yScale)); adjustedDestRect.setSize(FloatSize(selfSize.width() / xScale, selfSize.height() / yScale)); } if (!destRect.contains(adjustedDestRect)) CGContextClipToRect(context, destRect); } // If the image is only partially loaded, then shrink the destination rect that we're drawing into accordingly. if (!shouldUseSubimage && currHeight < selfSize.height()) adjustedDestRect.setHeight(adjustedDestRect.height() * currHeight / selfSize.height()); ctxt->setCompositeOperation(compositeOp); // Flip the coords. CGContextScaleCTM(context, 1, -1); adjustedDestRect.setY(-adjustedDestRect.maxY()); // Adjust the color space. image = imageWithColorSpace(image.get(), styleColorSpace); // Draw the image. CGContextDrawImage(context, adjustedDestRect, image.get()); stateSaver.restore(); if (imageObserver()) imageObserver()->didDraw(this); }