com.sun.javafx.sg.prism.NGText Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openjfx-78-backport Show documentation
Show all versions of openjfx-78-backport Show documentation
This is a backport of OpenJFX 8 to run on Java 7.
The newest version!
/*
* Copyright (c) 2012, 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.sg.prism;
import com.sun.javafx.font.FontResource;
import com.sun.javafx.font.FontStrike;
import com.sun.javafx.font.Metrics;
import com.sun.javafx.font.PGFont;
import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.Path2D;
import com.sun.javafx.geom.Point2D;
import com.sun.javafx.geom.RectBounds;
import com.sun.javafx.geom.RoundRectangle2D;
import com.sun.javafx.geom.Shape;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.scene.text.GlyphList;
import com.sun.javafx.sg.PGText;
import com.sun.javafx.text.TextRun;
import com.sun.prism.Graphics;
import com.sun.prism.paint.Color;
public class NGText extends NGShape implements PGText {
static final BaseTransform IDENT = BaseTransform.IDENTITY_TRANSFORM;
public NGText() {
}
private GlyphList[] runs;
@Override public void setGlyphs(Object[] glyphs) {
this.runs = (GlyphList[])glyphs;
geometryChanged();
}
private float layoutX, layoutY;
@Override public void setLayoutLocation(float x, float y) {
layoutX = x;
layoutY = y;
geometryChanged();
}
private PGFont font;
@Override public void setFont(Object font) {
if (font != null && font.equals(this.font)) {
return;
}
this.font = (PGFont)font;
this.fontStrike = null;
this.identityStrike = null;
geometryChanged();
}
private int fontSmoothingType;
@Override public void setFontSmoothingType(int fontSmoothingType) {
this.fontSmoothingType = fontSmoothingType;
geometryChanged();
}
private boolean underline;
@Override public void setUnderline(boolean underline) {
this.underline = underline;
geometryChanged();
}
private boolean strikethrough;
@Override public void setStrikethrough(boolean strikethrough) {
this.strikethrough = strikethrough;
geometryChanged();
}
private Object selectionPaint;
private int selectionStart;
private int selectionEnd;
@Override public void setSelection(int start, int end, Object color) {
selectionPaint = color;
selectionStart = start;
selectionEnd = end;
geometryChanged();
}
/**
* Provide some lucky padding in the case that we are rendering LCD
* text since there might be some pixels that lie outside the normally
* computed content bounds.
*/
@Override protected BaseBounds computePadding(BaseBounds region) {
float pad = fontSmoothingType == FontResource.AA_LCD ? 2f : 1f;
return region.deriveWithNewBounds(region.getMinX() - pad,
region.getMinY() - pad,
region.getMinZ(),
region.getMaxX() + pad,
region.getMaxY() + pad,
region.getMaxZ());
}
private static double EPSILON = 0.01;
private FontStrike fontStrike = null;
private FontStrike identityStrike = null;
private double[] strikeMat = new double[4];
private FontStrike getStrike(BaseTransform xform) {
int smoothingType = fontSmoothingType;
if (getMode() == Mode.STROKE_FILL) {
// When there's a stroke, we want the glyph to be unhinted to match
// the stroke. This currently means it must be grayscale.
smoothingType = FontResource.AA_GREYSCALE;
}
if (xform.isIdentity()) {
if (identityStrike == null ||
smoothingType != identityStrike.getAAMode()) {
identityStrike = font.getStrike(IDENT, smoothingType);
}
return identityStrike;
}
// REMIND: need to enhance this, to take other rendering attributes.
if (fontStrike == null ||
fontStrike.getSize() != font.getSize() ||
(xform.getMxy() == 0 && strikeMat[1] != 0) ||
(xform.getMyx() == 0 && strikeMat[2] != 0) ||
(Math.abs(strikeMat[0] - xform.getMxx()) > EPSILON) ||
(Math.abs(strikeMat[1] - xform.getMxy()) > EPSILON) ||
(Math.abs(strikeMat[2] - xform.getMyx()) > EPSILON) ||
(Math.abs(strikeMat[3] - xform.getMyy()) > EPSILON) ||
smoothingType != fontStrike.getAAMode())
{
fontStrike = font.getStrike(xform, smoothingType);
strikeMat[0] = xform.getMxx();
strikeMat[1] = xform.getMxy();
strikeMat[2] = xform.getMyx();
strikeMat[3] = xform.getMyy();
}
return fontStrike;
}
@Override public Shape getShape() {
if (runs == null) {
return new Path2D();
}
FontStrike strike = getStrike(IDENT);
Path2D outline = new Path2D();
for (int i = 0; i < runs.length; i++) {
GlyphList run = runs[i];
Point2D pt = run.getLocation();
float x = pt.x - layoutX;
float y = pt.y - layoutY;
BaseTransform t = BaseTransform.getTranslateInstance(x, y);
outline.append(strike.getOutline(run, t), false);
Metrics metrics = null;
if (underline) {
metrics = strike.getMetrics();
RoundRectangle2D rect = new RoundRectangle2D();
rect.x = x;
rect.y = y + metrics.getUnderLineOffset();
rect.width = run.getWidth();
rect.height = metrics.getUnderLineThickness();
outline.append(rect, false);
}
if (strikethrough) {
if (metrics == null) {
metrics = strike.getMetrics();
}
RoundRectangle2D rect = new RoundRectangle2D();
rect.x = x;
rect.y = y + metrics.getStrikethroughOffset();
rect.width = run.getWidth();
rect.height = metrics.getStrikethroughThickness();
outline.append(rect, false);
}
}
return outline;
}
private boolean drawingEffect = false;
@Override protected void renderEffect(Graphics g) {
/* Text as pre-composed image glyphs must be rendered in
* device space because otherwise pixelisation effects are
* very apparent.
* The Effects implementation seems to indicate that it applies
* effects in a space with the transforms already applied :
* ie PrEffectHelper.effect() says for at least a 2D TX :
* // process the effect using the current 2D transform, and then
* // render the resulting image in device space (i.e., with identity)
* However its apparent that (eg) a rotation is applied twice to
* shadow text. As if drawing the "non-shadow" text over the image
* with the shadow text overlooks that this is in effect
* applying that rotation again. However I don't think its quite
* that simple. Also the shadow text is cut-off as if it was
* clipped to the original unrotated coordinates.
* To work around this if an effect is detected, we will render
* as shapes since they are provided in user space.
* This is probably a reasonable compromise.
* However if no transform is detected we can try to use the normal
* image drawing path. If that's causes problems this test can
* be removed.
*/
if (!g.getTransformNoClone().isTranslateOrIdentity()) {
drawingEffect = true;
}
try {
super.renderEffect(g);
} finally {
drawingEffect = false;
}
}
private static int FILL = 1 << 1;
private static int SHAPE_FILL = 1 << 2;
private static int TEXT = 1 << 3;
private static int DECORATION = 1 << 4;
@Override protected void renderContent(Graphics g) {
if (mode == Mode.EMPTY) return;
if (runs == null || runs.length == 0) return;
BaseTransform tx = g.getTransformNoClone();
// Render this text node as geometry if it is 3D transformed
if (!tx.is2D()) {
super.renderContent(g);
return;
}
FontStrike strike = getStrike(tx);
if (strike.getAAMode() == FontResource.AA_LCD ||
(fillPaint != null && fillPaint.isProportional()) ||
(drawPaint != null && drawPaint.isProportional()))
{
/*
* This check is only a performance optimization, to prevent
* unnecessarily computing bounds. It's a quickly cautious estimate
* if we might need to setNodeBounds, graphics does practically no
* extra work by setting node bounds. But it's much faster to
* setNodeBounds for LCD text rendering and is required for correct
* proportional gradient.
*/
BaseBounds bds = getContentBounds(new RectBounds(), IDENT);
g.setNodeBounds((RectBounds)bds);
}
Color selectionColor = null;
if (selectionStart != selectionEnd && selectionPaint instanceof Color) {
selectionColor = (Color)selectionPaint;
}
BaseBounds clipBds = null;
if (getClipNode() != null) {
// Note: this clip does not including any clip in the ancestors.
clipBds = getClippedBounds(new RectBounds(), IDENT);
}
// FILL or STROKE_FILL
if (mode != Mode.STROKE) {
g.setPaint(fillPaint);
int op = TEXT;
op |= strike.drawAsShapes() || drawingEffect ? SHAPE_FILL : FILL;
renderText(g, strike, clipBds, selectionColor, op);
// Splitting decoration from text rendering is important in order
// to group common render states together, for fast performance.
if (underline || strikethrough) {
op = DECORATION | SHAPE_FILL;
renderText(g, strike, clipBds, selectionColor, op);
}
}
// STROKE or STROKE_FILL
if (mode != Mode.FILL) {
g.setPaint(drawPaint);
g.setStroke(drawStroke);
int op = TEXT;
if (underline || strikethrough) {
op |= DECORATION;
}
renderText(g, strike, clipBds, selectionColor, op);
}
g.setNodeBounds(null);
}
private void renderText(Graphics g, FontStrike strike, BaseBounds clipBds,
Color selectionColor, int op) {
for (int i = 0; i < runs.length; i++) {
TextRun run = (TextRun)runs[i];
RectBounds lineBounds = run.getLineBounds();
Point2D pt = run.getLocation();
float x = pt.x - layoutX;
float y = pt.y - layoutY;
if (clipBds != null) {
if (y > clipBds.getMaxY()) break;
if (y + lineBounds.getHeight() < clipBds.getMinY()) continue;
if (x > clipBds.getMaxX()) continue;
if (x + run.getWidth() < clipBds.getMinX()) continue;
}
y -= lineBounds.getMinY();
if ((op & TEXT) != 0) {
if ((op & FILL) != 0) {
int start = run.getStart();
g.drawString(run, strike, x, y,
selectionColor,
selectionStart - start,
selectionEnd - start);
} else {
BaseTransform t = BaseTransform.getTranslateInstance(x, y);
if ((op & SHAPE_FILL) != 0) {
g.fill(strike.getOutline(run, t));
} else {
g.draw(strike.getOutline(run, t));
}
}
}
if ((op & DECORATION) != 0) {
Metrics metrics = strike.getMetrics();
if (underline) {
float offset = metrics.getUnderLineOffset();
float thickness = metrics.getUnderLineThickness();
if ((op & SHAPE_FILL) != 0) {
g.fillRect(x, y + offset, run.getWidth(), thickness);
} else {
g.drawRect(x, y + offset, run.getWidth(), thickness);
}
}
if (strikethrough) {
float offset = metrics.getStrikethroughOffset();
float thickness = metrics.getStrikethroughThickness();
if ((op & SHAPE_FILL) != 0) {
g.fillRect(x, y + offset, run.getWidth(), thickness);
} else {
g.drawRect(x, y + offset, run.getWidth(), thickness);
}
}
}
}
}
}