gfx/skia/trunk/src/gpu/GrDistanceFieldTextContext.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rwxr-xr-x

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /*
michael@0 2 * Copyright 2013 Google Inc.
michael@0 3 *
michael@0 4 * Use of this source code is governed by a BSD-style license that can be
michael@0 5 * found in the LICENSE file.
michael@0 6 */
michael@0 7
michael@0 8 #include "GrDistanceFieldTextContext.h"
michael@0 9 #include "GrAtlas.h"
michael@0 10 #include "GrDrawTarget.h"
michael@0 11 #include "GrDrawTargetCaps.h"
michael@0 12 #include "GrFontScaler.h"
michael@0 13 #include "SkGlyphCache.h"
michael@0 14 #include "GrIndexBuffer.h"
michael@0 15 #include "GrTextStrike.h"
michael@0 16 #include "GrTextStrike_impl.h"
michael@0 17 #include "SkDraw.h"
michael@0 18 #include "SkGpuDevice.h"
michael@0 19 #include "SkPath.h"
michael@0 20 #include "SkRTConf.h"
michael@0 21 #include "SkStrokeRec.h"
michael@0 22 #include "effects/GrDistanceFieldTextureEffect.h"
michael@0 23
michael@0 24 static const int kGlyphCoordsAttributeIndex = 1;
michael@0 25
michael@0 26 static const int kBaseDFFontSize = 32;
michael@0 27
michael@0 28 SK_CONF_DECLARE(bool, c_DumpFontCache, "gpu.dumpFontCache", false,
michael@0 29 "Dump the contents of the font cache before every purge.");
michael@0 30
michael@0 31 #if SK_FORCE_DISTANCEFIELD_FONTS
michael@0 32 static const bool kForceDistanceFieldFonts = true;
michael@0 33 #else
michael@0 34 static const bool kForceDistanceFieldFonts = false;
michael@0 35 #endif
michael@0 36
michael@0 37 GrDistanceFieldTextContext::GrDistanceFieldTextContext(GrContext* context,
michael@0 38 const SkDeviceProperties& properties)
michael@0 39 : GrTextContext(context, properties) {
michael@0 40 fStrike = NULL;
michael@0 41
michael@0 42 fCurrTexture = NULL;
michael@0 43 fCurrVertex = 0;
michael@0 44
michael@0 45 fVertices = NULL;
michael@0 46 fMaxVertices = 0;
michael@0 47 }
michael@0 48
michael@0 49 GrDistanceFieldTextContext::~GrDistanceFieldTextContext() {
michael@0 50 this->flushGlyphs();
michael@0 51 }
michael@0 52
michael@0 53 bool GrDistanceFieldTextContext::canDraw(const SkPaint& paint) {
michael@0 54 return (kForceDistanceFieldFonts || paint.isDistanceFieldTextTEMP()) &&
michael@0 55 !paint.getRasterizer() && !paint.getMaskFilter() &&
michael@0 56 paint.getStyle() == SkPaint::kFill_Style &&
michael@0 57 fContext->getTextTarget()->caps()->shaderDerivativeSupport() &&
michael@0 58 !SkDraw::ShouldDrawTextAsPaths(paint, fContext->getMatrix());
michael@0 59 }
michael@0 60
michael@0 61 static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) {
michael@0 62 unsigned r = SkColorGetR(c);
michael@0 63 unsigned g = SkColorGetG(c);
michael@0 64 unsigned b = SkColorGetB(c);
michael@0 65 return GrColorPackRGBA(r, g, b, 0xff);
michael@0 66 }
michael@0 67
michael@0 68 void GrDistanceFieldTextContext::flushGlyphs() {
michael@0 69 if (NULL == fDrawTarget) {
michael@0 70 return;
michael@0 71 }
michael@0 72
michael@0 73 GrDrawState* drawState = fDrawTarget->drawState();
michael@0 74 GrDrawState::AutoRestoreEffects are(drawState);
michael@0 75 drawState->setFromPaint(fPaint, fContext->getMatrix(), fContext->getRenderTarget());
michael@0 76
michael@0 77 if (fCurrVertex > 0) {
michael@0 78 // setup our sampler state for our text texture/atlas
michael@0 79 SkASSERT(GrIsALIGN4(fCurrVertex));
michael@0 80 SkASSERT(fCurrTexture);
michael@0 81 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
michael@0 82
michael@0 83 // This effect could be stored with one of the cache objects (atlas?)
michael@0 84 SkISize size = fStrike->getAtlasSize();
michael@0 85 drawState->addCoverageEffect(
michael@0 86 GrDistanceFieldTextureEffect::Create(fCurrTexture, params, size),
michael@0 87 kGlyphCoordsAttributeIndex)->unref();
michael@0 88
michael@0 89 if (!GrPixelConfigIsAlphaOnly(fCurrTexture->config())) {
michael@0 90 if (kOne_GrBlendCoeff != fPaint.getSrcBlendCoeff() ||
michael@0 91 kISA_GrBlendCoeff != fPaint.getDstBlendCoeff() ||
michael@0 92 fPaint.numColorStages()) {
michael@0 93 GrPrintf("LCD Text will not draw correctly.\n");
michael@0 94 }
michael@0 95 // We don't use the GrPaint's color in this case because it's been premultiplied by
michael@0 96 // alpha. Instead we feed in a non-premultiplied color, and multiply its alpha by
michael@0 97 // the mask texture color. The end result is that we get
michael@0 98 // mask*paintAlpha*paintColor + (1-mask*paintAlpha)*dstColor
michael@0 99 int a = SkColorGetA(fSkPaint.getColor());
michael@0 100 // paintAlpha
michael@0 101 drawState->setColor(SkColorSetARGB(a, a, a, a));
michael@0 102 // paintColor
michael@0 103 drawState->setBlendConstant(skcolor_to_grcolor_nopremultiply(fSkPaint.getColor()));
michael@0 104 drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff);
michael@0 105 } else {
michael@0 106 // set back to normal in case we took LCD path previously.
michael@0 107 drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff());
michael@0 108 drawState->setColor(fPaint.getColor());
michael@0 109 }
michael@0 110
michael@0 111 int nGlyphs = fCurrVertex / 4;
michael@0 112 fDrawTarget->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
michael@0 113 fDrawTarget->drawIndexedInstances(kTriangles_GrPrimitiveType,
michael@0 114 nGlyphs,
michael@0 115 4, 6);
michael@0 116 fDrawTarget->resetVertexSource();
michael@0 117 fVertices = NULL;
michael@0 118 fMaxVertices = 0;
michael@0 119 fCurrVertex = 0;
michael@0 120 SkSafeSetNull(fCurrTexture);
michael@0 121 }
michael@0 122 }
michael@0 123
michael@0 124 namespace {
michael@0 125
michael@0 126 // position + texture coord
michael@0 127 extern const GrVertexAttrib gTextVertexAttribs[] = {
michael@0 128 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
michael@0 129 {kVec2f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding}
michael@0 130 };
michael@0 131
michael@0 132 };
michael@0 133
michael@0 134 void GrDistanceFieldTextContext::drawPackedGlyph(GrGlyph::PackedID packed,
michael@0 135 GrFixed vx, GrFixed vy,
michael@0 136 GrFontScaler* scaler) {
michael@0 137 if (NULL == fDrawTarget) {
michael@0 138 return;
michael@0 139 }
michael@0 140 if (NULL == fStrike) {
michael@0 141 fStrike = fContext->getFontCache()->getStrike(scaler, true);
michael@0 142 }
michael@0 143
michael@0 144 GrGlyph* glyph = fStrike->getGlyph(packed, scaler);
michael@0 145 if (NULL == glyph || glyph->fBounds.isEmpty()) {
michael@0 146 return;
michael@0 147 }
michael@0 148
michael@0 149 SkScalar sx = SkFixedToScalar(vx);
michael@0 150 SkScalar sy = SkFixedToScalar(vy);
michael@0 151 /*
michael@0 152 // not valid, need to find a different solution for this
michael@0 153 vx += SkIntToFixed(glyph->fBounds.fLeft);
michael@0 154 vy += SkIntToFixed(glyph->fBounds.fTop);
michael@0 155
michael@0 156 // keep them as ints until we've done the clip-test
michael@0 157 GrFixed width = glyph->fBounds.width();
michael@0 158 GrFixed height = glyph->fBounds.height();
michael@0 159
michael@0 160 // check if we clipped out
michael@0 161 if (true || NULL == glyph->fPlot) {
michael@0 162 int x = vx >> 16;
michael@0 163 int y = vy >> 16;
michael@0 164 if (fClipRect.quickReject(x, y, x + width, y + height)) {
michael@0 165 // SkCLZ(3); // so we can set a break-point in the debugger
michael@0 166 return;
michael@0 167 }
michael@0 168 }
michael@0 169 */
michael@0 170 if (NULL == glyph->fPlot) {
michael@0 171 if (fStrike->addGlyphToAtlas(glyph, scaler)) {
michael@0 172 goto HAS_ATLAS;
michael@0 173 }
michael@0 174
michael@0 175 // try to clear out an unused plot before we flush
michael@0 176 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
michael@0 177 fStrike->addGlyphToAtlas(glyph, scaler)) {
michael@0 178 goto HAS_ATLAS;
michael@0 179 }
michael@0 180
michael@0 181 if (c_DumpFontCache) {
michael@0 182 #ifdef SK_DEVELOPER
michael@0 183 fContext->getFontCache()->dump();
michael@0 184 #endif
michael@0 185 }
michael@0 186
michael@0 187 // before we purge the cache, we must flush any accumulated draws
michael@0 188 this->flushGlyphs();
michael@0 189 fContext->flush();
michael@0 190
michael@0 191 // we should have an unused plot now
michael@0 192 if (fContext->getFontCache()->freeUnusedPlot(fStrike) &&
michael@0 193 fStrike->addGlyphToAtlas(glyph, scaler)) {
michael@0 194 goto HAS_ATLAS;
michael@0 195 }
michael@0 196
michael@0 197 if (NULL == glyph->fPath) {
michael@0 198 SkPath* path = SkNEW(SkPath);
michael@0 199 if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
michael@0 200 // flag the glyph as being dead?
michael@0 201 delete path;
michael@0 202 return;
michael@0 203 }
michael@0 204 glyph->fPath = path;
michael@0 205 }
michael@0 206
michael@0 207 GrContext::AutoMatrix am;
michael@0 208 SkMatrix translate;
michael@0 209 translate.setTranslate(sx, sy);
michael@0 210 GrPaint tmpPaint(fPaint);
michael@0 211 am.setPreConcat(fContext, translate, &tmpPaint);
michael@0 212 SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
michael@0 213 fContext->drawPath(tmpPaint, *glyph->fPath, stroke);
michael@0 214 return;
michael@0 215 }
michael@0 216
michael@0 217 HAS_ATLAS:
michael@0 218 SkASSERT(glyph->fPlot);
michael@0 219 GrDrawTarget::DrawToken drawToken = fDrawTarget->getCurrentDrawToken();
michael@0 220 glyph->fPlot->setDrawToken(drawToken);
michael@0 221
michael@0 222 GrTexture* texture = glyph->fPlot->texture();
michael@0 223 SkASSERT(texture);
michael@0 224
michael@0 225 if (fCurrTexture != texture || fCurrVertex + 4 > fMaxVertices) {
michael@0 226 this->flushGlyphs();
michael@0 227 fCurrTexture = texture;
michael@0 228 fCurrTexture->ref();
michael@0 229 }
michael@0 230
michael@0 231 if (NULL == fVertices) {
michael@0 232 // If we need to reserve vertices allow the draw target to suggest
michael@0 233 // a number of verts to reserve and whether to perform a flush.
michael@0 234 fMaxVertices = kMinRequestedVerts;
michael@0 235 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
michael@0 236 SK_ARRAY_COUNT(gTextVertexAttribs));
michael@0 237 bool flush = fDrawTarget->geometryHints(&fMaxVertices, NULL);
michael@0 238 if (flush) {
michael@0 239 this->flushGlyphs();
michael@0 240 fContext->flush();
michael@0 241 fDrawTarget->drawState()->setVertexAttribs<gTextVertexAttribs>(
michael@0 242 SK_ARRAY_COUNT(gTextVertexAttribs));
michael@0 243 }
michael@0 244 fMaxVertices = kDefaultRequestedVerts;
michael@0 245 // ignore return, no point in flushing again.
michael@0 246 fDrawTarget->geometryHints(&fMaxVertices, NULL);
michael@0 247
michael@0 248 int maxQuadVertices = 4 * fContext->getQuadIndexBuffer()->maxQuads();
michael@0 249 if (fMaxVertices < kMinRequestedVerts) {
michael@0 250 fMaxVertices = kDefaultRequestedVerts;
michael@0 251 } else if (fMaxVertices > maxQuadVertices) {
michael@0 252 // don't exceed the limit of the index buffer
michael@0 253 fMaxVertices = maxQuadVertices;
michael@0 254 }
michael@0 255 bool success = fDrawTarget->reserveVertexAndIndexSpace(fMaxVertices,
michael@0 256 0,
michael@0 257 GrTCast<void**>(&fVertices),
michael@0 258 NULL);
michael@0 259 GrAlwaysAssert(success);
michael@0 260 SkASSERT(2*sizeof(GrPoint) == fDrawTarget->getDrawState().getVertexSize());
michael@0 261 }
michael@0 262
michael@0 263 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft);
michael@0 264 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop);
michael@0 265 SkScalar width = SkIntToScalar(glyph->fBounds.width());
michael@0 266 SkScalar height = SkIntToScalar(glyph->fBounds.height());
michael@0 267
michael@0 268 SkScalar scale = fTextRatio;
michael@0 269 dx *= scale;
michael@0 270 dy *= scale;
michael@0 271 sx += dx;
michael@0 272 sy += dy;
michael@0 273 width *= scale;
michael@0 274 height *= scale;
michael@0 275
michael@0 276 GrFixed tx = SkIntToFixed(glyph->fAtlasLocation.fX);
michael@0 277 GrFixed ty = SkIntToFixed(glyph->fAtlasLocation.fY);
michael@0 278 GrFixed tw = SkIntToFixed(glyph->fBounds.width());
michael@0 279 GrFixed th = SkIntToFixed(glyph->fBounds.height());
michael@0 280
michael@0 281 static const size_t kVertexSize = 2 * sizeof(SkPoint);
michael@0 282 fVertices[2*fCurrVertex].setRectFan(sx,
michael@0 283 sy,
michael@0 284 sx + width,
michael@0 285 sy + height,
michael@0 286 kVertexSize);
michael@0 287 fVertices[2*fCurrVertex+1].setRectFan(SkFixedToFloat(texture->normalizeFixedX(tx)),
michael@0 288 SkFixedToFloat(texture->normalizeFixedY(ty)),
michael@0 289 SkFixedToFloat(texture->normalizeFixedX(tx + tw)),
michael@0 290 SkFixedToFloat(texture->normalizeFixedY(ty + th)),
michael@0 291 kVertexSize);
michael@0 292 fCurrVertex += 4;
michael@0 293 }
michael@0 294
michael@0 295 inline void GrDistanceFieldTextContext::init(const GrPaint& paint, const SkPaint& skPaint) {
michael@0 296 GrTextContext::init(paint, skPaint);
michael@0 297
michael@0 298 fStrike = NULL;
michael@0 299
michael@0 300 fCurrTexture = NULL;
michael@0 301 fCurrVertex = 0;
michael@0 302
michael@0 303 fVertices = NULL;
michael@0 304 fMaxVertices = 0;
michael@0 305
michael@0 306 fTextRatio = fSkPaint.getTextSize()/kBaseDFFontSize;
michael@0 307
michael@0 308 fSkPaint.setTextSize(SkIntToScalar(kBaseDFFontSize));
michael@0 309 fSkPaint.setLCDRenderText(false);
michael@0 310 fSkPaint.setAutohinted(false);
michael@0 311 fSkPaint.setSubpixelText(true);
michael@0 312 }
michael@0 313
michael@0 314 inline void GrDistanceFieldTextContext::finish() {
michael@0 315 flushGlyphs();
michael@0 316
michael@0 317 GrTextContext::finish();
michael@0 318 }
michael@0 319
michael@0 320 void GrDistanceFieldTextContext::drawText(const GrPaint& paint, const SkPaint& skPaint,
michael@0 321 const char text[], size_t byteLength,
michael@0 322 SkScalar x, SkScalar y) {
michael@0 323 SkASSERT(byteLength == 0 || text != NULL);
michael@0 324
michael@0 325 // nothing to draw or can't draw
michael@0 326 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/
michael@0 327 || fSkPaint.getRasterizer()) {
michael@0 328 return;
michael@0 329 }
michael@0 330
michael@0 331 this->init(paint, skPaint);
michael@0 332
michael@0 333 SkScalar sizeRatio = fTextRatio;
michael@0 334
michael@0 335 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
michael@0 336
michael@0 337 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL);
michael@0 338 SkGlyphCache* cache = autoCache.getCache();
michael@0 339 GrFontScaler* fontScaler = GetGrFontScaler(cache);
michael@0 340
michael@0 341 // need to measure first
michael@0 342 // TODO - generate positions and pre-load cache as well?
michael@0 343 const char* stop = text + byteLength;
michael@0 344 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
michael@0 345 SkFixed stopX = 0;
michael@0 346 SkFixed stopY = 0;
michael@0 347
michael@0 348 const char* textPtr = text;
michael@0 349 while (textPtr < stop) {
michael@0 350 // don't need x, y here, since all subpixel variants will have the
michael@0 351 // same advance
michael@0 352 const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0);
michael@0 353
michael@0 354 stopX += glyph.fAdvanceX;
michael@0 355 stopY += glyph.fAdvanceY;
michael@0 356 }
michael@0 357 SkASSERT(textPtr == stop);
michael@0 358
michael@0 359 SkScalar alignX = SkFixedToScalar(stopX)*sizeRatio;
michael@0 360 SkScalar alignY = SkFixedToScalar(stopY)*sizeRatio;
michael@0 361
michael@0 362 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
michael@0 363 alignX = SkScalarHalf(alignX);
michael@0 364 alignY = SkScalarHalf(alignY);
michael@0 365 }
michael@0 366
michael@0 367 x -= alignX;
michael@0 368 y -= alignY;
michael@0 369 }
michael@0 370
michael@0 371 SkFixed fx = SkScalarToFixed(x) + SK_FixedHalf;
michael@0 372 SkFixed fy = SkScalarToFixed(y) + SK_FixedHalf;
michael@0 373 SkFixed fixedScale = SkScalarToFixed(sizeRatio);
michael@0 374 while (text < stop) {
michael@0 375 const SkGlyph& glyph = glyphCacheProc(cache, &text, fx, fy);
michael@0 376
michael@0 377 if (glyph.fWidth) {
michael@0 378 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
michael@0 379 glyph.getSubXFixed(),
michael@0 380 glyph.getSubYFixed()),
michael@0 381 SkFixedFloorToFixed(fx),
michael@0 382 SkFixedFloorToFixed(fy),
michael@0 383 fontScaler);
michael@0 384 }
michael@0 385
michael@0 386 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedScale);
michael@0 387 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedScale);
michael@0 388 }
michael@0 389
michael@0 390 this->finish();
michael@0 391 }
michael@0 392
michael@0 393 void GrDistanceFieldTextContext::drawPosText(const GrPaint& paint, const SkPaint& skPaint,
michael@0 394 const char text[], size_t byteLength,
michael@0 395 const SkScalar pos[], SkScalar constY,
michael@0 396 int scalarsPerPosition) {
michael@0 397
michael@0 398 SkASSERT(byteLength == 0 || text != NULL);
michael@0 399 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
michael@0 400
michael@0 401 // nothing to draw
michael@0 402 if (text == NULL || byteLength == 0 /* no raster clip? || fRC->isEmpty()*/) {
michael@0 403 return;
michael@0 404 }
michael@0 405
michael@0 406 this->init(paint, skPaint);
michael@0 407
michael@0 408 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
michael@0 409
michael@0 410 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL);
michael@0 411 SkGlyphCache* cache = autoCache.getCache();
michael@0 412 GrFontScaler* fontScaler = GetGrFontScaler(cache);
michael@0 413
michael@0 414 const char* stop = text + byteLength;
michael@0 415
michael@0 416 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
michael@0 417 while (text < stop) {
michael@0 418 // the last 2 parameters are ignored
michael@0 419 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
michael@0 420
michael@0 421 if (glyph.fWidth) {
michael@0 422 SkScalar x = pos[0];
michael@0 423 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
michael@0 424
michael@0 425 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
michael@0 426 glyph.getSubXFixed(),
michael@0 427 glyph.getSubYFixed()),
michael@0 428 SkScalarToFixed(x) + SK_FixedHalf, //d1g.fHalfSampleX,
michael@0 429 SkScalarToFixed(y) + SK_FixedHalf, //d1g.fHalfSampleY,
michael@0 430 fontScaler);
michael@0 431 }
michael@0 432 pos += scalarsPerPosition;
michael@0 433 }
michael@0 434 } else {
michael@0 435 int alignShift = SkPaint::kCenter_Align == fSkPaint.getTextAlign() ? 1 : 0;
michael@0 436 while (text < stop) {
michael@0 437 // the last 2 parameters are ignored
michael@0 438 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
michael@0 439
michael@0 440 if (glyph.fWidth) {
michael@0 441 SkScalar x = pos[0];
michael@0 442 SkScalar y = scalarsPerPosition == 1 ? constY : pos[1];
michael@0 443
michael@0 444 this->drawPackedGlyph(GrGlyph::Pack(glyph.getGlyphID(),
michael@0 445 glyph.getSubXFixed(),
michael@0 446 glyph.getSubYFixed()),
michael@0 447 SkScalarToFixed(x) - (glyph.fAdvanceX >> alignShift)
michael@0 448 + SK_FixedHalf, //d1g.fHalfSampleX,
michael@0 449 SkScalarToFixed(y) - (glyph.fAdvanceY >> alignShift)
michael@0 450 + SK_FixedHalf, //d1g.fHalfSampleY,
michael@0 451 fontScaler);
michael@0 452 }
michael@0 453 pos += scalarsPerPosition;
michael@0 454 }
michael@0 455 }
michael@0 456
michael@0 457 this->finish();
michael@0 458 }

mercurial