void GraphicsContext::drawBidiText(const Font& font, const TextRun& run, const FloatPoint& point) { if (paintingDisabled()) return; BidiResolver<TextRunIterator, BidiCharacterRun> bidiResolver; bidiResolver.setStatus(BidiStatus(run.direction(), run.directionalOverride())); bidiResolver.setPosition(TextRunIterator(&run, 0)); // FIXME: This ownership should be reversed. We should pass BidiRunList // to BidiResolver in createBidiRunsForLine. BidiRunList<BidiCharacterRun>& bidiRuns = bidiResolver.runs(); bidiResolver.createBidiRunsForLine(TextRunIterator(&run, run.length())); if (!bidiRuns.runCount()) return; FloatPoint currPoint = point; BidiCharacterRun* bidiRun = bidiRuns.firstRun(); while (bidiRun) { TextRun subrun = run; subrun.setText(run.data(bidiRun->start()), bidiRun->stop() - bidiRun->start()); bool isRTL = bidiRun->level() % 2; subrun.setDirection(isRTL ? RTL : LTR); subrun.setDirectionalOverride(bidiRun->dirOverride(false)); font.drawText(this, subrun, currPoint); bidiRun = bidiRun->next(); // FIXME: Have Font::drawText return the width of what it drew so that we don't have to re-measure here. if (bidiRun) currPoint.move(font.width(subrun), 0); } bidiRuns.deleteRuns(); }
TextDirection directionForRun(TextRun& run, bool& hasStrongDirectionality) { BidiResolver<TextRunIterator, BidiCharacterRun> bidiResolver; bidiResolver.setStatus(BidiStatus(run.direction(), run.directionalOverride())); bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&run, 0)); return bidiResolver.determineParagraphDirectionality(&hasStrongDirectionality); }
PassRefPtr<ShapeResult> ShapeResult::createForTabulationCharacters(const Font* font, const TextRun& textRun, float positionOffset, unsigned count) { const SimpleFontData* fontData = font->primaryFont(); OwnPtr<ShapeResult::RunInfo> run = adoptPtr(new ShapeResult::RunInfo(fontData, // Tab characters are always LTR or RTL, not TTB, even when isVerticalAnyUpright(). textRun.rtl() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR, HB_SCRIPT_COMMON, 0, count, count)); float position = textRun.xPos() + positionOffset; float startPosition = position; for (unsigned i = 0; i < count; i++) { float advance = font->tabWidth(*fontData, textRun.getTabSize(), position); run->m_glyphData[i].characterIndex = i; run->setGlyphAndPositions(i, fontData->spaceGlyph(), advance, 0, 0); position += advance; } run->m_width = position - startPosition; RefPtr<ShapeResult> result = ShapeResult::create(font, count, textRun.direction()); result->m_width = run->m_width; result->m_numGlyphs = count; ASSERT(result->m_numGlyphs == count); // no overflow result->m_hasVerticalOffsets = fontData->platformData().isVerticalAnyUpright(); result->m_runs.append(run.release()); return result.release(); }
void GraphicsContext::drawBidiText(const FontCascade& font, const TextRun& run, const FloatPoint& point, FontCascade::CustomFontNotReadyAction customFontNotReadyAction) { if (paintingDisabled()) return; BidiResolver<TextRunIterator, BidiCharacterRun> bidiResolver; bidiResolver.setStatus(BidiStatus(run.direction(), run.directionalOverride())); bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&run, 0)); // FIXME: This ownership should be reversed. We should pass BidiRunList // to BidiResolver in createBidiRunsForLine. BidiRunList<BidiCharacterRun>& bidiRuns = bidiResolver.runs(); bidiResolver.createBidiRunsForLine(TextRunIterator(&run, run.length())); if (!bidiRuns.runCount()) return; FloatPoint currPoint = point; BidiCharacterRun* bidiRun = bidiRuns.firstRun(); while (bidiRun) { TextRun subrun = run.subRun(bidiRun->start(), bidiRun->stop() - bidiRun->start()); bool isRTL = bidiRun->level() % 2; subrun.setDirection(isRTL ? RTL : LTR); subrun.setDirectionalOverride(bidiRun->dirOverride(false)); float width = font.drawText(*this, subrun, currPoint, 0, -1, customFontNotReadyAction); currPoint.move(width, 0); bidiRun = bidiRun->next(); } bidiRuns.deleteRuns(); }
CharacterRange CachingWordShaper::getCharacterRange(const Font* font, const TextRun& run, unsigned from, unsigned to) { ShapeResultBuffer buffer; float totalWidth = shapeResultsForRun(m_shapeCache, font, run, nullptr, &buffer); return buffer.getCharacterRange(run.direction(), totalWidth, from, to); }
FloatRect CachingWordShaper::selectionRect(const Font* font, const TextRun& run, const FloatPoint& point, int height, unsigned from, unsigned to) { Vector<RefPtr<ShapeResult>> results; float totalWidth = shapeResultsForRun(m_shapeCache, font, run, nullptr, &results); return ShapeResult::selectionRect(results, run.direction(), totalWidth, point, height, from, to); }
TextRunComponent::TextRunComponent(const UChar *start, int length, const TextRun& parentTextRun, const Font &font, int o) : m_textRun(start, length, 0, 0 , parentTextRun.allowsTrailingExpansion() ? TextRun::AllowTrailingExpansion : TextRun::ForbidTrailingExpansion , parentTextRun.direction() , parentTextRun.directionalOverride()) , m_offset(o) , m_spaces(0) { m_textRun.setTabSize(parentTextRun.allowTabs(), parentTextRun.tabSize()); WidthIterator it(&font, m_textRun); it.advance(m_textRun.length(), 0); m_width = it.m_runWidthSoFar; }
FloatRect CachingWordShaper::selectionRect(const Font* font, const TextRun& run, const FloatPoint& point, int height, unsigned from, unsigned to) { #ifdef MINIBLINK_NOT_IMPLEMENTED Vector<RefPtr<ShapeResult>> results; float totalWidth = shapeResultsForRun(m_shapeCache, font, run, nullptr, &results); return ShapeResult::selectionRect(results, run.direction(), totalWidth, point, height, from, to); #endif // MINIBLINK_NOT_IMPLEMENTED notImplemented(); return FloatRect(); }
Vector<CharacterRange> CachingWordShaper::individualCharacterRanges( const Font* font, const TextRun& run) { ShapeResultBuffer buffer; float totalWidth = shapeResultsForRun(m_shapeCache, font, run, nullptr, &buffer); auto ranges = buffer.individualCharacterRanges(run.direction(), totalWidth); // The shaper can fail to return glyph metrics for all characters (see // crbug.com/613915 and crbug.com/615661) so add empty ranges to ensure all // characters have an associated range. while (ranges.size() < static_cast<unsigned>(run.length())) ranges.append(CharacterRange(0, 0)); return ranges; }
TextDirection directionForRun(TextRun& run, bool* hasStrongDirectionality) { if (!hasStrongDirectionality) { // 8bit is Latin-1 and therefore is always LTR. if (run.is8Bit()) return LTR; // length == 1 for more than 90% of cases of width() for CJK text. if (run.length() == 1 && U16_IS_SINGLE(run.characters16()[0])) return directionForCharacter(run.characters16()[0]); } BidiResolver<TextRunIterator, BidiCharacterRun> bidiResolver; bidiResolver.setStatus(BidiStatus(run.direction(), run.directionalOverride())); bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&run, 0)); return bidiResolver.determineParagraphDirectionality(hasStrongDirectionality); }
void ImagePainter::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { LayoutUnit cWidth = m_renderImage.contentWidth(); LayoutUnit cHeight = m_renderImage.contentHeight(); GraphicsContext* context = paintInfo.context; if (!m_renderImage.imageResource()->hasImage() || m_renderImage.imageResource()->errorOccurred()) { if (paintInfo.phase == PaintPhaseSelection) return; if (cWidth > 2 && cHeight > 2) { const int borderWidth = 1; LayoutUnit leftBorder = m_renderImage.borderLeft(); LayoutUnit topBorder = m_renderImage.borderTop(); LayoutUnit leftPad = m_renderImage.paddingLeft(); LayoutUnit topPad = m_renderImage.paddingTop(); // Draw an outline rect where the image should be. IntRect paintRect = pixelSnappedIntRect(LayoutRect(paintOffset.x() + leftBorder + leftPad, paintOffset.y() + topBorder + topPad, cWidth, cHeight)); DrawingRecorder recorder(context, &m_renderImage, paintInfo.phase, paintRect); context->setStrokeStyle(SolidStroke); context->setStrokeColor(Color::lightGray); context->setFillColor(Color::transparent); context->drawRect(paintRect); bool errorPictureDrawn = false; LayoutSize imageOffset; // When calculating the usable dimensions, exclude the pixels of // the ouline rect so the error image/alt text doesn't draw on it. LayoutUnit usableWidth = cWidth - 2 * borderWidth; LayoutUnit usableHeight = cHeight - 2 * borderWidth; RefPtr<Image> image = m_renderImage.imageResource()->image(); if (m_renderImage.imageResource()->errorOccurred() && !image->isNull() && usableWidth >= image->width() && usableHeight >= image->height()) { float deviceScaleFactor = blink::deviceScaleFactor(m_renderImage.frame()); // Call brokenImage() explicitly to ensure we get the broken image icon at the appropriate resolution. pair<Image*, float> brokenImageAndImageScaleFactor = ImageResource::brokenImage(deviceScaleFactor); image = brokenImageAndImageScaleFactor.first; IntSize imageSize = image->size(); imageSize.scale(1 / brokenImageAndImageScaleFactor.second); // Center the error image, accounting for border and padding. LayoutUnit centerX = (usableWidth - imageSize.width()) / 2; if (centerX < 0) centerX = 0; LayoutUnit centerY = (usableHeight - imageSize.height()) / 2; if (centerY < 0) centerY = 0; imageOffset = LayoutSize(leftBorder + leftPad + centerX + borderWidth, topBorder + topPad + centerY + borderWidth); context->drawImage(image.get(), pixelSnappedIntRect(LayoutRect(paintOffset + imageOffset, imageSize)), CompositeSourceOver, m_renderImage.shouldRespectImageOrientation()); errorPictureDrawn = true; } if (!m_renderImage.altText().isEmpty()) { const Font& font = m_renderImage.style()->font(); const FontMetrics& fontMetrics = font.fontMetrics(); LayoutUnit ascent = fontMetrics.ascent(); LayoutPoint textRectOrigin = paintOffset; textRectOrigin.move(leftBorder + leftPad + (RenderImage::paddingWidth / 2) - borderWidth, topBorder + topPad + (RenderImage::paddingHeight / 2) - borderWidth); LayoutPoint textOrigin(textRectOrigin.x(), textRectOrigin.y() + ascent); // Only draw the alt text if it'll fit within the content box, // and only if it fits above the error image. TextRun textRun = constructTextRun(&m_renderImage, font, m_renderImage.altText(), m_renderImage.style(), TextRun::AllowTrailingExpansion | TextRun::ForbidLeadingExpansion, DefaultTextRunFlags | RespectDirection); float textWidth = font.width(textRun); TextRunPaintInfo textRunPaintInfo(textRun); textRunPaintInfo.bounds = FloatRect(textRectOrigin, FloatSize(textWidth, fontMetrics.height())); context->setFillColor(m_renderImage.resolveColor(CSSPropertyColor)); if (textRun.direction() == RTL) { int availableWidth = cWidth - static_cast<int>(RenderImage::paddingWidth); textOrigin.move(availableWidth - ceilf(textWidth), 0); } if (errorPictureDrawn) { if (usableWidth >= textWidth && fontMetrics.height() <= imageOffset.height()) context->drawBidiText(font, textRunPaintInfo, textOrigin); } else if (usableWidth >= textWidth && usableHeight >= fontMetrics.height()) { context->drawBidiText(font, textRunPaintInfo, textOrigin); } } } } else if (m_renderImage.imageResource()->hasImage() && cWidth > 0 && cHeight > 0) { LayoutRect contentRect = m_renderImage.contentBoxRect(); contentRect.moveBy(paintOffset); LayoutRect paintRect = m_renderImage.replacedContentRect(); paintRect.moveBy(paintOffset); DrawingRecorder recorder(context, &m_renderImage, paintInfo.phase, contentRect); bool clip = !contentRect.contains(paintRect); if (clip) { context->save(); context->clip(contentRect); } paintIntoRect(context, paintRect); if (clip) context->restore(); } }
PassOwnPtr<DragImage> DragImage::create(const KURL& url, const String& inLabel, const FontDescription& systemFont, float deviceScaleFactor) { const Font labelFont = deriveDragLabelFont(kDragLinkLabelFontSize, FontWeightBold, systemFont); const Font urlFont = deriveDragLabelFont(kDragLinkUrlFontSize, FontWeightNormal, systemFont); FontCachePurgePreventer fontCachePurgePreventer; bool drawURLString = true; bool clipURLString = false; bool clipLabelString = false; String urlString = url.string(); String label = inLabel.stripWhiteSpace(); if (label.isEmpty()) { drawURLString = false; label = urlString; } // First step is drawing the link drag image width. TextRun labelRun(label.impl()); TextRun urlRun(urlString.impl()); IntSize labelSize(labelFont.width(labelRun), labelFont.fontMetrics().ascent() + labelFont.fontMetrics().descent()); if (labelSize.width() > kMaxDragLabelStringWidth) { labelSize.setWidth(kMaxDragLabelStringWidth); clipLabelString = true; } IntSize urlStringSize; IntSize imageSize(labelSize.width() + kDragLabelBorderX * 2, labelSize.height() + kDragLabelBorderY * 2); if (drawURLString) { urlStringSize.setWidth(urlFont.width(urlRun)); urlStringSize.setHeight(urlFont.fontMetrics().ascent() + urlFont.fontMetrics().descent()); imageSize.setHeight(imageSize.height() + urlStringSize.height()); if (urlStringSize.width() > kMaxDragLabelStringWidth) { imageSize.setWidth(kMaxDragLabelWidth); clipURLString = true; } else imageSize.setWidth(std::max(labelSize.width(), urlStringSize.width()) + kDragLabelBorderX * 2); } // We now know how big the image needs to be, so we create and // fill the background IntSize scaledImageSize = imageSize; scaledImageSize.scale(deviceScaleFactor); OwnPtr<ImageBuffer> buffer(ImageBuffer::create(scaledImageSize)); if (!buffer) return nullptr; buffer->context()->scale(FloatSize(deviceScaleFactor, deviceScaleFactor)); const float DragLabelRadius = 5; const IntSize radii(DragLabelRadius, DragLabelRadius); IntRect rect(IntPoint(), imageSize); const Color backgroundColor(140, 140, 140); buffer->context()->fillRoundedRect(rect, radii, radii, radii, radii, backgroundColor); // Draw the text if (drawURLString) { if (clipURLString) urlString = StringTruncator::centerTruncate(urlString, imageSize.width() - (kDragLabelBorderX * 2.0f), urlFont, StringTruncator::EnableRoundingHacks); IntPoint textPos(kDragLabelBorderX, imageSize.height() - (kLabelBorderYOffset + urlFont.fontMetrics().descent())); TextRun textRun(urlString); buffer->context()->drawText(urlFont, TextRunPaintInfo(textRun), textPos); } if (clipLabelString) label = StringTruncator::rightTruncate(label, imageSize.width() - (kDragLabelBorderX * 2.0f), labelFont, StringTruncator::EnableRoundingHacks); bool hasStrongDirectionality; TextRun textRun = textRunWithDirectionality(label, hasStrongDirectionality); IntPoint textPos(kDragLabelBorderX, kDragLabelBorderY + labelFont.fontDescription().computedPixelSize()); if (hasStrongDirectionality && textRun.direction() == RTL) { float textWidth = urlFont.width(textRun); int availableWidth = imageSize.width() - kDragLabelBorderX * 2; textPos.setX(availableWidth - ceilf(textWidth)); } buffer->context()->drawBidiText(urlFont, TextRunPaintInfo(textRun), textPos); RefPtr<Image> image = buffer->copyImage(); return DragImage::create(image.get(), DoNotRespectImageOrientation, deviceScaleFactor); }
float GraphicsContext::drawBidiText(const Font& font, const TextRun& run, const FloatPoint& point, Font::CustomFontNotReadyAction customFontNotReadyAction, BidiStatus* status, int length) #endif { if (paintingDisabled()) #if !PLATFORM(IOS) return; #else return 0; #endif BidiResolver<TextRunIterator, BidiCharacterRun> bidiResolver; #if !PLATFORM(IOS) bidiResolver.setStatus(BidiStatus(run.direction(), run.directionalOverride())); #else bidiResolver.setStatus(status ? *status : BidiStatus(run.direction(), run.directionalOverride())); #endif bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&run, 0)); // FIXME: This ownership should be reversed. We should pass BidiRunList // to BidiResolver in createBidiRunsForLine. BidiRunList<BidiCharacterRun>& bidiRuns = bidiResolver.runs(); #if !PLATFORM(IOS) bidiResolver.createBidiRunsForLine(TextRunIterator(&run, run.length())); #else bidiResolver.createBidiRunsForLine(TextRunIterator(&run, length < 0 ? run.length() : length)); #endif if (!bidiRuns.runCount()) #if !PLATFORM(IOS) return; #else return 0; #endif FloatPoint currPoint = point; BidiCharacterRun* bidiRun = bidiRuns.firstRun(); while (bidiRun) { TextRun subrun = run.subRun(bidiRun->start(), bidiRun->stop() - bidiRun->start()); bool isRTL = bidiRun->level() % 2; subrun.setDirection(isRTL ? RTL : LTR); subrun.setDirectionalOverride(bidiRun->dirOverride(false)); #if !PLATFORM(IOS) font.drawText(this, subrun, currPoint, 0, -1, customFontNotReadyAction); bidiRun = bidiRun->next(); // FIXME: Have Font::drawText return the width of what it drew so that we don't have to re-measure here. if (bidiRun) currPoint.move(font.width(subrun), 0); #else float width = font.drawText(this, subrun, currPoint, 0, -1, customFontNotReadyAction); currPoint.move(width, 0); bidiRun = bidiRun->next(); #endif } #if PLATFORM(IOS) if (status) *status = bidiResolver.status(); #endif bidiRuns.deleteRuns(); #if PLATFORM(IOS) return currPoint.x() - static_cast<float>(point.x()); #endif }
float ShapeResult::fillGlyphBufferForTextEmphasisRun(GlyphBuffer* glyphBuffer, const RunInfo* run, const TextRun& textRun, const GlyphData* emphasisData, float initialAdvance, unsigned from, unsigned to, unsigned runOffset) { if (!run) return 0; unsigned graphemesInCluster = 1; float clusterAdvance = 0; FloatPoint glyphCenter = emphasisData->fontData-> boundsForGlyph(emphasisData->glyph).center(); TextDirection direction = textRun.direction(); // A "cluster" in this context means a cluster as it is used by HarfBuzz: // The minimal group of characters and corresponding glyphs, that cannot be broken // down further from a text shaping point of view. // A cluster can contain multiple glyphs and grapheme clusters, with mutually // overlapping boundaries. Below we count grapheme clusters per HarfBuzz clusters, // then linearly split the sum of corresponding glyph advances by the number of // grapheme clusters in order to find positions for emphasis mark drawing. uint16_t clusterStart = direction == RTL ? run->m_startIndex + run->m_numCharacters + runOffset : run->glyphToCharacterIndex(0) + runOffset; float advanceSoFar = initialAdvance; unsigned numGlyphs = run->m_numGlyphs; for (unsigned i = 0; i < numGlyphs; ++i) { const HarfBuzzRunGlyphData& glyphData = run->m_glyphData[i]; uint16_t currentCharacterIndex = run->m_startIndex + glyphData.characterIndex + runOffset; bool isRunEnd = (i + 1 == numGlyphs); bool isClusterEnd = isRunEnd || (run->glyphToCharacterIndex(i + 1) + runOffset != currentCharacterIndex); if ((direction == RTL && currentCharacterIndex >= to) || (direction != RTL && currentCharacterIndex < from)) { advanceSoFar += glyphData.advance; direction == RTL ? --clusterStart : ++clusterStart; continue; } clusterAdvance += glyphData.advance; if (textRun.is8Bit()) { float glyphAdvanceX = glyphData.advance; if (Character::canReceiveTextEmphasis(textRun[currentCharacterIndex])) { addEmphasisMark(glyphBuffer, emphasisData, glyphCenter, advanceSoFar + glyphAdvanceX / 2); } advanceSoFar += glyphAdvanceX; } else if (isClusterEnd) { uint16_t clusterEnd; if (direction == RTL) clusterEnd = currentCharacterIndex; else clusterEnd = isRunEnd ? run->m_startIndex + run->m_numCharacters + runOffset : run->glyphToCharacterIndex(i + 1) + runOffset; graphemesInCluster = countGraphemesInCluster(textRun.characters16(), textRun.charactersLength(), clusterStart, clusterEnd); if (!graphemesInCluster || !clusterAdvance) continue; float glyphAdvanceX = clusterAdvance / graphemesInCluster; for (unsigned j = 0; j < graphemesInCluster; ++j) { // Do not put emphasis marks on space, separator, and control characters. if (Character::canReceiveTextEmphasis(textRun[currentCharacterIndex])) addEmphasisMark(glyphBuffer, emphasisData, glyphCenter, advanceSoFar + glyphAdvanceX / 2); advanceSoFar += glyphAdvanceX; } clusterStart = clusterEnd; clusterAdvance = 0; } } return advanceSoFar - initialAdvance; }