QList<TextShaper::TextRun> TextShaper::itemizeScripts(const QList<TextRun> &runs) { QList<TextRun> newRuns; ScriptRun scriptrun((const UChar*) m_text.utf16(), m_text.length()); foreach (TextRun run, runs) { int start = run.start; QList<TextRun> subRuns; while (scriptrun.next()) { if (scriptrun.getScriptStart() <= start && scriptrun.getScriptEnd() > start) break; } while (start < run.start + run.len) { int end = qMin(scriptrun.getScriptEnd(), run.start + run.len); UScriptCode script = scriptrun.getScriptCode(); if (run.dir == UBIDI_RTL) subRuns.prepend(TextRun(start, end - start, run.dir, script)); else subRuns.append(TextRun(start, end - start, run.dir, script)); start = end; scriptrun.next(); } scriptrun.reset(); newRuns.append(subRuns); }
static float textWidth(const RenderText& text, unsigned from, unsigned length, float xPosition, const RenderStyle& style) { if (style.font().isFixedPitch() || (!from && length == text.textLength())) return text.width(from, length, style.font(), xPosition, nullptr, nullptr); // FIXME: Add templated UChar/LChar paths. TextRun run = text.is8Bit() ? TextRun(text.characters8() + from, length) : TextRun(text.characters16() + from, length); run.setCharactersLength(text.textLength() - from); ASSERT(run.charactersLength() >= run.length()); run.setXPos(xPosition); return style.font().width(run); }
QList<TextShaper::TextRun> TextShaper::itemizeBiDi() { QList<TextRun> textRuns; UBiDi *obj = ubidi_open(); UErrorCode err = U_ZERO_ERROR; UBiDiLevel parLevel = UBIDI_LTR; ParagraphStyle style = m_story.paragraphStyle(m_firstChar); if (style.direction() == ParagraphStyle::RTL) parLevel = UBIDI_RTL; ubidi_setPara(obj, (const UChar*) m_text.utf16(), m_text.length(), parLevel, NULL, &err); if (U_SUCCESS(err)) { int32_t count = ubidi_countRuns(obj, &err); if (U_SUCCESS(err)) { textRuns.reserve(count); for (int32_t i = 0; i < count; i++) { int32_t start, length; UBiDiDirection dir = ubidi_getVisualRun(obj, i, &start, &length); textRuns.append(TextRun(start, length, dir)); } } } ubidi_close(obj); return textRuns; }
void EllipsisBox::paint(RenderObject::PaintInfo& paintInfo, int tx, int ty) { GraphicsContext* context = paintInfo.context; RenderStyle* style = m_object->style(m_firstLine); if (style->font() != context->font()) context->setFont(style->font()); Color textColor = style->color(); if (textColor != context->fillColor()) context->setFillColor(textColor); bool setShadow = false; if (style->textShadow()) { context->setShadow(IntSize(style->textShadow()->x, style->textShadow()->y), style->textShadow()->blur, style->textShadow()->color); setShadow = true; } const String& str = m_str; TextStyle textStyle(0, 0, 0, false, style->visuallyOrdered()); context->drawText(TextRun(str.impl()), IntPoint(m_x + tx, m_y + ty + m_baseline), textStyle); if (setShadow) context->clearShadow(); if (m_markupBox) { // Paint the markup box tx += m_x + m_width - m_markupBox->xPos(); ty += m_y + m_baseline - (m_markupBox->yPos() + m_markupBox->baseline()); m_markupBox->paint(paintInfo, tx, ty); } }
void PopupMenu::calculatePositionAndSize(const IntRect& r, FrameView* v) { IntRect rScreenCoords(v->contentsToWindow(r.location()), r.size()); rScreenCoords.setY(rScreenCoords.y() + rScreenCoords.height()); m_itemHeight = rScreenCoords.height(); int itemCount = client()->listSize(); int naturalHeight = m_itemHeight * itemCount; int popupWidth = 0; for (int i = 0; i < itemCount; ++i) { String text = client()->itemText(i); if (text.isEmpty()) continue; Font itemFont = client()->clientStyle()->font(); if (client()->itemIsLabel(i)) { FontDescription d = itemFont.fontDescription(); d.setWeight(d.bolderWeight()); itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing()); itemFont.update(m_popupClient->fontSelector()); } popupWidth = max(popupWidth, itemFont.width(TextRun(text.characters(), text.length()))); } rScreenCoords.setHeight(naturalHeight); rScreenCoords.setWidth(popupWidth + 10); m_windowRect = rScreenCoords; }
void InlineTextBox::paintSelection(GraphicsContext* context, int tx, int ty, RenderStyle* style, const Font*) { // See if we have a selection to paint at all. int sPos, ePos; selectionStartEnd(sPos, ePos); if (sPos >= ePos) return; Color textColor = style->color(); Color c = object()->selectionBackgroundColor(); if (!c.isValid() || c.alpha() == 0) return; // If the text color ends up being the same as the selection background, invert the selection // background. This should basically never happen, since the selection has transparency. if (textColor == c) c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue()); context->save(); updateGraphicsContext(context, c, c, 0); // Don't draw text at all! int y = selectionTop(); int h = selectionHeight(); context->clip(IntRect(m_x + tx, y + ty, m_width, h)); context->drawHighlightForText(TextRun(textObject()->text()->characters() + m_start, m_len, textObject()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride || style->visuallyOrdered()), IntPoint(m_x + tx, y + ty), h, c, sPos, ePos); context->restore(); }
static AvoidanceReasonFlags canUseForFontAndText(const RenderBlockFlow& flow, FallThrough fallthrough) { AvoidanceReasonFlags reasons = NoReason; // We assume that all lines have metrics based purely on the primary font. const auto& style = flow.style(); auto& primaryFont = style.fontCascade().primaryFont(); if (primaryFont.isLoading()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowIsMissingPrimaryFont, fallthrough); if (primaryFont.isSVGFont()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowHasSVGFont, fallthrough); for (const auto& textRenderer : childrenOfType<RenderText>(flow)) { if (textRenderer.isCombineText()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowTextIsCombineText, fallthrough); if (textRenderer.isCounter()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowTextIsRenderCounter, fallthrough); if (textRenderer.isQuote()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowTextIsRenderQuote, fallthrough); if (textRenderer.isTextFragment()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowTextIsTextFragment, fallthrough); if (textRenderer.isSVGInlineText()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowTextIsSVGInlineText, fallthrough); if (style.fontCascade().codePath(TextRun(textRenderer.text())) != FontCascade::Simple) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowFontIsNotSimple, fallthrough); auto textReasons = canUseForText(textRenderer, primaryFont, fallthrough); if (textReasons != NoReason) SET_REASON_AND_RETURN_IF_NEEDED(reasons, textReasons, fallthrough); } return reasons; }
IntRect EllipsisBox::selectionRect(int tx, int ty) { RenderStyle* style = m_renderer->style(m_firstLine); const Font& f = style->font(); return enclosingIntRect(f.selectionRectForText(TextRun(m_str.characters(), m_str.length(), false, 0, 0, false, style->visuallyOrdered()), IntPoint(m_x + tx, m_y + ty + root()->selectionTop()), root()->selectionHeight())); }
static inline TextRun constructTextRunInternal(RenderObject* context, const Font& font, const CharacterType* characters, int length, RenderStyle* style, TextDirection direction, TextRun::ExpansionBehavior expansion) { ASSERT(style); bool directionalOverride = style->rtlOrdering() == VisualOrder; return TextRun(characters, length, 0, 0, expansion, direction, directionalOverride); }
int InlineTextBox::offsetForPosition(int _x, bool includePartialGlyphs) const { if (isLineBreak()) return 0; RenderText* text = static_cast<RenderText*>(m_object); RenderStyle *style = text->style(m_firstLine); const Font* f = &style->font(); return f->offsetForPosition(TextRun(textObject()->text()->characters() + m_start, m_len, textObject()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride || style->visuallyOrdered()), _x - m_x, includePartialGlyphs); }
String FileChooser::basenameForWidth(const Font& font, int width) const { if (!m_filenames.size()) return String(); // FIXME: This could be a lot faster, but assuming the data will not // often be much longer than the provided width, this may be fast enough. String output = m_filenames[0].threadsafeCopy(); while (font.width(TextRun(output.impl())) > width && output.length() > 4) output = output.replace(output.length() - 4, 4, String("...")); return output; }
Style(const RenderStyle& style) : font(style.font()) , textAlign(style.textAlign()) , collapseWhitespace(style.collapseWhiteSpace()) , preserveNewline(style.preserveNewline()) , wrapLines(style.autoWrap()) , breakWordOnOverflow(style.overflowWrap() == BreakOverflowWrap && (wrapLines || preserveNewline)) , spaceWidth(font.width(TextRun(&space, 1))) , tabWidth(collapseWhitespace ? 0 : style.tabSize()) { }
void RenderCombineText::combineText() { if (!m_needsFontUpdate) return; setIsCombined(false); setNeedsFontUpdate(false); // CSS3 spec says text-combine works only in vertical writing mode. if (style()->isHorizontalWritingMode()) return; TextRun run = TextRun(String(text())); FontDescription description = originalFont().fontDescription(); float emWidth = description.computedSize() * textCombineMargin; bool shouldUpdateFont = false; description.setOrientation(Horizontal); // We are going to draw combined text horizontally. setCombinedTextWidth(originalFont().width(run)); setIsCombined(m_combinedTextWidth <= emWidth); if (m_isCombined) shouldUpdateFont = style()->setFontDescription(description); // Need to change font orientation to horizontal. else { // Need to try compressed glyphs. static const FontWidthVariant widthVariants[] = { HalfWidth, ThirdWidth, QuarterWidth }; for (size_t i = 0 ; i < WTF_ARRAY_LENGTH(widthVariants) ; ++i) { description.setWidthVariant(widthVariants[i]); Font compressedFont = Font(description, style()->font().letterSpacing(), style()->font().wordSpacing()); compressedFont.update(style()->font().fontSelector()); float runWidth = compressedFont.width(run); if (runWidth <= emWidth) { setCombinedTextWidth(runWidth); setIsCombined(true); // Replace my font with the new one. shouldUpdateFont = style()->setFontDescription(description); break; } } } if (!m_isCombined) shouldUpdateFont = style()->setFontDescription(originalFont().fontDescription()); if (shouldUpdateFont) style()->font().update(style()->font().fontSelector()); if (m_isCombined) { DEFINE_STATIC_LOCAL(String, objectReplacementCharacterString, (&objectReplacementCharacter, 1)); RenderText::setTextInternal(objectReplacementCharacterString.impl()); } }
TextFragmentIterator::Style::Style(const RenderStyle& style) : font(style.fontCascade()) , textAlign(style.textAlign()) , collapseWhitespace(style.collapseWhiteSpace()) , preserveNewline(style.preserveNewline()) , wrapLines(style.autoWrap()) , breakWordOnOverflow(style.overflowWrap() == BreakOverflowWrap && (wrapLines || preserveNewline)) , spaceWidth(font.width(TextRun(StringView(&space, 1)))) , tabWidth(collapseWhitespace ? 0 : style.tabSize()) , locale(style.locale()) { }
String FileChooser::basenameForWidth(const Font& font, int width) const { if (!m_filenames.size()) return String(); // FIXME: This could be a lot faster, but assuming the data will not // often be much longer than the provided width, this may be fast enough. // If this does not need to be threadsafe, we can use crossThreadString(). // See http://trac.webkit.org/changeset/49160. String output = m_filenames[0].threadsafeCopy(); while (font.width(TextRun(output.impl())) > width && output.length() > 4) output = output.replace(0, 4, String("...")); return output; }
static inline TextRun constructTextRunInternal(RenderObject* context, const Font& font, const CharacterType* characters, int length, RenderStyle* style, TextDirection direction, TextRun::ExpansionBehavior expansion, TextRunFlags flags) { ASSERT(style); TextDirection textDirection = direction; bool directionalOverride = style->rtlOrdering() == VisualOrder; if (flags != DefaultTextRunFlags) { if (flags & RespectDirection) textDirection = style->direction(); if (flags & RespectDirectionOverride) directionalOverride |= isOverride(style->unicodeBidi()); } return TextRun(characters, length, 0, 0, expansion, textDirection, directionalOverride); }
LayoutUnit LayoutListMarker::getWidthOfTextWithSuffix() const { if (m_text.isEmpty()) return LayoutUnit(); const Font& font = style()->font(); LayoutUnit itemWidth = LayoutUnit(font.width(TextRun(m_text))); // TODO(wkorman): Look into constructing a text run for both text and suffix // and painting them together. UChar suffix[2] = { ListMarkerText::suffix(style()->listStyleType(), m_listItem->value()), ' '}; TextRun run = constructTextRun(font, suffix, 2, styleRef(), style()->direction()); LayoutUnit suffixSpaceWidth = LayoutUnit(font.width(run)); return itemWidth + suffixSpaceWidth; }
TextFragmentIterator::Style::Style(const RenderStyle& style) : font(style.fontCascade()) , textAlign(style.textAlign()) , collapseWhitespace(style.collapseWhiteSpace()) , preserveNewline(style.preserveNewline()) , wrapLines(style.autoWrap()) , breakAnyWordOnOverflow(style.wordBreak() == BreakAllWordBreak && wrapLines) , breakFirstWordOnOverflow(breakAnyWordOnOverflow || (style.breakWords() && (wrapLines || preserveNewline))) , breakNBSP(wrapLines && style.nbspMode() == SPACE) , keepAllWordsForCJK(style.wordBreak() == KeepAllWordBreak) , spaceWidth(font.width(TextRun(StringView(&space, 1)))) , wordSpacing(font.wordSpacing()) , tabWidth(collapseWhitespace ? 0 : style.tabSize()) , locale(style.locale()) { }
int InlineTextBox::positionForOffset(int offset) const { ASSERT(offset >= m_start); ASSERT(offset <= m_start + m_len); if (isLineBreak()) return m_x; RenderText* text = static_cast<RenderText*>(m_object); const Font& f = text->style(m_firstLine)->font(); int from = direction() == RTL ? offset - m_start : 0; int to = direction() == RTL ? m_len : offset - m_start; // FIXME: Do we need to add rightBearing here? return enclosingIntRect(f.selectionRectForText(TextRun(text->text()->characters() + m_start, m_len, textObject()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride), IntPoint(m_x, 0), 0, from, to)).right(); }
IntRect RenderListMarker::getRelativeMarkerRect() { if (isImage()) return IntRect(m_x, m_y, m_image->imageSize().width(), m_image->imageSize().height()); switch (style()->listStyleType()) { case DISC: case CIRCLE: case SQUARE: { // FIXME: Are these particular rounding rules necessary? const Font& font = style()->font(); int ascent = font.ascent(); int bulletWidth = (ascent * 2 / 3 + 1) / 2; return IntRect(m_x + 1, m_y + 3 * (ascent - ascent * 2 / 3) / 2, bulletWidth, bulletWidth); } case LNONE: return IntRect(); case ARMENIAN: case CJK_IDEOGRAPHIC: case DECIMAL_LEADING_ZERO: case GEORGIAN: case HEBREW: case HIRAGANA: case HIRAGANA_IROHA: case KATAKANA: case KATAKANA_IROHA: case LDECIMAL: case LOWER_ALPHA: case LOWER_GREEK: case LOWER_LATIN: case LOWER_ROMAN: case UPPER_ALPHA: case UPPER_LATIN: case UPPER_ROMAN: if (m_text.isEmpty()) return IntRect(); const Font& font = style()->font(); int itemWidth = font.width(m_text); const UChar periodSpace[2] = { '.', ' ' }; int periodSpaceWidth = font.width(TextRun(periodSpace, 2)); return IntRect(m_x, m_y + font.ascent(), itemWidth + periodSpaceWidth, font.height()); } return IntRect(); }
void EllipsisBox::paintSelection(GraphicsContext* context, int tx, int ty, RenderStyle* style, const Font& font) { Color textColor = style->color(); Color c = m_renderer->selectionBackgroundColor(); if (!c.isValid() || !c.alpha()) return; // If the text color ends up being the same as the selection background, invert the selection // background. if (textColor == c) c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue()); context->save(); int y = root()->selectionTop(); int h = root()->selectionHeight(); context->clip(IntRect(m_x + tx, y + ty, m_width, h)); context->drawHighlightForText(font, TextRun(m_str.characters(), m_str.length(), false, 0, 0, false, style->visuallyOrdered()), IntPoint(m_x + tx, m_y + ty + y), h, c, style->colorSpace()); context->restore(); }
void InlineTextBox::paintCompositionBackground(GraphicsContext* context, int tx, int ty, RenderStyle* style, const Font*, int startPos, int endPos) { int offset = m_start; int sPos = max(startPos - offset, 0); int ePos = min(endPos - offset, (int)m_len); if (sPos >= ePos) return; context->save(); Color c = Color(225, 221, 85); updateGraphicsContext(context, c, c, 0); // Don't draw text at all! int y = selectionTop(); int h = selectionHeight(); context->drawHighlightForText(TextRun(textObject()->text()->characters() + m_start, m_len, textObject()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride || style->visuallyOrdered()), IntPoint(m_x + tx, y + ty), h, c, sPos, ePos); context->restore(); }
IntRect InlineTextBox::selectionRect(int tx, int ty, int startPos, int endPos) { int sPos = max(startPos - m_start, 0); int ePos = min(endPos - m_start, (int)m_len); if (sPos >= ePos) return IntRect(); RenderText* textObj = textObject(); int selTop = selectionTop(); int selHeight = selectionHeight(); const Font& f = textObj->style(m_firstLine)->font(); IntRect r = enclosingIntRect(f.selectionRectForText(TextRun(textObj->text()->characters() + m_start, m_len, textObj->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride), IntPoint(tx + m_x, ty + selTop), selHeight, sPos, ePos)); if (r.x() > tx + m_x + m_width) r.setWidth(0); else if (r.right() - 1 > tx + m_x + m_width) r.setWidth(tx + m_x + m_width - r.x()); return r; }
void EllipsisBox::paint(RenderObject::PaintInfo& paintInfo, int tx, int ty) { GraphicsContext* context = paintInfo.context; RenderStyle* style = m_renderer->style(m_firstLine); Color textColor = style->color(); if (textColor != context->fillColor()) context->setFillColor(textColor, style->colorSpace()); bool setShadow = false; if (style->textShadow()) { context->setShadow(IntSize(style->textShadow()->x, style->textShadow()->y), style->textShadow()->blur, style->textShadow()->color, style->colorSpace()); setShadow = true; } if (selectionState() != RenderObject::SelectionNone) { paintSelection(context, tx, ty, style, style->font()); // Select the correct color for painting the text. Color foreground = paintInfo.forceBlackText ? Color::black : renderer()->selectionForegroundColor(); if (foreground.isValid() && foreground != textColor) context->setFillColor(foreground, style->colorSpace()); } const String& str = m_str; context->drawText(style->font(), TextRun(str.characters(), str.length(), false, 0, 0, false, style->visuallyOrdered()), IntPoint(m_x + tx, m_y + ty + style->font().ascent())); // Restore the regular fill color. if (textColor != context->fillColor()) context->setFillColor(textColor, style->colorSpace()); if (setShadow) context->clearShadow(); if (m_markupBox) { // Paint the markup box tx += m_x + m_width - m_markupBox->x(); ty += m_y + style->font().ascent() - (m_markupBox->y() + m_markupBox->renderer()->style(m_firstLine)->font().ascent()); m_markupBox->paint(paintInfo, tx, ty); } }
bool canUseFor(const RenderBlockFlow& flow) { if (!flow.frame().settings().simpleLineLayoutEnabled()) return false; if (!flow.firstChild()) return false; // This currently covers <blockflow>#text</blockflow> case. // The <blockflow><inline>#text</inline></blockflow> case is also popular and should be relatively easy to cover. if (flow.firstChild() != flow.lastChild()) return false; if (!flow.firstChild()->isText()) return false; if (!flow.isHorizontalWritingMode()) return false; if (flow.flowThreadState() != RenderObject::NotInsideFlowThread) return false; if (flow.hasOutline()) return false; if (flow.isRubyText() || flow.isRubyBase()) return false; if (flow.parent()->isDeprecatedFlexibleBox()) return false; // FIXME: Implementation of wrap=hard looks into lineboxes. if (flow.parent()->isTextArea() && flow.parent()->element()->fastHasAttribute(HTMLNames::wrapAttr)) return false; // FIXME: Placeholders do something strange. if (flow.parent()->isTextControl() && toRenderTextControl(*flow.parent()).textFormControlElement().placeholderElement()) return false; const RenderStyle& style = flow.style(); if (style.textDecorationsInEffect() != TextDecorationNone) return false; if (style.textAlign() == JUSTIFY) return false; // Non-visible overflow should be pretty easy to support. if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE) return false; if (!style.textIndent().isZero()) return false; if (!style.wordSpacing().isZero() || style.letterSpacing()) return false; if (style.textTransform() != TTNONE) return false; if (!style.isLeftToRightDirection()) return false; if (style.lineBoxContain() != RenderStyle::initialLineBoxContain()) return false; if (style.writingMode() != TopToBottomWritingMode) return false; if (style.lineBreak() != LineBreakAuto) return false; if (style.wordBreak() != NormalWordBreak) return false; if (style.unicodeBidi() != UBNormal || style.rtlOrdering() != LogicalOrder) return false; if (style.lineAlign() != LineAlignNone || style.lineSnap() != LineSnapNone) return false; if (style.hyphens() == HyphensAuto) return false; if (style.textEmphasisFill() != TextEmphasisFillFilled || style.textEmphasisMark() != TextEmphasisMarkNone) return false; if (style.textShadow()) return false; if (style.textOverflow() || (flow.isAnonymousBlock() && flow.parent()->style().textOverflow())) return false; if (style.hasPseudoStyle(FIRST_LINE) || style.hasPseudoStyle(FIRST_LETTER)) return false; if (style.hasTextCombine()) return false; if (style.backgroundClip() == TextFillBox) return false; if (style.borderFit() == BorderFitLines) return false; const RenderText& textRenderer = toRenderText(*flow.firstChild()); if (flow.containsFloats()) { // We can't use the code path if any lines would need to be shifted below floats. This is because we don't keep per-line y coordinates. float minimumWidthNeeded = textRenderer.minLogicalWidth(); for (auto& floatRenderer : *flow.floatingObjectSet()) { ASSERT(floatRenderer); float availableWidth = flow.availableLogicalWidthForLine(floatRenderer->y(), false); if (availableWidth < minimumWidthNeeded) return false; } } if (textRenderer.isCombineText() || textRenderer.isCounter() || textRenderer.isQuote() || textRenderer.isTextFragment() || textRenderer.isSVGInlineText()) return false; if (style.font().codePath(TextRun(textRenderer.text())) != Font::Simple) return false; if (style.font().isSVGFont()) return false; // We assume that all lines have metrics based purely on the primary font. auto& primaryFontData = *style.font().primaryFont(); if (primaryFontData.isLoading()) return false; if (!canUseForText(textRenderer, primaryFontData)) return false; return true; }
virtual void drawInContext(PlatformGraphicsContext* context) { if (!m_owner) return; CGContextSaveGState(context); CGRect layerBounds = bounds(); if (m_owner->contentsOrientation() == WebCore::GraphicsLayer::CompositingCoordinatesTopDown) { CGContextScaleCTM(context, 1, -1); CGContextTranslateCTM(context, 0, -layerBounds.size.height); } if (m_owner->client()) { GraphicsContext graphicsContext(context); // It's important to get the clip from the context, because it may be significantly // smaller than the layer bounds (e.g. tiled layers) CGRect clipBounds = CGContextGetClipBoundingBox(context); IntRect clip(enclosingIntRect(clipBounds)); m_owner->paintGraphicsLayerContents(graphicsContext, clip); } #ifndef NDEBUG else { ASSERT_NOT_REACHED(); // FIXME: ideally we'd avoid calling -setNeedsDisplay on a layer that is a plain color, // so CA never makes backing store for it (which is what -setNeedsDisplay will do above). CGContextSetRGBFillColor(context, 0.0f, 1.0f, 0.0f, 1.0f); CGContextFillRect(context, layerBounds); } #endif if (m_owner->showRepaintCounter()) { String text = String::format("%d", m_owner->incrementRepaintCount());; CGContextSaveGState(context); CGContextSetRGBFillColor(context, 1.0f, 0.0f, 0.0f, 0.8f); CGRect aBounds = layerBounds; aBounds.size.width = 10 + 12 * text.length(); aBounds.size.height = 25; CGContextFillRect(context, aBounds); FontDescription desc; NONCLIENTMETRICS metrics; metrics.cbSize = sizeof(metrics); SystemParametersInfo(SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0); FontFamily family; family.setFamily(metrics.lfSmCaptionFont.lfFaceName); desc.setFamily(family); desc.setComputedSize(22); Font font = Font(desc, 0, 0); font.update(0); GraphicsContext cg(context); cg.setFillColor(Color::black, DeviceColorSpace); cg.drawText(font, TextRun(text), IntPoint(aBounds.origin.x + 3, aBounds.origin.y + 20)); CGContextRestoreGState(context); } CGContextRestoreGState(context); }
bool canUseFor(const RenderBlockFlow& flow) { #if !PLATFORM(MAC) && !PLATFORM(GTK) && !PLATFORM(EFL) // FIXME: Non-mac platforms are hitting ASSERT(run.charactersLength() >= run.length()) // https://bugs.webkit.org/show_bug.cgi?id=123338 return false; #else if (!flow.frame().settings().simpleLineLayoutEnabled()) return false; if (!flow.firstChild()) return false; // This currently covers <blockflow>#text</blockflow> case. // The <blockflow><inline>#text</inline></blockflow> case is also popular and should be relatively easy to cover. if (flow.firstChild() != flow.lastChild()) return false; if (!flow.firstChild()->isText()) return false; if (!flow.isHorizontalWritingMode()) return false; if (flow.flowThreadState() != RenderObject::NotInsideFlowThread) return false; if (flow.hasOutline()) return false; if (flow.isRubyText() || flow.isRubyBase()) return false; if (flow.parent()->isDeprecatedFlexibleBox()) return false; // FIXME: Implementation of wrap=hard looks into lineboxes. if (flow.parent()->isTextArea() && flow.parent()->element()->fastHasAttribute(HTMLNames::wrapAttr)) return false; // FIXME: Placeholders do something strange. if (flow.parent()->isTextControl() && toRenderTextControl(*flow.parent()).textFormControlElement().placeholderElement()) return false; // These tests only works during layout. Outside layout this function may give false positives. if (flow.view().layoutState()) { #if ENABLE(CSS_SHAPES) if (flow.view().layoutState()->shapeInsideInfo()) return false; #endif if (flow.view().layoutState()->m_columnInfo) return false; } const RenderStyle& style = flow.style(); if (style.textDecorationsInEffect() != TextDecorationNone) return false; if (style.textAlign() == JUSTIFY) return false; // Non-visible overflow should be pretty easy to support. if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE) return false; if (!style.textIndent().isZero()) return false; if (style.wordSpacing() || style.letterSpacing()) return false; if (style.textTransform() != TTNONE) return false; if (!style.isLeftToRightDirection()) return false; if (style.lineBoxContain() != RenderStyle::initialLineBoxContain()) return false; if (style.writingMode() != TopToBottomWritingMode) return false; if (style.lineBreak() != LineBreakAuto) return false; if (style.wordBreak() != NormalWordBreak) return false; if (style.unicodeBidi() != UBNormal || style.rtlOrdering() != LogicalOrder) return false; if (style.lineAlign() != LineAlignNone || style.lineSnap() != LineSnapNone) return false; if (style.hyphens() == HyphensAuto) return false; if (style.textEmphasisFill() != TextEmphasisFillFilled || style.textEmphasisMark() != TextEmphasisMarkNone) return false; if (style.textShadow()) return false; #if ENABLE(CSS_SHAPES) if (style.resolvedShapeInside()) return true; #endif if (style.textOverflow() || (flow.isAnonymousBlock() && flow.parent()->style().textOverflow())) return false; if (style.hasPseudoStyle(FIRST_LINE) || style.hasPseudoStyle(FIRST_LETTER)) return false; if (style.hasTextCombine()) return false; if (style.backgroundClip() == TextFillBox) return false; if (style.borderFit() == BorderFitLines) return false; const RenderText& textRenderer = toRenderText(*flow.firstChild()); if (flow.containsFloats()) { // We can't use the code path if any lines would need to be shifted below floats. This is because we don't keep per-line y coordinates. // It is enough to test the first line width only as currently all floats must be overhanging. if (textRenderer.minLogicalWidth() > LineWidth(const_cast<RenderBlockFlow&>(flow), false, DoNotIndentText).availableWidth()) return false; } if (textRenderer.isCombineText() || textRenderer.isCounter() || textRenderer.isQuote() || textRenderer.isTextFragment() #if ENABLE(SVG) || textRenderer.isSVGInlineText() #endif ) return false; if (style.font().codePath(TextRun(textRenderer.text())) != Font::Simple) return false; if (style.font().isSVGFont()) return false; // We assume that all lines have metrics based purely on the primary font. auto& primaryFontData = *style.font().primaryFont(); if (primaryFontData.isLoading()) return false; if (!canUseForText(textRenderer, primaryFontData)) return false; return true; #endif }
bool canUseFor(const RenderBlockFlow& flow) { #if !PLATFORM(MAC) // FIXME: Non-mac platforms are hitting ASSERT(run.charactersLength() >= run.length()) // https://bugs.webkit.org/show_bug.cgi?id=123338 return false; #endif if (!flow.firstChild()) return false; // This currently covers <blockflow>#text</blockflow> case. // The <blockflow><inline>#text</inline></blockflow> case is also popular and should be relatively easy to cover. if (flow.firstChild() != flow.lastChild()) return false; if (!flow.firstChild()->isText()) return false; // Supporting floats would be very beneficial. if (flow.containsFloats()) return false; if (!flow.isHorizontalWritingMode()) return false; if (flow.flowThreadState() != RenderObject::NotInsideFlowThread) return false; if (flow.hasOutline()) return false; if (flow.isRubyText() || flow.isRubyBase()) return false; // These tests only works during layout. Outside layout this function may give false positives. if (flow.view().layoutState()) { #if ENABLE(CSS_SHAPES) if (flow.view().layoutState()->shapeInsideInfo()) return false; #endif if (flow.view().layoutState()->m_columnInfo) return false; } const RenderStyle& style = *flow.style(); // It shoudn't be hard to support other alignments. if (style.textAlign() != LEFT && style.textAlign() != WEBKIT_LEFT && style.textAlign() != TASTART) return false; // Non-visible overflow should be pretty easy to support. if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE) return false; // Pre/no-wrap would be very helpful to support. if (style.whiteSpace() != NORMAL) return false; if (!style.textIndent().isZero()) return false; if (style.wordSpacing() || style.letterSpacing()) return false; if (style.textTransform() != TTNONE) return false; if (!style.isLeftToRightDirection()) return false; if (style.lineBoxContain() != RenderStyle::initialLineBoxContain()) return false; if (style.writingMode() != TopToBottomWritingMode) return false; if (style.lineBreak() != LineBreakAuto) return false; if (style.wordBreak() != NormalWordBreak) return false; if (style.unicodeBidi() != UBNormal || style.rtlOrdering() != LogicalOrder) return false; if (style.lineAlign() != LineAlignNone || style.lineSnap() != LineSnapNone) return false; if (style.hyphens() == HyphensAuto) return false; if (style.textEmphasisFill() != TextEmphasisFillFilled || style.textEmphasisMark() != TextEmphasisMarkNone) return false; if (style.textShadow()) return false; #if ENABLE(CSS_SHAPES) if (style.resolvedShapeInside()) return true; #endif if (style.textOverflow() || (flow.isAnonymousBlock() && flow.parent()->style()->textOverflow())) return false; if (style.hasPseudoStyle(FIRST_LINE) || style.hasPseudoStyle(FIRST_LETTER)) return false; if (style.hasTextCombine()) return false; if (style.overflowWrap() != NormalOverflowWrap) return false; if (style.backgroundClip() == TextFillBox) return false; if (style.borderFit() == BorderFitLines) return false; const RenderText& textRenderer = toRenderText(*flow.firstChild()); if (textRenderer.isCombineText() || textRenderer.isCounter() || textRenderer.isQuote() || textRenderer.isTextFragment() #if ENABLE(SVG) || textRenderer.isSVGInlineText() #endif ) return false; if (style.font().codePath(TextRun(textRenderer.text())) != Font::Simple) return false; auto primaryFontData = style.font().primaryFont(); unsigned length = textRenderer.textLength(); unsigned consecutiveSpaceCount = 0; for (unsigned i = 0; i < length; ++i) { // This rejects anything with more than one consecutive whitespace, except at the beginning or end. // This is because we don't currently do subruns within lines. Fixing this would improve coverage significantly. UChar character = textRenderer.characterAt(i); if (isWhitespace(character)) ++consecutiveSpaceCount; else { if (consecutiveSpaceCount != i && consecutiveSpaceCount > 1) return false; consecutiveSpaceCount = 0; } // These would be easy to support. if (character == noBreakSpace) return false; if (character == softHyphen) return false; static const UChar lowestRTLCharacter = 0x590; if (character >= lowestRTLCharacter) { UCharDirection direction = u_charDirection(character); if (direction == U_RIGHT_TO_LEFT || direction == U_RIGHT_TO_LEFT_ARABIC || direction == U_RIGHT_TO_LEFT_EMBEDDING || direction == U_RIGHT_TO_LEFT_OVERRIDE || direction == U_LEFT_TO_RIGHT_EMBEDDING || direction == U_LEFT_TO_RIGHT_OVERRIDE) return false; } if (!primaryFontData->glyphForCharacter(character)) return false; } return true; }
std::unique_ptr<Lines> createLines(RenderBlockFlow& flow) { auto lines = std::make_unique<Lines>(); RenderText& textRenderer = toRenderText(*flow.firstChild()); ASSERT(!textRenderer.firstTextBox()); const RenderStyle& style = *flow.style(); const unsigned textLength = textRenderer.textLength(); float wordTrailingSpaceWidth = style.font().width(TextRun(&space, 1)); LazyLineBreakIterator lineBreakIterator(textRenderer.text(), style.locale()); int nextBreakable = -1; unsigned lineEndOffset = 0; while (lineEndOffset < textLength) { lineEndOffset = skipWhitespaces(textRenderer, lineEndOffset, textLength); unsigned lineStartOffset = lineEndOffset; unsigned runEndOffset = lineEndOffset; LineWidth lineWidth(flow, false, DoNotIndentText); while (runEndOffset < textLength) { ASSERT(!isWhitespace(textRenderer.characterAt(runEndOffset))); bool previousWasSpaceBetweenRuns = runEndOffset > lineStartOffset && isWhitespace(textRenderer.characterAt(runEndOffset - 1)); unsigned runStartOffset = previousWasSpaceBetweenRuns ? runEndOffset - 1 : runEndOffset; ++runEndOffset; while (runEndOffset < textLength) { if (runEndOffset > lineStartOffset && isBreakable(lineBreakIterator, runEndOffset, nextBreakable, false)) break; ++runEndOffset; } unsigned runLength = runEndOffset - runStartOffset; bool includeEndSpace = runEndOffset < textLength && textRenderer.characterAt(runEndOffset) == ' '; float wordWidth; if (includeEndSpace) wordWidth = textWidth(textRenderer, runStartOffset, runLength + 1, lineWidth.committedWidth(), style) - wordTrailingSpaceWidth; else wordWidth = textWidth(textRenderer, runStartOffset, runLength, lineWidth.committedWidth(), style); lineWidth.addUncommittedWidth(wordWidth); if (!lineWidth.fitsOnLine()) { if (!lineWidth.committedWidth()) { lineWidth.commit(); lineEndOffset = runEndOffset; } break; } lineWidth.commit(); lineEndOffset = runEndOffset; runEndOffset = skipWhitespaces(textRenderer, runEndOffset, textLength); } if (lineStartOffset == lineEndOffset) continue; Line line; line.textOffset = lineStartOffset; line.textLength = lineEndOffset - lineStartOffset; line.width = lineWidth.committedWidth(); lines->append(line); } textRenderer.clearNeedsLayout(); lines->shrinkToFit(); return lines; }
WebTextRun::operator WebCore::TextRun() const { return TextRun(text, 0, 0, TextRun::AllowTrailingExpansion, rtl ? RTL : LTR, directionalOverride); }