1 /*
   2  * Copyright (c) 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.javafx.font;
  27 
  28 import java.util.HashMap;
  29 import java.util.Map;
  30 
  31 import com.sun.javafx.geom.Path2D;
  32 import com.sun.javafx.geom.Point2D;
  33 import com.sun.javafx.geom.Shape;
  34 import com.sun.javafx.geom.transform.Affine2D;
  35 import com.sun.javafx.geom.transform.BaseTransform;
  36 import com.sun.javafx.scene.text.GlyphList;
  37 
  38 public abstract class PrismFontStrike<T extends PrismFontFile> implements FontStrike {
  39     private DisposerRecord disposer;
  40     private T fontResource;
  41     private Map<Integer,Glyph> glyphMap = new HashMap<Integer,Glyph>();
  42     private PrismMetrics metrics;
  43     protected boolean drawShapes = false;
  44     private float size;
  45     private BaseTransform transform;
  46     private int aaMode;
  47     private FontStrikeDesc desc;
  48 
  49     protected PrismFontStrike(T fontResource,
  50                               float size, BaseTransform tx, int aaMode,
  51                               FontStrikeDesc desc) {
  52 
  53         this.fontResource = fontResource;
  54         this.size = size;
  55         this.desc = desc;
  56         PrismFontFactory factory = PrismFontFactory.getFontFactory();
  57         boolean lcdEnabled = factory.isLCDTextSupported();
  58         this.aaMode = lcdEnabled ? aaMode : FontResource.AA_GREYSCALE;
  59         if (tx.isTranslateOrIdentity()) {
  60             transform = BaseTransform.IDENTITY_TRANSFORM;
  61         } else {
  62             transform = new Affine2D(tx.getMxx(), tx.getMyx(),
  63                                      tx.getMxy(), tx.getMyy(),
  64                                      0f, 0f);
  65         }
  66     }
  67 
  68     DisposerRecord getDisposer() {
  69         if (disposer == null) {
  70             // Caller will arrange for the disposer to be enqueued.
  71             // Strikes are partialy managed by a GlyphCache such that when it
  72             // wants to free space there it calls back to remove the
  73             // strike from a font's map.
  74             // So we could instead arrange for synchronously freeing the resources
  75             // at that time, in which case a disposer reference queue isn't needed.
  76             // But the disposer is more certain (safer).
  77             disposer = createDisposer(desc);
  78         }
  79         return disposer;
  80     }
  81 
  82     protected abstract DisposerRecord createDisposer(FontStrikeDesc desc);
  83 
  84     public synchronized void clearDesc() {
  85         fontResource.getStrikeMap().remove(desc);
  86         // Native resources are freed via a disposer once we are sure
  87         // all references are cleared. It also ensures we don't leak.
  88     }
  89 
  90     /**
  91      * Returns the notional size of this strike with
  92      * the graphics transform factored out. This is presently
  93      * needed for the J2D pipeline but arguably the strike should
  94      * not even need to keep this around except for needing to
  95      * return  metrics and outlines in user space. The consequence is
  96      * we can't share a strike between 12 pt at scale of 2.0 and 24 pt
  97      * at scale of 1.0
  98      */
  99     public float getSize() {
 100         return size;
 101     }
 102 
 103     public Metrics getMetrics() {
 104         // I don't need native code to do this .. it can be done
 105         // by just reading the hhea table once for the font. This should
 106         // save a JNI call per strike.
 107         // T2K uses the hhea table. Maybe we should use OS/2 metrics
 108         // but www.microsoft.com/typography/otspec/recom.htm#tad has
 109         // a section on GDI baseline to baseline distance which shows it
 110         // to be a wash if the usWin ascent and descent match, and in any
 111         // case, clearly the hhea values are part of the calculation for
 112         // leading.
 113         if (metrics == null) {
 114             metrics = fontResource.getFontMetrics(size);
 115         }
 116         return metrics;
 117     }
 118 
 119     public T getFontResource() {
 120         return fontResource;
 121     }
 122 
 123     public boolean drawAsShapes() {
 124         return drawShapes;
 125     }
 126 
 127     public int getAAMode() {
 128         return aaMode;
 129     }
 130 
 131     public BaseTransform getTransform() {
 132         return transform;
 133     }
 134 
 135     @Override
 136     public int getQuantizedPosition(Point2D point) {
 137         if (aaMode == FontResource.AA_GREYSCALE) {
 138             /* No subpixel position */
 139             point.x = (float)Math.round(point.x);
 140         } else {
 141             /* Prism can produce 3 subpixel positions in the shader */
 142             point.x = (float)Math.round(3.0 * point.x)/ 3.0f;
 143         }
 144         point.y = (float)Math.round(point.y);
 145         return 0;
 146     }
 147 
 148     /**
 149      * Access to individual character advances are frequently needed for layout
 150      * understand that advance may vary for single glyph if ligatures or kerning
 151      * are enabled
 152      * @param ch char
 153      * @return advance of single char
 154      */
 155     public float getCharAdvance(char ch) {
 156         int glyphCode = fontResource.getGlyphMapper().charToGlyph((int)ch);
 157         return fontResource.getAdvance(glyphCode, size);
 158     }
 159 
 160     /* REMIND A map is not the solution ultimately required here */
 161     public Glyph getGlyph(char ch) {
 162         int glyphCode = fontResource.getGlyphMapper().charToGlyph((int)ch);
 163         return getGlyph(glyphCode);
 164     }
 165 
 166     protected abstract Glyph createGlyph(int glyphCode);
 167 
 168     public Glyph getGlyph(int glyphCode) {
 169         Glyph glyph = glyphMap.get(glyphCode);
 170         if (glyph == null) {
 171             glyph = createGlyph(glyphCode);
 172             glyphMap.put(glyphCode, glyph);
 173         }
 174         return glyph;
 175     }
 176 
 177     protected abstract Path2D createGlyphOutline(int glyphCode);
 178 
 179     public Shape getOutline(GlyphList gl, BaseTransform transform) {
 180         Path2D result = new Path2D();
 181         getOutline(gl, transform, result);
 182         return result;
 183     }
 184 
 185     void getOutline(GlyphList gl, BaseTransform transform, Path2D p) {
 186         p.reset();
 187         if (gl == null) {
 188             return;
 189         }
 190         if (transform == null) {
 191             transform = BaseTransform.IDENTITY_TRANSFORM;
 192         }
 193         Affine2D t = new Affine2D();
 194         for (int i = 0; i < gl.getGlyphCount(); i++) {
 195             int glyphCode = gl.getGlyphCode(i);
 196             if (glyphCode != CharToGlyphMapper.INVISIBLE_GLYPH_ID) {
 197                 Shape gp = createGlyphOutline(glyphCode);
 198                 if (gp != null) {
 199                     t.setTransform(transform);
 200                     t.translate(gl.getPosX(i), gl.getPosY(i));
 201                     p.append(gp.getPathIterator(t), false);
 202                 }
 203             }
 204         }
 205     }
 206 
 207     @Override
 208     public boolean equals(Object obj) {
 209         if (obj == null) {
 210             return false;
 211         }
 212         if (!(obj instanceof PrismFontStrike)) {
 213             return false;
 214         }
 215         final PrismFontStrike other = (PrismFontStrike) obj;
 216 
 217         // REMIND: When fonts can be rendered other than as greyscale
 218         // and generally differ in ways other than the size
 219         // we need to update this method.
 220         return this.size == other.size &&
 221                this.transform.getMxx() == other.transform.getMxx() &&
 222                this.transform.getMxy() == other.transform.getMxy() &&
 223                this.transform.getMyx() == other.transform.getMyx() &&
 224                this.transform.getMyy() == other.transform.getMyy() &&
 225                this.fontResource.equals(other.fontResource);
 226     }
 227 
 228     private int hash;
 229     @Override
 230     public int hashCode() {
 231         if (hash != 0) {
 232             return hash;
 233         }
 234         hash = Float.floatToIntBits(size) +
 235                Float.floatToIntBits((float)transform.getMxx()) +
 236                Float.floatToIntBits((float)transform.getMyx()) +
 237                Float.floatToIntBits((float)transform.getMxy()) +
 238                Float.floatToIntBits((float)transform.getMyy());
 239         hash = 71 * hash + fontResource.hashCode();
 240         return hash;
 241     }
 242 
 243     public String toString() {
 244         return "FontStrike: " + super.toString() +
 245                " font resource = " + fontResource +
 246                " size = " + size +
 247                " matrix = " + transform;
 248     }
 249 }