1 /*
   2  * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.prism.impl;
  27 
  28 import com.sun.javafx.font.CharToGlyphMapper;
  29 import com.sun.javafx.font.CompositeGlyphMapper;
  30 import com.sun.javafx.font.FontResource;
  31 import com.sun.javafx.font.FontStrike;
  32 import com.sun.javafx.font.Glyph;
  33 import com.sun.javafx.geom.BaseBounds;
  34 import com.sun.javafx.geom.Rectangle;
  35 import com.sun.javafx.geom.Point2D;
  36 import com.sun.javafx.geom.transform.BaseTransform;
  37 import com.sun.javafx.scene.text.GlyphList;
  38 import com.sun.prism.impl.packrect.RectanglePacker;
  39 import com.sun.prism.Texture;
  40 import com.sun.prism.impl.shape.MaskData;
  41 import com.sun.prism.paint.Color;
  42 
  43 import java.nio.ByteBuffer;
  44 import java.util.HashMap;
  45 import java.util.WeakHashMap;
  46 
  47 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
  48 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
  49 
  50 import com.sun.prism.ResourceFactory;
  51 import com.sun.prism.Texture.WrapMode;
  52 
  53 public class GlyphCache {
  54 
  55     // REMIND: For a less powerful device, the size of this cache
  56     // is likely something we'd want to tune as they may have much less
  57     // VRAM and are less likely to be used for apps that have huge
  58     // text demands.
  59     // 2048 pixels introduced very noticeable pauses when trying
  60     // to free 1/4 of the glyphs, which for spiral text also amounts
  61     // to 1/4 of the strikes.
  62     private static final int WIDTH = PrismSettings.glyphCacheWidth; // in pixels
  63     private static final int HEIGHT = PrismSettings.glyphCacheHeight; // in pixels
  64     private static ByteBuffer emptyMask;
  65 
  66     private final BaseContext context;
  67     private final FontStrike strike;
  68 
  69     // segmented arrays are in blocks of 32 glyphs.
  70     private static final int SEGSHIFT = 5;
  71     private static final int SEGSIZE  = 1 << SEGSHIFT;
  72     HashMap<Integer, GlyphData[]>
  73         glyphDataMap = new HashMap<Integer, GlyphData[]>();
  74 
  75     // Because of SEGSHIFT the 5 high bit in the key to glyphDataMap are unused
  76     // Using them for subpixel
  77     private static final int SUBPIXEL_SHIFT = 27;
  78 
  79     private RectanglePacker packer;
  80 
  81     private boolean isLCDCache;
  82 
  83     /* Share a RectanglePacker and its associated texture cache
  84      * for all uses on a particular screen.
  85      */
  86     static WeakHashMap<BaseContext, RectanglePacker> greyPackerMap =
  87         new WeakHashMap<BaseContext, RectanglePacker>();
  88 
  89     static WeakHashMap<BaseContext, RectanglePacker> lcdPackerMap =
  90         new WeakHashMap<BaseContext, RectanglePacker>();
  91 
  92     public GlyphCache(BaseContext context, FontStrike strike) {
  93         this.context = context;
  94         this.strike = strike;
  95         //numGlyphs = strike.getNumGlyphs();
  96         //int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE;
  97         //this.glyphs = new GlyphData[numSegments][];
  98         isLCDCache = strike.getAAMode() == FontResource.AA_LCD;
  99         WeakHashMap<BaseContext, RectanglePacker>
 100             packerMap = isLCDCache ? lcdPackerMap : greyPackerMap;
 101         packer = packerMap.get(context);
 102         if (packer == null) {
 103             ResourceFactory factory = context.getResourceFactory();
 104             Texture tex = factory.createMaskTexture(WIDTH, HEIGHT,
 105                                                     WrapMode.CLAMP_NOT_NEEDED);
 106             tex.contentsUseful();
 107             tex.makePermanent();
 108             if (!isLCDCache) {
 109                 factory.setGlyphTexture(tex);
 110             }
 111             tex.setLinearFiltering(false);
 112             packer = new RectanglePacker(tex, WIDTH, HEIGHT);
 113             packerMap.put(context, packer);
 114         }
 115     }
 116 
 117     public void render(BaseContext ctx, GlyphList gl, float x, float y,
 118                        int start, int end, Color rangeColor, Color textColor,
 119                        BaseTransform xform, BaseBounds clip) {
 120 
 121         int dstw, dsth;
 122         if (isLCDCache) {
 123             dstw = ctx.getLCDBuffer().getPhysicalWidth();
 124             dsth = ctx.getLCDBuffer().getPhysicalHeight();
 125         } else {
 126             dstw = 1;
 127             dsth = 1;
 128         }
 129         Texture tex = getBackingStore();
 130         VertexBuffer vb = ctx.getVertexBuffer();
 131 
 132         int len = gl.getGlyphCount();
 133         Color currentColor = null;
 134         Point2D pt = new Point2D();
 135 
 136         for (int gi = 0; gi < len; gi++) {
 137             int gc = gl.getGlyphCode(gi);
 138 
 139             // If we have a supplementary character, then a special
 140             // glyph is inserted in the list, which is one we skip
 141             // over for rendering. It has no advance.
 142             if ((gc & CompositeGlyphMapper.GLYPHMASK) == CharToGlyphMapper.INVISIBLE_GLYPH_ID) {
 143                 continue;
 144             }
 145             pt.setLocation(x + gl.getPosX(gi), y + gl.getPosY(gi));
 146             int subPixel = strike.getQuantizedPosition(pt);
 147             GlyphData data = getCachedGlyph(gc, subPixel);
 148             if (data != null) {
 149                 if (clip != null) {
 150                     // Always check clipping using user space.
 151                     if (x + gl.getPosX(gi) > clip.getMaxX()) break;
 152                     if (x + gl.getPosX(gi + 1) < clip.getMinX()) continue;
 153                 }
 154                 /* Will not render selected text for complex
 155                  * paints such as gradient.
 156                  */
 157                 if (rangeColor != null && textColor != null) {
 158                     int offset = gl.getCharOffset(gi);
 159                     if (start <= offset && offset < end) {
 160                         if (rangeColor != currentColor) {
 161                             vb.setPerVertexColor(rangeColor, 1.0f);
 162                             currentColor = rangeColor;
 163                         }
 164                     } else {
 165                         if (textColor != currentColor) {
 166                             vb.setPerVertexColor(textColor, 1.0f);
 167                             currentColor = textColor;
 168                         }
 169                     }
 170                 }
 171                 xform.transform(pt, pt);
 172                 addDataToQuad(data, vb, tex, pt.x, pt.y, dstw, dsth);
 173             }
 174         }
 175     }
 176 
 177     private void addDataToQuad(GlyphData data, VertexBuffer vb,
 178                                Texture tex, float x, float y,
 179                                float dstw, float dsth) {
 180         // We are sampling texture using nearest point sampling, for clear
 181         // text. As a consequence of nearest point sampling, graphics artifacts
 182         // may occur when sampling close to texel boundaries.
 183         // By rounding the glyph placement we can avoid the texture boundaries.
 184         // REMIND: If we start using linear sampling then we should remove
 185         // rounding.
 186         y = Math.round(y);
 187         Rectangle rect = data.getRect();
 188         if (rect == null) {
 189             // Glyph with no visual representation (whitespace)
 190             return;
 191         }
 192         int border = data.getBlankBoundary();
 193         float gw = rect.width - (border * 2);
 194         float gh = rect.height - (border * 2);
 195         float dx1 = data.getOriginX() + x;
 196         float dy1 = data.getOriginY() + y;
 197         float dx2;
 198         float dy2 = dy1 + gh;
 199         float tw = tex.getPhysicalWidth();
 200         float th = tex.getPhysicalHeight();
 201         float tx1 = (rect.x + border) / tw;
 202         float ty1 = (rect.y + border) / th;
 203         float tx2 = tx1 + (gw / tw);
 204         float ty2 = ty1 + (gh / th);
 205         if (isLCDCache) {
 206             dx1 = Math.round(dx1 * 3.0f) / 3.0f;
 207             dx2 = dx1 + gw / 3.0f;
 208             float t2x1 = dx1 / dstw;
 209             float t2x2 = dx2 / dstw;
 210             float t2y1 = dy1 / dsth;
 211             float t2y2 = dy2 / dsth;
 212             vb.addQuad(dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2, t2x1, t2y1, t2x2, t2y2);
 213         } else {
 214             dx1 = Math.round(dx1);
 215             dx2 = dx1 + gw;
 216             if (context.isSuperShaderEnabled()) {
 217                 vb.addSuperQuad(dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2, true);
 218             } else {
 219                 vb.addQuad(dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2);
 220             }
 221         }
 222     }
 223 
 224     public Texture getBackingStore() {
 225         return packer.getBackingStore();
 226     }
 227 
 228     public void clear() {
 229         glyphDataMap.clear();
 230     }
 231 
 232     private void clearAll() {
 233         // flush any pending vertices that may depend on the current state
 234         // of the glyph cache texture.
 235         context.flushVertexBuffer();
 236         context.clearGlyphCaches();
 237         packer.clear();
 238     }
 239 
 240     private GlyphData getCachedGlyph(int glyphCode, int subPixel) {
 241         int segIndex = glyphCode >> SEGSHIFT;
 242         int subIndex = glyphCode % SEGSIZE;
 243         segIndex |= (subPixel << SUBPIXEL_SHIFT);
 244         GlyphData[] segment = glyphDataMap.get(segIndex);
 245         if (segment != null) {
 246             if (segment[subIndex] != null) {
 247                 return segment[subIndex];
 248             }
 249         } else {
 250             segment = new GlyphData[SEGSIZE];
 251             glyphDataMap.put(segIndex, segment);
 252         }
 253 
 254         // Render the glyph and insert it in the cache
 255         GlyphData data = null;
 256         Glyph glyph = strike.getGlyph(glyphCode);
 257         if (glyph != null) {
 258             byte[] glyphImage = glyph.getPixelData(subPixel);
 259             if (glyphImage == null || glyphImage.length == 0) {
 260                 data = new GlyphData(0, 0, 0,
 261                                      glyph.getPixelXAdvance(),
 262                                      glyph.getPixelYAdvance(),
 263                                      null);
 264             } else {
 265                 // Rasterize the glyph
 266                 // NOTE : if the MaskData can be stored back directly
 267                 // in the glyph, even as an opaque type, it should save
 268                 // repeated work next time the glyph is used.
 269                 MaskData maskData = MaskData.create(glyphImage,
 270                                                     glyph.getOriginX(),
 271                                                     glyph.getOriginY(),
 272                                                     glyph.getWidth(),
 273                                                     glyph.getHeight());
 274 
 275                 // Make room for the rectangle on the backing store
 276                 int border = 1;
 277                 int rectW = maskData.getWidth()  + (2 * border);
 278                 int rectH = maskData.getHeight() + (2 * border);
 279                 int originX = maskData.getOriginX();
 280                 int originY = maskData.getOriginY();
 281                 Rectangle rect = new Rectangle(0, 0, rectW, rectH);
 282                 data = new GlyphData(originX, originY, border,
 283                                      glyph.getPixelXAdvance(),
 284                                      glyph.getPixelYAdvance(),
 285                                      rect);
 286 
 287                 if (!packer.add(rect)) {
 288                     if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.renderIncrementCounter("Font Glyph Cache Cleared");
 289                     // If add fails,clear up the cache. Try add again.
 290                     clearAll();
 291                     packer.add(rect);
 292                 }
 293 
 294                 // We always pass skipFlush=true to backingStore.update()
 295                 // since we are in control of the contents of the backingStore
 296                 // texture and explicitly flush the vertex buffer only when
 297                 // it is truly needed.
 298                 boolean skipFlush = true;
 299 
 300                 // Upload the an empty byte array to ensure the boundary
 301                 // area is filled with zeros. Note that the rectangle
 302                 // is already padded on each edge.
 303                 Texture backingStore = getBackingStore();
 304                 int emw = rect.width;
 305                 int emh = rect.height;
 306                 int bpp = backingStore.getPixelFormat().getBytesPerPixelUnit();
 307                 int stride = emw * bpp;
 308                 int size = stride * emh;
 309                 if (emptyMask == null || size > emptyMask.capacity()) {
 310                     emptyMask = BufferUtil.newByteBuffer(size);
 311                 }
 312                 // try/catch is a precaution against not fitting into the store.
 313                 try {
 314                     backingStore.update(emptyMask,
 315                                         backingStore.getPixelFormat(),
 316                                         rect.x, rect.y,
 317                                         0, 0, emw, emh, stride,
 318                                         skipFlush);
 319                 } catch (Exception e) {
 320                     e.printStackTrace();
 321                     return null;
 322                 }
 323                 // Upload the glyph
 324                 maskData.uploadToTexture(backingStore,
 325                                          border + rect.x,
 326                                          border + rect.y,
 327                                          skipFlush);
 328 
 329             }
 330             segment[subIndex] = data;
 331         }
 332 
 333         return data;
 334     }
 335 
 336     static class GlyphData {
 337         // The following must be defined and used VERY precisely. This is
 338         // the offset from the upper-left corner of this rectangle (Java
 339         // 2D coordinate system) at which the string must be rasterized in
 340         // order to fit within the rectangle -- the leftmost point of the
 341         // baseline.
 342         private final int originX;
 343         private final int originY;
 344 
 345         // The blank boundary around the real image of the glyph on
 346         // the backing store
 347         private final int blankBoundary;
 348 
 349         // The advance of this glyph
 350         private final float xAdvance, yAdvance;
 351 
 352         // The rectangle on the backing store corresponding to this glyph
 353         private final Rectangle rect;
 354 
 355         GlyphData(int originX, int originY, int blankBoundary,
 356                   float xAdvance, float yAdvance, Rectangle rect)
 357         {
 358             this.originX = originX;
 359             this.originY = originY;
 360             this.blankBoundary = blankBoundary;
 361             this.xAdvance = xAdvance;
 362             this.yAdvance = yAdvance;
 363             this.rect = rect;
 364         }
 365 
 366         int getOriginX() {
 367             return originX;
 368         }
 369 
 370         int getOriginY() {
 371             return originY;
 372         }
 373 
 374         int getBlankBoundary() {
 375             return blankBoundary;
 376         }
 377 
 378         float getXAdvance() {
 379             return xAdvance;
 380         }
 381 
 382         float getYAdvance() {
 383             return yAdvance;
 384         }
 385 
 386         Rectangle getRect() {
 387             return rect;
 388         }
 389     }
 390 }