
org.apache.fontbox.ttf.GlyphRenderer Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.fontbox.ttf;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.awt.geom.GeneralPath;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* This class provides a glyph to GeneralPath conversion for true type fonts.
* Based on code from Apache Batik, a subproject of Apache XMLGraphics.
*
* @see
* http://xmlgraphics.apache.org/batik
*
* Contour rendering ported from PDF.js, viewed on 14.2.2015, rev 2e97c0d
*
* @see
* pdf.js/src/core/font_renderer.js
*
*/
class GlyphRenderer
{
private static final Log LOG = LogFactory.getLog(GlyphRenderer.class);
private GlyphDescription glyphDescription;
GlyphRenderer(GlyphDescription glyphDescription)
{
this.glyphDescription = glyphDescription;
}
/**
* Returns the path of the glyph.
* @return the path
*/
public GeneralPath getPath()
{
Point[] points = describe(glyphDescription);
return calculatePath(points);
}
/**
* Set the points of a glyph from the GlyphDescription.
*/
private Point[] describe(GlyphDescription gd)
{
int endPtIndex = 0;
int endPtOfContourIndex = -1;
Point[] points = new Point[gd.getPointCount()];
for (int i = 0; i < gd.getPointCount(); i++)
{
if (endPtOfContourIndex == -1)
{
endPtOfContourIndex = gd.getEndPtOfContours(endPtIndex);
}
boolean endPt = endPtOfContourIndex == i;
if (endPt)
{
endPtIndex++;
endPtOfContourIndex = -1;
}
points[i] = new Point(gd.getXCoordinate(i), gd.getYCoordinate(i),
(gd.getFlags(i) & GlyfDescript.ON_CURVE) != 0, endPt);
}
return points;
}
/**
* Use the given points to calculate a GeneralPath.
*
* @param points the points to be used to generate the GeneralPath
*
* @return the calculated GeneralPath
*/
private GeneralPath calculatePath(Point[] points)
{
GeneralPath path = new GeneralPath();
int start = 0;
for (int p = 0, len = points.length; p < len; ++p)
{
if (points[p].endOfContour)
{
Point firstPoint = points[start];
Point lastPoint = points[p];
List contour = new ArrayList();
for (int q = start; q <= p; ++q)
{
contour.add(points[q]);
}
if (points[start].onCurve)
{
// using start point at the contour end
contour.add(firstPoint);
}
else if (points[p].onCurve)
{
// first is off-curve point, trying to use one from the end
contour.add(0, lastPoint);
}
else
{
// start and end are off-curve points, creating implicit one
Point pmid = midValue(firstPoint, lastPoint);
contour.add(0, pmid);
contour.add(pmid);
}
moveTo(path, contour.get(0));
for (int j = 1, clen = contour.size(); j < clen; j++)
{
Point pnow = contour.get(j);
if (pnow.onCurve)
{
lineTo(path, pnow);
}
else if (contour.get(j + 1).onCurve)
{
quadTo(path, pnow, contour.get(j + 1));
++j;
}
else
{
quadTo(path, pnow, midValue(pnow, contour.get(j + 1)));
}
}
path.closePath();
start = p + 1;
}
}
return path;
}
private void moveTo(GeneralPath path, Point point)
{
path.moveTo(point.x, point.y);
if (LOG.isDebugEnabled())
{
LOG.trace("moveTo: " + String.format(Locale.US, "%d,%d", point.x, point.y));
}
}
private void lineTo(GeneralPath path, Point point)
{
path.lineTo(point.x, point.y);
if (LOG.isDebugEnabled())
{
LOG.trace("lineTo: " + String.format(Locale.US, "%d,%d", point.x, point.y));
}
}
private void quadTo(GeneralPath path, Point ctrlPoint, Point point)
{
path.quadTo(ctrlPoint.x, ctrlPoint.y, point.x, point.y);
if (LOG.isDebugEnabled())
{
LOG.trace("quadTo: " + String.format(Locale.US, "%d,%d %d,%d", ctrlPoint.x, ctrlPoint.y,
point.x, point.y));
}
}
private int midValue(int a, int b)
{
return a + (b - a) / 2;
}
// this creates an onCurve point that is between point1 and point2
private Point midValue(Point point1, Point point2)
{
return new Point(midValue(point1.x, point2.x), midValue(point1.y, point2.y));
}
/**
* This class represents one point of a glyph.
*/
private static class Point
{
private int x = 0;
private int y = 0;
private boolean onCurve = true;
private boolean endOfContour = false;
Point(int xValue, int yValue, boolean onCurveValue, boolean endOfContourValue)
{
x = xValue;
y = yValue;
onCurve = onCurveValue;
endOfContour = endOfContourValue;
}
// this constructs an on-curve, non-endofcountour point
Point(int xValue, int yValue)
{
this(xValue, yValue, true, false);
}
@Override
public String toString()
{
return String.format(Locale.US, "Point(%d,%d,%s,%s)", x, y, onCurve ? "onCurve" : "",
endOfContour ? "endOfContour" : "");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy