| /* |
| * 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. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.svg.font; |
| |
| import java.awt.Graphics2D; |
| import java.awt.Rectangle; |
| import java.awt.Shape; |
| import java.awt.font.FontRenderContext; |
| import java.awt.font.GlyphJustificationInfo; |
| import java.awt.font.GlyphMetrics; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.GeneralPath; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.text.AttributedCharacterIterator; |
| import java.text.CharacterIterator; |
| import java.text.StringCharacterIterator; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import org.apache.batik.gvt.font.GVTFont; |
| import org.apache.batik.gvt.font.GVTGlyphMetrics; |
| import org.apache.batik.gvt.font.GVTGlyphVector; |
| import org.apache.batik.gvt.font.GVTLineMetrics; |
| import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; |
| |
| import org.apache.fop.fonts.Font; |
| import org.apache.fop.fonts.FontMetrics; |
| import org.apache.fop.fonts.GlyphMapping; |
| import org.apache.fop.fonts.TextFragment; |
| import org.apache.fop.traits.MinOptMax; |
| |
| public class FOPGVTGlyphVector implements GVTGlyphVector { |
| |
| protected final TextFragment text; |
| |
| protected final FOPGVTFont font; |
| |
| private final int fontSize; |
| |
| private final FontMetrics fontMetrics; |
| |
| private final FontRenderContext frc; |
| |
| protected int[] glyphs; |
| |
| protected List associations; |
| |
| protected int[][] gposAdjustments; |
| |
| protected float[] positions; |
| |
| protected Rectangle2D[] boundingBoxes; |
| |
| protected GeneralPath outline; |
| |
| protected AffineTransform[] glyphTransforms; |
| |
| protected boolean[] glyphVisibilities; |
| |
| protected Rectangle2D logicalBounds; |
| |
| FOPGVTGlyphVector(FOPGVTFont font, final CharacterIterator iter, FontRenderContext frc) { |
| this.text = new SVGTextFragment(iter); |
| this.font = font; |
| Font f = font.getFont(); |
| this.fontSize = f.getFontSize(); |
| this.fontMetrics = f.getFontMetrics(); |
| this.frc = frc; |
| } |
| |
| public void performDefaultLayout() { |
| Font f = font.getFont(); |
| MinOptMax letterSpaceIPD = MinOptMax.ZERO; |
| MinOptMax[] letterSpaceAdjustments = new MinOptMax[text.getEndIndex()]; |
| boolean retainControls = false; |
| GlyphMapping mapping = GlyphMapping.doGlyphMapping(text, text.getBeginIndex(), text.getEndIndex(), |
| f, letterSpaceIPD, letterSpaceAdjustments, '\0', '\0', |
| false, text.getBidiLevel(), true, true, retainControls); |
| CharacterIterator glyphAsCharIter = |
| mapping.mapping != null ? new StringCharacterIterator(mapping.mapping) : text.getIterator(); |
| this.glyphs = buildGlyphs(f, glyphAsCharIter); |
| this.associations = mapping.associations; |
| this.gposAdjustments = mapping.gposAdjustments; |
| if (text.getBeginIndex() > 0) { |
| int arrlen = text.getEndIndex() - text.getBeginIndex(); |
| MinOptMax[] letterSpaceAdjustmentsNew = new MinOptMax[arrlen]; |
| System.arraycopy(letterSpaceAdjustments, text.getBeginIndex(), letterSpaceAdjustmentsNew, |
| 0, arrlen); |
| letterSpaceAdjustments = letterSpaceAdjustmentsNew; |
| } |
| this.positions = buildGlyphPositions(glyphAsCharIter, mapping.gposAdjustments, letterSpaceAdjustments); |
| this.glyphVisibilities = new boolean[this.glyphs.length]; |
| Arrays.fill(glyphVisibilities, true); |
| this.glyphTransforms = new AffineTransform[this.glyphs.length]; |
| } |
| |
| private static class SVGTextFragment implements TextFragment { |
| |
| private final CharacterIterator charIter; |
| |
| private String script; |
| |
| private String language; |
| |
| private int level = -1; |
| |
| SVGTextFragment(CharacterIterator charIter) { |
| this.charIter = charIter; |
| if (charIter instanceof AttributedCharacterIterator) { |
| AttributedCharacterIterator aci = (AttributedCharacterIterator) charIter; |
| aci.first(); |
| this.script = (String) aci.getAttribute(GVTAttributedCharacterIterator.TextAttribute.SCRIPT); |
| this.language = (String) aci.getAttribute(GVTAttributedCharacterIterator.TextAttribute.LANGUAGE); |
| Integer level = (Integer) aci.getAttribute(GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL); |
| if (level != null) { |
| this.level = level; |
| } |
| } |
| } |
| |
| public CharacterIterator getIterator() { |
| return charIter; |
| } |
| |
| public int getBeginIndex() { |
| return charIter.getBeginIndex(); |
| } |
| |
| public int getEndIndex() { |
| return charIter.getEndIndex(); |
| } |
| |
| // TODO - [GA] the following appears to be broken because it ignores |
| // sttart and end index arguments |
| public CharSequence subSequence(int startIndex, int endIndex) { |
| StringBuilder sb = new StringBuilder(); |
| for (char c = charIter.first(); c != CharacterIterator.DONE; c = charIter.next()) { |
| sb.append(c); |
| } |
| return sb.toString(); |
| } |
| |
| public String getScript() { |
| if (script != null) { |
| return script; |
| } else { |
| return "auto"; |
| } |
| } |
| |
| public String getLanguage() { |
| if (language != null) { |
| return language; |
| } else { |
| return "none"; |
| } |
| } |
| |
| public int getBidiLevel() { |
| return level; |
| } |
| |
| public char charAt(int index) { |
| return charIter.setIndex(index - charIter.getBeginIndex()); |
| } |
| } |
| |
| private int[] buildGlyphs(Font font, final CharacterIterator glyphAsCharIter) { |
| int[] glyphs = new int[glyphAsCharIter.getEndIndex() - glyphAsCharIter.getBeginIndex()]; |
| int index = 0; |
| for (char c = glyphAsCharIter.first(); c != CharacterIterator.DONE; c = glyphAsCharIter.next()) { |
| glyphs[index] = font.mapChar(c); |
| index++; |
| } |
| return glyphs; |
| } |
| |
| private static final int[] PA_ZERO = new int[4]; |
| |
| /** |
| * Build glyph position array. |
| * @param glyphAsCharIter iterator for mapped glyphs as char codes (not glyph codes) |
| * @param dp optionally null glyph position adjustments array |
| * @param lsa optionally null letter space adjustments array |
| * @return array of floats that denote [X,Y] position pairs for each glyph including |
| * including an implied subsequent glyph; i.e., returned array contains one more pair |
| * than the numbers of glyphs, where the position denoted by this last pair represents |
| * the position after the last glyph has incurred advancement |
| */ |
| private float[] buildGlyphPositions(final CharacterIterator glyphAsCharIter, int[][] dp, MinOptMax[] lsa) { |
| int numGlyphs = glyphAsCharIter.getEndIndex() - glyphAsCharIter.getBeginIndex(); |
| float[] positions = new float[2 * (numGlyphs + 1)]; |
| float xc = 0f; |
| float yc = 0f; |
| if (dp != null) { |
| for (int i = 0; i < numGlyphs + 1; ++i) { |
| int[] pa = ((i >= dp.length) || (dp[i] == null)) ? PA_ZERO : dp[i]; |
| float xo = xc + ((float) pa[0]) / 1000f; |
| float yo = yc - ((float) pa[1]) / 1000f; |
| float xa = getGlyphWidth(i) + ((float) pa[2]) / 1000f; |
| float ya = ((float) pa[3]) / 1000f; |
| int k = 2 * i; |
| positions[k + 0] = xo; |
| positions[k + 1] = yo; |
| xc += xa; |
| yc += ya; |
| } |
| } else if (lsa != null) { |
| for (int i = 0; i < numGlyphs + 1; ++i) { |
| MinOptMax sa = (((i + 1) >= lsa.length) || (lsa[i + 1] == null)) ? MinOptMax.ZERO : lsa[i + 1]; |
| float xo = xc; |
| float yo = yc; |
| float xa = getGlyphWidth(i) + sa.getOpt() / 1000f; |
| float ya = 0; |
| int k = 2 * i; |
| positions[k + 0] = xo; |
| positions[k + 1] = yo; |
| xc += xa; |
| yc += ya; |
| } |
| } |
| return positions; |
| } |
| |
| private float getGlyphWidth(int index) { |
| if (index < glyphs.length) { |
| return fontMetrics.getWidth(glyphs[index], fontSize) / 1000000f; |
| } else { |
| return 0f; |
| } |
| } |
| |
| public GVTFont getFont() { |
| return font; |
| } |
| |
| public FontRenderContext getFontRenderContext() { |
| return frc; |
| } |
| |
| public void setGlyphCode(int glyphIndex, int glyphCode) { |
| glyphs[glyphIndex] = glyphCode; |
| } |
| |
| public int getGlyphCode(int glyphIndex) { |
| return glyphs[glyphIndex]; |
| } |
| |
| public int[] getGlyphCodes(int beginGlyphIndex, int numEntries, |
| int[] codeReturn) { |
| if (codeReturn == null) { |
| codeReturn = new int[numEntries]; |
| } |
| System.arraycopy(glyphs, beginGlyphIndex, codeReturn, 0, numEntries); |
| return codeReturn; |
| } |
| |
| public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex) { |
| // TODO Auto-generated method stub |
| throw new UnsupportedOperationException(); |
| } |
| |
| public Shape getGlyphLogicalBounds(int glyphIndex) { |
| GVTGlyphMetrics metrics = getGlyphMetrics(glyphIndex); |
| Point2D pos = getGlyphPosition(glyphIndex); |
| GVTLineMetrics fontMetrics = font.getLineMetrics(0); |
| Rectangle2D bounds = new Rectangle2D.Float(0, -fontMetrics.getDescent(), metrics.getHorizontalAdvance(), |
| fontMetrics.getAscent() + fontMetrics.getDescent()); |
| AffineTransform t = AffineTransform.getTranslateInstance(pos.getX(), pos.getY()); |
| AffineTransform transf = getGlyphTransform(glyphIndex); |
| if (transf != null) { |
| t.concatenate(transf); |
| } |
| t.scale(1, -1); // Translate from glyph coordinate system to user |
| return t.createTransformedShape(bounds); |
| } |
| |
| public GVTGlyphMetrics getGlyphMetrics(int glyphIndex) { |
| Rectangle2D bbox = getBoundingBoxes()[glyphIndex]; |
| return new GVTGlyphMetrics(positions[2 * (glyphIndex + 1)] - positions[2 * glyphIndex], |
| (fontMetrics.getAscender(fontSize) - fontMetrics.getDescender(fontSize)) / 1000000f, |
| bbox, GlyphMetrics.STANDARD); |
| } |
| |
| public Shape getGlyphOutline(int glyphIndex) { |
| Shape glyphBox = getBoundingBoxes()[glyphIndex]; |
| AffineTransform tr = AffineTransform.getTranslateInstance(positions[glyphIndex * 2], |
| positions[glyphIndex * 2 + 1]); |
| AffineTransform glyphTransform = getGlyphTransform(glyphIndex); |
| if (glyphTransform != null) { |
| tr.concatenate(glyphTransform); |
| } |
| return tr.createTransformedShape(glyphBox); |
| } |
| |
| public Rectangle2D getGlyphCellBounds(int glyphIndex) { |
| // TODO Auto-generated method stub |
| throw new UnsupportedOperationException(); |
| } |
| |
| public int[][] getGlyphPositionAdjustments() { |
| return gposAdjustments; |
| } |
| |
| public Point2D getGlyphPosition(int glyphIndex) { |
| int positionIndex = glyphIndex * 2; |
| return new Point2D.Float(positions[positionIndex], positions[positionIndex + 1]); |
| } |
| |
| public float[] getGlyphPositions(int beginGlyphIndex, int numEntries, float[] positionReturn) { |
| if (positionReturn == null) { |
| positionReturn = new float[numEntries * 2]; |
| } |
| System.arraycopy(positions, beginGlyphIndex * 2, positionReturn, 0, numEntries * 2); |
| return positionReturn; |
| } |
| |
| public AffineTransform getGlyphTransform(int glyphIndex) { |
| return glyphTransforms[glyphIndex]; |
| } |
| |
| public Shape getGlyphVisualBounds(int glyphIndex) { |
| Rectangle2D bbox = getBoundingBoxes()[glyphIndex]; |
| Point2D pos = getGlyphPosition(glyphIndex); |
| AffineTransform t = AffineTransform.getTranslateInstance(pos.getX(), pos.getY()); |
| AffineTransform transf = getGlyphTransform(glyphIndex); |
| if (transf != null) { |
| t.concatenate(transf); |
| } |
| return t.createTransformedShape(bbox); |
| } |
| |
| public Rectangle2D getLogicalBounds() { |
| if (logicalBounds == null) { |
| GeneralPath logicalBoundsPath = new GeneralPath(); |
| for (int i = 0; i < getNumGlyphs(); i++) { |
| Shape glyphLogicalBounds = getGlyphLogicalBounds(i); |
| logicalBoundsPath.append(glyphLogicalBounds, false); |
| } |
| logicalBounds = logicalBoundsPath.getBounds2D(); |
| } |
| return logicalBounds; |
| } |
| |
| public int getNumGlyphs() { |
| return glyphs.length; |
| } |
| |
| public Shape getOutline() { |
| if (outline == null) { |
| outline = new GeneralPath(); |
| for (int i = 0; i < glyphs.length; i++) { |
| outline.append(getGlyphOutline(i), false); |
| } |
| } |
| return outline; |
| } |
| |
| public Shape getOutline(float x, float y) { |
| // TODO Auto-generated method stub |
| throw new UnsupportedOperationException(); |
| } |
| |
| public Rectangle2D getGeometricBounds() { |
| // TODO Auto-generated method stub |
| throw new UnsupportedOperationException(); |
| } |
| |
| public Rectangle2D getBounds2D(AttributedCharacterIterator aci) { |
| return getOutline().getBounds2D(); |
| } |
| |
| public void setGlyphPosition(int glyphIndex, Point2D newPos) { |
| int idx = glyphIndex * 2; |
| positions[idx] = (float) newPos.getX(); |
| positions[idx + 1] = (float) newPos.getY(); |
| } |
| |
| public void setGlyphTransform(int glyphIndex, AffineTransform newTX) { |
| glyphTransforms[glyphIndex] = newTX; |
| } |
| |
| public void setGlyphVisible(int glyphIndex, boolean visible) { |
| glyphVisibilities[glyphIndex] = visible; |
| } |
| |
| public boolean isGlyphVisible(int glyphIndex) { |
| return glyphVisibilities[glyphIndex]; |
| } |
| |
| public int getCharacterCount(int startGlyphIndex, int endGlyphIndex) { |
| // TODO Not that simple if complex scripts are involved |
| return endGlyphIndex - startGlyphIndex + 1; |
| } |
| |
| public boolean isReversed() { |
| return false; |
| } |
| |
| public void maybeReverse(boolean mirror) { |
| } |
| |
| public void draw(Graphics2D graphics2d, AttributedCharacterIterator aci) { |
| // NOP |
| } |
| |
| private Rectangle2D[] getBoundingBoxes() { |
| if (boundingBoxes == null) { |
| buildBoundingBoxes(); |
| } |
| return boundingBoxes; |
| } |
| |
| private void buildBoundingBoxes() { |
| boundingBoxes = new Rectangle2D[glyphs.length]; |
| for (int i = 0; i < glyphs.length; i++) { |
| Rectangle bbox = fontMetrics.getBoundingBox(glyphs[i], fontSize); |
| boundingBoxes[i] = new Rectangle2D.Float(bbox.x / 1000000f, -(bbox.y + bbox.height) / 1000000f, |
| bbox.width / 1000000f, bbox.height / 1000000f); |
| } |
| } |
| |
| } |