/*
* Copyright (c) 2013, 2017, 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<T extends PrismFontFile> implements FontStrike {
private DisposerRecord disposer;
private T fontResource;
private Map<Integer,Glyph> glyphMap = new HashMap<Integer,Glyph>();
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;
PrismFontFactory factory = PrismFontFactory.getFontFactory();
boolean lcdEnabled = factory.isLCDTextSupported();
this.aaMode = lcdEnabled ? aaMode : FontResource.AA_GREYSCALE;
if (tx.isTranslateOrIdentity()) {
transform = BaseTransform.IDENTITY_TRANSFORM;
} else {
transform = new Affine2D(tx.getMxx(), tx.getMyx(),
tx.getMxy(), tx.getMyy(),
0f, 0f);
}
}
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.
}
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
/**
* 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
Params: - ch – char
Returns: advance of single char
/**
* 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;
}
}