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