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