1 /* 2 Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) 3 Copyright (C) 2008, 2010 Holger Hans Peter Freyther 4 Copyright (C) 2009 Dirk Schulze <krit (at) webkit.org> 5 6 This library is free software; you can redistribute it and/or 7 modify it under the terms of the GNU Library General Public 8 License as published by the Free Software Foundation; either 9 version 2 of the License, or (at your option) any later version. 10 11 This library is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 Library General Public License for more details. 15 16 You should have received a copy of the GNU Library General Public License 17 along with this library; see the file COPYING.LIB. If not, write to 18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 Boston, MA 02110-1301, USA. 20 */ 21 22 #include "config.h" 23 #include "Font.h" 24 25 #include "AffineTransform.h" 26 #include "ContextShadow.h" 27 #include "FontDescription.h" 28 #include "FontFallbackList.h" 29 #include "FontSelector.h" 30 #include "Gradient.h" 31 #include "GraphicsContext.h" 32 #include "NotImplemented.h" 33 #include "Pattern.h" 34 #include "TextRun.h" 35 36 #include <QBrush> 37 #include <QFontInfo> 38 #include <QFontMetrics> 39 #include <QPainter> 40 #include <QPainterPath> 41 #include <QPen> 42 #include <QTextLayout> 43 #include <qalgorithms.h> 44 #include <qdebug.h> 45 46 #include <limits.h> 47 48 namespace WebCore { 49 50 static const QString fromRawDataWithoutRef(const String& string, int start = 0, int len = -1) 51 { 52 if (len < 0) 53 len = string.length() - start; 54 Q_ASSERT(start + len <= string.length()); 55 56 // We don't detach. This assumes the WebCore string data will stay valid for the 57 // lifetime of the QString we pass back, since we don't ref the WebCore string. 58 return QString::fromRawData(reinterpret_cast<const QChar*>(string.characters() + start), len); 59 } 60 61 static QTextLine setupLayout(QTextLayout* layout, const TextRun& style) 62 { 63 int flags = style.rtl() ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight; 64 if (style.expansion()) 65 flags |= Qt::TextJustificationForced; 66 layout->setFlags(flags); 67 layout->beginLayout(); 68 QTextLine line = layout->createLine(); 69 line.setLineWidth(INT_MAX/256); 70 if (style.expansion()) 71 line.setLineWidth(line.naturalTextWidth() + style.expansion()); 72 layout->endLayout(); 73 return line; 74 } 75 76 static void drawTextCommon(GraphicsContext* ctx, const TextRun& run, const FloatPoint& point, int from, int to, const QFont& font, bool isComplexText) 77 { 78 if (to < 0) 79 to = run.length(); 80 81 QPainter *p = ctx->platformContext(); 82 83 QPen textFillPen; 84 if (ctx->textDrawingMode() & TextModeFill) { 85 if (ctx->fillGradient()) { 86 QBrush brush(*ctx->fillGradient()->platformGradient()); 87 brush.setTransform(ctx->fillGradient()->gradientSpaceTransform()); 88 textFillPen = QPen(brush, 0); 89 } else if (ctx->fillPattern()) { 90 AffineTransform affine; 91 textFillPen = QPen(QBrush(ctx->fillPattern()->createPlatformPattern(affine)), 0); 92 } else 93 textFillPen = QPen(QColor(ctx->fillColor())); 94 } 95 96 QPen textStrokePen; 97 if (ctx->textDrawingMode() & TextModeStroke) { 98 if (ctx->strokeGradient()) { 99 QBrush brush(*ctx->strokeGradient()->platformGradient()); 100 brush.setTransform(ctx->strokeGradient()->gradientSpaceTransform()); 101 textStrokePen = QPen(brush, ctx->strokeThickness()); 102 } else if (ctx->strokePattern()) { 103 AffineTransform affine; 104 QBrush brush(ctx->strokePattern()->createPlatformPattern(affine)); 105 textStrokePen = QPen(brush, ctx->strokeThickness()); 106 } else 107 textStrokePen = QPen(QColor(ctx->strokeColor()), ctx->strokeThickness()); 108 } 109 110 String sanitized = Font::normalizeSpaces(run.characters(), run.length()); 111 QString string = fromRawDataWithoutRef(sanitized); 112 QPointF pt(point.x(), point.y()); 113 114 if (from > 0 || to < run.length()) { 115 if (isComplexText) { 116 QTextLayout layout(string, font); 117 QTextLine line = setupLayout(&layout, run); 118 float x1 = line.cursorToX(from); 119 float x2 = line.cursorToX(to); 120 if (x2 < x1) 121 qSwap(x1, x2); 122 123 QFontMetrics fm(font); 124 int ascent = fm.ascent(); 125 QRectF boundingRect(point.x() + x1, point.y() - ascent, x2 - x1, fm.height()); 126 QRectF clip = boundingRect; 127 128 ContextShadow* ctxShadow = ctx->contextShadow(); 129 130 if (ctxShadow->m_type != ContextShadow::NoShadow) { 131 qreal dx1 = 0, dx2 = 0, dy1 = 0, dy2 = 0; 132 if (ctxShadow->offset().x() > 0) 133 dx2 = ctxShadow->offset().x(); 134 else 135 dx1 = -ctxShadow->offset().x(); 136 if (ctxShadow->offset().y() > 0) 137 dy2 = ctxShadow->offset().y(); 138 else 139 dy1 = -ctxShadow->offset().y(); 140 // expand the clip rect to include the text shadow as well 141 clip.adjust(dx1, dx2, dy1, dy2); 142 clip.adjust(-ctxShadow->m_blurDistance, -ctxShadow->m_blurDistance, ctxShadow->m_blurDistance, ctxShadow->m_blurDistance); 143 } 144 p->save(); 145 p->setClipRect(clip.toRect(), Qt::IntersectClip); 146 pt.setY(pt.y() - ascent); 147 148 if (ctxShadow->m_type != ContextShadow::NoShadow) { 149 ContextShadow* ctxShadow = ctx->contextShadow(); 150 if (!ctxShadow->mustUseContextShadow(ctx)) { 151 p->save(); 152 p->setPen(ctxShadow->m_color); 153 p->translate(ctxShadow->offset()); 154 line.draw(p, pt); 155 p->restore(); 156 } else { 157 QPainter* shadowPainter = ctxShadow->beginShadowLayer(ctx, boundingRect); 158 if (shadowPainter) { 159 // Since it will be blurred anyway, we don't care about render hints. 160 shadowPainter->setFont(p->font()); 161 shadowPainter->setPen(ctxShadow->m_color); 162 line.draw(shadowPainter, pt); 163 ctxShadow->endShadowLayer(ctx); 164 } 165 } 166 } 167 p->setPen(textFillPen); 168 line.draw(p, pt); 169 p->restore(); 170 return; 171 } 172 int skipWidth = QFontMetrics(font).width(string, from, Qt::TextBypassShaping); 173 pt.setX(pt.x() + skipWidth); 174 string = fromRawDataWithoutRef(sanitized, from, to - from); 175 } 176 177 p->setFont(font); 178 179 int flags = run.rtl() ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight; 180 if (!isComplexText && !(ctx->textDrawingMode() & TextModeStroke)) 181 flags |= Qt::TextBypassShaping; 182 183 QPainterPath textStrokePath; 184 if (ctx->textDrawingMode() & TextModeStroke) 185 textStrokePath.addText(pt, font, string); 186 187 ContextShadow* ctxShadow = ctx->contextShadow(); 188 if (ctxShadow->m_type != ContextShadow::NoShadow) { 189 if (ctx->textDrawingMode() & TextModeFill) { 190 if (ctxShadow->m_type != ContextShadow::BlurShadow) { 191 p->save(); 192 p->setPen(ctxShadow->m_color); 193 p->translate(ctxShadow->offset()); 194 p->drawText(pt, string, flags, run.expansion()); 195 p->restore(); 196 } else { 197 QFontMetrics fm(font); 198 QRectF boundingRect(pt.x(), point.y() - fm.ascent(), fm.width(string, -1, flags), fm.height()); 199 QPainter* shadowPainter = ctxShadow->beginShadowLayer(ctx, boundingRect); 200 if (shadowPainter) { 201 // Since it will be blurred anyway, we don't care about render hints. 202 shadowPainter->setFont(p->font()); 203 shadowPainter->setPen(ctxShadow->m_color); 204 shadowPainter->drawText(pt, string, flags, run.expansion()); 205 ctxShadow->endShadowLayer(ctx); 206 } 207 } 208 } else if (ctx->textDrawingMode() & TextModeStroke) { 209 if (ctxShadow->m_type != ContextShadow::BlurShadow) { 210 p->translate(ctxShadow->offset()); 211 p->strokePath(textStrokePath, QPen(ctxShadow->m_color)); 212 p->translate(-ctxShadow->offset()); 213 } else { 214 QFontMetrics fm(font); 215 QRectF boundingRect(pt.x(), point.y() - fm.ascent(), fm.width(string, -1, flags), fm.height()); 216 QPainter* shadowPainter = ctxShadow->beginShadowLayer(ctx, boundingRect); 217 if (shadowPainter) { 218 // Since it will be blurred anyway, we don't care about render hints. 219 shadowPainter->setFont(p->font()); 220 shadowPainter->strokePath(textStrokePath, QPen(ctxShadow->m_color)); 221 ctxShadow->endShadowLayer(ctx); 222 } 223 } 224 } 225 } 226 227 if (ctx->textDrawingMode() & TextModeStroke) 228 p->strokePath(textStrokePath, textStrokePen); 229 230 if (ctx->textDrawingMode() & TextModeFill) { 231 QPen previousPen = p->pen(); 232 p->setPen(textFillPen); 233 p->drawText(pt, string, flags, run.expansion()); 234 p->setPen(previousPen); 235 } 236 } 237 238 void Font::drawSimpleText(GraphicsContext* ctx, const TextRun& run, const FloatPoint& point, int from, int to) const 239 { 240 drawTextCommon(ctx, run, point, from, to, font(), /* isComplexText = */false); 241 } 242 243 void Font::drawComplexText(GraphicsContext* ctx, const TextRun& run, const FloatPoint& point, int from, int to) const 244 { 245 drawTextCommon(ctx, run, point, from, to, font(), /* isComplexText = */true); 246 } 247 248 int Font::emphasisMarkAscent(const AtomicString&) const 249 { 250 notImplemented(); 251 return 0; 252 } 253 254 int Font::emphasisMarkDescent(const AtomicString&) const 255 { 256 notImplemented(); 257 return 0; 258 } 259 260 int Font::emphasisMarkHeight(const AtomicString&) const 261 { 262 notImplemented(); 263 return 0; 264 } 265 266 void Font::drawEmphasisMarksForSimpleText(GraphicsContext* /* context */, const TextRun& /* run */, const AtomicString& /* mark */, const FloatPoint& /* point */, int /* from */, int /* to */) const 267 { 268 notImplemented(); 269 } 270 271 void Font::drawEmphasisMarksForComplexText(GraphicsContext* /* context */, const TextRun& /* run */, const AtomicString& /* mark */, const FloatPoint& /* point */, int /* from */, int /* to */) const 272 { 273 notImplemented(); 274 } 275 276 float Font::floatWidthForSimpleText(const TextRun& run, GlyphBuffer* glyphBuffer, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const 277 { 278 if (!primaryFont()->platformData().size()) 279 return 0; 280 281 if (!run.length()) 282 return 0; 283 284 String sanitized = Font::normalizeSpaces(run.characters(), run.length()); 285 QString string = fromRawDataWithoutRef(sanitized); 286 287 int w = QFontMetrics(font()).width(string, -1, Qt::TextBypassShaping); 288 289 // WebKit expects us to ignore word spacing on the first character (as opposed to what Qt does) 290 if (treatAsSpace(run[0])) 291 w -= m_wordSpacing; 292 293 return w + run.expansion(); 294 } 295 296 float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>*, GlyphOverflow*) const 297 { 298 if (!primaryFont()->platformData().size()) 299 return 0; 300 301 if (!run.length()) 302 return 0; 303 304 if (run.length() == 1 && treatAsSpace(run[0])) 305 return QFontMetrics(font()).width(space) + run.expansion(); 306 307 String sanitized = Font::normalizeSpaces(run.characters(), run.length()); 308 QString string = fromRawDataWithoutRef(sanitized); 309 310 int w = QFontMetrics(font()).width(string); 311 // WebKit expects us to ignore word spacing on the first character (as opposed to what Qt does) 312 if (treatAsSpace(run[0])) 313 w -= m_wordSpacing; 314 315 return w + run.expansion(); 316 } 317 318 int Font::offsetForPositionForSimpleText(const TextRun& run, float position, bool includePartialGlyphs) const 319 { 320 String sanitized = Font::normalizeSpaces(run.characters(), run.length()); 321 QString string = fromRawDataWithoutRef(sanitized); 322 323 QFontMetrics fm(font()); 324 float delta = position; 325 int curPos = 0; 326 do { 327 float charWidth = fm.width(string[curPos]); 328 delta -= charWidth; 329 if (includePartialGlyphs) { 330 if (delta + charWidth / 2 <= 0) 331 break; 332 } else { 333 if (delta + charWidth <= 0) 334 break; 335 } 336 } while (++curPos < string.size()); 337 338 return curPos; 339 } 340 341 int Font::offsetForPositionForComplexText(const TextRun& run, float position, bool) const 342 { 343 String sanitized = Font::normalizeSpaces(run.characters(), run.length()); 344 QString string = fromRawDataWithoutRef(sanitized); 345 346 QTextLayout layout(string, font()); 347 QTextLine line = setupLayout(&layout, run); 348 return line.xToCursor(position); 349 } 350 351 FloatRect Font::selectionRectForSimpleText(const TextRun& run, const FloatPoint& pt, int h, int from, int to) const 352 { 353 String sanitized = Font::normalizeSpaces(run.characters(), run.length()); 354 QString wholeText = fromRawDataWithoutRef(sanitized); 355 QString selectedText = fromRawDataWithoutRef(sanitized, from, qMin(to - from, wholeText.length() - from)); 356 357 int startX = QFontMetrics(font()).width(wholeText, from, Qt::TextBypassShaping); 358 int width = QFontMetrics(font()).width(selectedText, -1, Qt::TextBypassShaping); 359 360 return FloatRect(pt.x() + startX, pt.y(), width, h); 361 } 362 363 FloatRect Font::selectionRectForComplexText(const TextRun& run, const FloatPoint& pt, int h, int from, int to) const 364 { 365 String sanitized = Font::normalizeSpaces(run.characters(), run.length()); 366 QString string = fromRawDataWithoutRef(sanitized); 367 368 QTextLayout layout(string, font()); 369 QTextLine line = setupLayout(&layout, run); 370 371 float x1 = line.cursorToX(from); 372 float x2 = line.cursorToX(to); 373 if (x2 < x1) 374 qSwap(x1, x2); 375 376 return FloatRect(pt.x() + x1, pt.y(), x2 - x1, h); 377 } 378 379 bool Font::canReturnFallbackFontsForComplexText() 380 { 381 return false; 382 } 383 384 bool Font::canExpandAroundIdeographsInComplexText() 385 { 386 return false; 387 } 388 389 bool Font::primaryFontHasGlyphForCharacter(UChar32) const 390 { 391 notImplemented(); 392 return true; 393 } 394 395 QFont Font::font() const 396 { 397 QFont f = primaryFont()->getQtFont(); 398 if (m_letterSpacing != 0) 399 f.setLetterSpacing(QFont::AbsoluteSpacing, m_letterSpacing); 400 if (m_wordSpacing != 0) 401 f.setWordSpacing(m_wordSpacing); 402 return f; 403 } 404 405 } 406 407