/* * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javafx.font; import java.util.HashMap; import java.util.Map; import com.sun.javafx.geom.Path2D; import com.sun.javafx.geom.Point2D; import com.sun.javafx.geom.Shape; import com.sun.javafx.geom.transform.Affine2D; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.scene.text.GlyphList; public abstract class PrismFontStrike implements FontStrike { private DisposerRecord disposer; private T fontResource; private Map glyphMap = new HashMap(); private PrismMetrics metrics; protected boolean drawShapes = false; private float size; private BaseTransform transform; private int aaMode; private FontStrikeDesc desc; protected PrismFontStrike(T fontResource, float size, BaseTransform tx, int aaMode, FontStrikeDesc desc) { this.fontResource = fontResource; this.size = size; this.desc = desc; if (tx.isTranslateOrIdentity()) { transform = BaseTransform.IDENTITY_TRANSFORM; } else { transform = new Affine2D(tx.getMxx(), tx.getMyx(), tx.getMxy(), tx.getMyy(), 0f, 0f); } this.aaMode = calculateAAMode(aaMode); } protected int calculateAAMode(int aaMode) { if (aaMode == FontResource.AA_GREYSCALE) return aaMode; PrismFontFactory factory = PrismFontFactory.getFontFactory(); return factory.isLCDTextSupported() ? aaMode : FontResource.AA_GREYSCALE; } DisposerRecord getDisposer() { if (disposer == null) { // Caller will arrange for the disposer to be enqueued. // Strikes are partialy managed by a GlyphCache such that when it // wants to free space there it calls back to remove the // strike from a font's map. // So we could instead arrange for synchronously freeing the resources // at that time, in which case a disposer reference queue isn't needed. // But the disposer is more certain (safer). disposer = createDisposer(desc); } return disposer; } protected abstract DisposerRecord createDisposer(FontStrikeDesc desc); public synchronized void clearDesc() { fontResource.getStrikeMap().remove(desc); // Native resources are freed via a disposer once we are sure // all references are cleared. It also ensures we don't leak. // So this is approach is not used right now, in favour of a disposer. //T2KFontFile.freePointer(pScalerContext); //pScalerContext = 0L; } /** * Returns the notional size of this strike with * the graphics transform factored out. This is presently * needed for the J2D pipeline but arguably the strike should * not even need to keep this around except for needing to * return metrics and outlines in user space. The consequence is * we can't share a strike between 12 pt at scale of 2.0 and 24 pt * at scale of 1.0 */ public float getSize() { return size; } public Metrics getMetrics() { // I don't need native code to do this .. it can be done // by just reading the hhea table once for the font. This should // save a JNI call per strike. // T2K uses the hhea table. Maybe we should use OS/2 metrics // but www.microsoft.com/typography/otspec/recom.htm#tad has // a section on GDI baseline to baseline distance which shows it // to be a wash if the usWin ascent and descent match, and in any // case, clearly the hhea values are part of the calculation for // leading. if (metrics == null) { metrics = fontResource.getFontMetrics(size); } return metrics; } public T getFontResource() { return fontResource; } public boolean drawAsShapes() { return drawShapes; } public int getAAMode() { return aaMode; } public BaseTransform getTransform() { return transform; } @Override public int getQuantizedPosition(Point2D point) { if (aaMode == FontResource.AA_GREYSCALE) { /* No subpixel position */ point.x = (float)Math.round(point.x); } else { /* Prism can produce 3 subpixel positions in the shader */ point.x = (float)Math.round(3.0 * point.x)/ 3.0f; } point.y = (float)Math.round(point.y); return 0; } /** * Access to individual character advances are frequently needed for layout * understand that advance may vary for single glyph if ligatures or kerning * are enabled * @param ch char * @return advance of single char */ public float getCharAdvance(char ch) { int glyphCode = fontResource.getGlyphMapper().charToGlyph((int)ch); return fontResource.getAdvance(glyphCode, size); } /* REMIND A map is not the solution ultimately required here */ public Glyph getGlyph(char ch) { int glyphCode = fontResource.getGlyphMapper().charToGlyph((int)ch); return getGlyph(glyphCode); } protected abstract Glyph createGlyph(int glyphCode); public Glyph getGlyph(int glyphCode) { Glyph glyph = glyphMap.get(glyphCode); if (glyph == null) { glyph = createGlyph(glyphCode); glyphMap.put(glyphCode, glyph); } return glyph; } protected abstract Path2D createGlyphOutline(int glyphCode); public Shape getOutline(GlyphList gl, BaseTransform transform) { Path2D result = new Path2D(); getOutline(gl, transform, result); return result; } void getOutline(GlyphList gl, BaseTransform transform, Path2D p) { p.reset(); if (gl == null) { return; } if (transform == null) { transform = BaseTransform.IDENTITY_TRANSFORM; } Affine2D t = new Affine2D(); for (int i = 0; i < gl.getGlyphCount(); i++) { int glyphCode = gl.getGlyphCode(i); if (glyphCode != CharToGlyphMapper.INVISIBLE_GLYPH_ID) { Shape gp = createGlyphOutline(glyphCode); if (gp != null) { t.setTransform(transform); t.translate(gl.getPosX(i), gl.getPosY(i)); p.append(gp.getPathIterator(t), false); } } } } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (!(obj instanceof PrismFontStrike)) { return false; } final PrismFontStrike other = (PrismFontStrike) obj; // REMIND: When fonts can be rendered other than as greyscale // and generally differ in ways other than the size // we need to update this method. return this.size == other.size && this.transform.getMxx() == other.transform.getMxx() && this.transform.getMxy() == other.transform.getMxy() && this.transform.getMyx() == other.transform.getMyx() && this.transform.getMyy() == other.transform.getMyy() && this.fontResource.equals(other.fontResource); } private int hash; @Override public int hashCode() { if (hash != 0) { return hash; } hash = Float.floatToIntBits(size) + Float.floatToIntBits((float)transform.getMxx()) + Float.floatToIntBits((float)transform.getMyx()) + Float.floatToIntBits((float)transform.getMxy()) + Float.floatToIntBits((float)transform.getMyy()); hash = 71 * hash + fontResource.hashCode(); return hash; } public String toString() { return "FontStrike: " + super.toString() + " font resource = " + fontResource + " size = " + size + " matrix = " + transform; } }