Skip to content

Commit

Permalink
FOP-3180: SVG Glyph positions ignored when using a custom font by Joã…
Browse files Browse the repository at this point in the history
…o André Gonçalves
  • Loading branch information
simonsteiner1984 committed Nov 7, 2024
1 parent 3294f60 commit 8c1be2a
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 73 deletions.
28 changes: 17 additions & 11 deletions fop-core/src/main/java/org/apache/fop/svg/NativeTextPainter.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,15 @@ protected final void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOExce
}

protected void writeGlyphs(FOPGVTGlyphVector gv, GeneralPath debugShapes) throws IOException {
AffineTransform localTransform = new AffineTransform();
Point2D prevPos = null;
AffineTransform prevGlyphTransform = null;
font = ((FOPGVTFont) gv.getFont()).getFont();
for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) {
if (!gv.isGlyphVisible(index)) {
continue;
}

char glyph = (char) gv.getGlyphCode(index);
Point2D glyphPos = gv.getGlyphPosition(index);

AffineTransform glyphTransform = gv.getGlyphTransform(index);
Expand All @@ -156,22 +157,27 @@ protected void writeGlyphs(FOPGVTGlyphVector gv, GeneralPath debugShapes) throws
debugShapes.append(sh, false);
}

//Exact position of the glyph
localTransform.setToIdentity();
localTransform.translate(glyphPos.getX(), glyphPos.getY());
if (glyphTransform != null) {
localTransform.concatenate(glyphTransform);
}
localTransform.scale(1, -1);

positionGlyph(prevPos, glyphPos, glyphTransform != null || prevGlyphTransform != null);
char glyph = (char) gv.getGlyphCode(index);

//Update last position
prevPos = glyphPos;
prevGlyphTransform = glyphTransform;

writeGlyph(glyph, localTransform);
writeGlyph(glyph, getLocalTransform(glyphPos, glyphTransform));
}
}

protected AffineTransform getLocalTransform(Point2D glyphPos, AffineTransform glyphTransform) {
//Exact position of the glyph
AffineTransform localTransform = new AffineTransform();
localTransform.setToIdentity();
localTransform.translate(glyphPos.getX(), glyphPos.getY());
if (glyphTransform != null) {
localTransform.concatenate(glyphTransform);
}
localTransform.scale(1, -1);

return localTransform;
}

@Override
Expand Down
77 changes: 43 additions & 34 deletions fop-core/src/main/java/org/apache/fop/svg/PDFTextPainter.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@

import org.apache.batik.gvt.text.TextPaintInfo;

import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.svg.font.FOPGVTFont;
import org.apache.fop.svg.font.FOPGVTGlyphVector;
Expand All @@ -48,6 +47,8 @@
*/
class PDFTextPainter extends NativeTextPainter {

private static final int[] PA_ZERO = new int[4];

private PDFGraphics2D pdf;

private PDFTextUtil textUtil;
Expand Down Expand Up @@ -108,40 +109,50 @@ protected void clip(Shape clip) {
pdf.writeClip(clip);
}

private static int[] paZero = new int[4];

@Override
protected void writeGlyphs(FOPGVTGlyphVector gv, GeneralPath debugShapes) throws IOException {
if (gv.getGlyphPositionAdjustments() == null) {
super.writeGlyphs(gv, debugShapes);
} else {
FOPGVTFont gvtFont = (FOPGVTFont) gv.getFont();
String fk = gvtFont.getFontKey();
Font f = gvtFont.getFont();
Point2D initialPos = gv.getGlyphPosition(0);
int fs = f.getFontSize();
float fsPoints = fs / 1000f;
double xc = 0f;
double yc = 0f;
double xoLast = 0f;
double yoLast = 0f;
textUtil.writeTextMatrix(new AffineTransform(1, 0, 0, -1, initialPos.getX(), initialPos.getY()));
textUtil.updateTf(fk, fsPoints, f.isMultiByte(), false);
int[][] dp = gv.getGlyphPositionAdjustments();
for (int i = 0, n = gv.getNumGlyphs(); i < n; i++) {
int gc = gv.getGlyphCode(i);
int[] pa = ((i > dp.length) || (dp[i] == null)) ? paZero : dp[i];
double xo = xc + pa[0];
double yo = yc + pa[1];
double xa = f.getWidth(gc);
double ya = 0;
double xd = (xo - xoLast) / 1000f;
double yd = (yo - yoLast) / 1000f;
textUtil.writeTd(xd, yd);
textUtil.writeTj((char) gc, f.isMultiByte(), false);
xc += xa + pa[2];
yc += ya + pa[3];
xoLast = xo;
yoLast = yo;
Point2D prevPos = null;
AffineTransform prevGlyphTransform = null;
font = ((FOPGVTFont) gv.getFont()).getFont();
int[][] glyphPositionAdjustments = gv.getGlyphPositionAdjustments();
double glyphXPos = 0f;
double glyphYPos = 0f;
double prevAdjustedGlyphXPos = 0f;
double prevAdjustedGlyphYPos = 0f;
for (int index = 0; index < gv.getNumGlyphs(); index++) {
if (!gv.isGlyphVisible(index)) {
continue;
}
Point2D glyphPos = gv.getGlyphPosition(index);
AffineTransform glyphTransform = gv.getGlyphTransform(index);
int[] positionAdjust = ((index > glyphPositionAdjustments.length)
|| (glyphPositionAdjustments[index] == null)) ? PA_ZERO : glyphPositionAdjustments[index];
if (log.isTraceEnabled()) {
log.trace("pos " + glyphPos + ", transform " + glyphTransform);
}

positionGlyph(prevPos, glyphPos, glyphTransform != null || prevGlyphTransform != null);

char glyph = (char) gv.getGlyphCode(index);
double adjustedGlyphXPos = glyphXPos + positionAdjust[0];
double adjustedGlyphYPos = glyphYPos + positionAdjust[1];
double tdXPos = (adjustedGlyphXPos - prevAdjustedGlyphXPos) / 1000f;
double tdYPos = (adjustedGlyphYPos - prevAdjustedGlyphYPos) / 1000f;

textUtil.writeTd(tdXPos, tdYPos);

writeGlyph(glyph, getLocalTransform(glyphPos, glyphTransform));

//Update last position
prevPos = glyphPos;
prevGlyphTransform = glyphTransform;
glyphXPos = glyphPos.getX() + positionAdjust[2];
glyphYPos = glyphPos.getY() + positionAdjust[3];
prevAdjustedGlyphXPos = adjustedGlyphXPos;
prevAdjustedGlyphYPos = adjustedGlyphYPos;
}
}
}
Expand Down Expand Up @@ -193,9 +204,7 @@ protected void positionGlyph(Point2D prevPos, Point2D glyphPos, boolean repositi
|| reposition);
if (!repositionNextGlyph) {
double xdiff = glyphPos.getX() - prevPos.getX();
//Width of previous character
double cw = prevVisibleGlyphWidth;
double effxdiff = (1000 * xdiff) - cw;
double effxdiff = (1000 * xdiff) - prevVisibleGlyphWidth;
if (effxdiff != 0) {
double adjust = (-effxdiff / font.getFontSize());
textUtil.adjustGlyphTJ(adjust * 1000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ public int[][] getGlyphPositionAdjustments() {
return gposAdjustments;
}

public List getAssociations() {
return associations;
}

public Point2D getGlyphPosition(int glyphIndex) {
int positionIndex = glyphIndex * 2;
return new Point2D.Float(positions[positionIndex], positions[positionIndex + 1]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@
import java.awt.font.FontRenderContext;
import java.awt.geom.Point2D;
import java.text.AttributedCharacterIterator;
import java.util.List;

import org.apache.batik.bridge.GlyphLayout;
import org.apache.batik.gvt.font.GVTFont;
import org.apache.batik.gvt.font.GVTGlyphVector;
import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;

import org.apache.fop.complexscripts.util.CharAssociation;
import org.apache.fop.fonts.Font;
import org.apache.fop.svg.font.FOPGVTFont;
import org.apache.fop.svg.font.FOPGVTGlyphVector;

public class ComplexGlyphLayout extends GlyphLayout {

Expand All @@ -39,16 +41,21 @@ public ComplexGlyphLayout(AttributedCharacterIterator aci,
}

@Override
protected void doExplicitGlyphLayout() {
GVTGlyphVector gv = this.gv;
gv.performDefaultLayout();
int ng = gv.getNumGlyphs();
if (ng > 0) {
this.advance = gv.getGlyphPosition(ng);
} else {
this.advance = new Point2D.Float(0, 0);
protected int getAciIndex(int aciIndex, int loopIndex) {
if (gv instanceof FOPGVTGlyphVector) {
List associations = ((FOPGVTGlyphVector) gv).getAssociations();
// this method is called at the end of the cycle, therefore we still have the index of the current cycle
// since we are trying to determine the aci index for the next interation, we need to add 1 to the index
// the parent method does that automatically when it tries to get the character count
int nextIndex = loopIndex + 1;
if (nextIndex < associations.size() && associations.get(nextIndex) instanceof CharAssociation) {
CharAssociation association = (CharAssociation) associations.get(nextIndex);
return association.getStart();
}
}
this.layoutApplied = true;

//will only be used on the last iteration. the loop will stop after this and the value will not be used
return super.getAciIndex(aciIndex, loopIndex);
}

public static final boolean mayRequireComplexLayout(AttributedCharacterIterator aci) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
import java.awt.geom.GeneralPath;
import java.io.StringWriter;

import org.junit.Assert;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

Expand All @@ -38,6 +38,7 @@

import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontMetrics;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.fonts.base14.Base14FontCollection;
import org.apache.fop.pdf.PDFDocument;
Expand Down Expand Up @@ -164,16 +165,42 @@ public void testBaselineShift() throws Exception {
*/
@Test
public void testSingleByteAdjustments() throws Exception {
defaultSingleByteAdjustmentsTest(1, "BT\n"
+ "3 Tr\n"
+ "0.002 0.003 Td\n"
+ "/F5 0.012 Tf\n"
+ "1 0 0 -1 0 0 Tm [(\\0)] TJ\n"
+ "ET\n");
}

@Test
public void testSingleByteAdjustmentsMultipleGlyphs() throws Exception {
defaultSingleByteAdjustmentsTest(3, "BT\n"
+ "3 Tr\n"
+ "0.002 0.003 Td\n"
+ "/F5 0.012 Tf\n"
+ "1 0 0 -1 0 0 Tm 0.008 0.009 Td\n"
+ "[(\\0)] TJ\n"
+ "1 0 0 -1 1 1 Tm 0.009 0.009 Td\n"
+ "[(\\1)] TJ\n"
+ "1 0 0 -1 2 2 Tm [(\\2)] TJ\n"
+ "ET\n");
}

private void defaultSingleByteAdjustmentsTest(int numberOfGlyphs, String expected) throws Exception {
FontInfo fontInfo = new FontInfo();
new Base14FontCollection(true).setup(0, fontInfo);

PDFTextPainter painter = new PDFTextPainter(fontInfo);
TextPaintInfo tpi = new TextPaintInfo();
tpi.visible = true;

PDFGraphics2D g2d = mock(PDFGraphics2D.class);
g2d.fontInfo = fontInfo;
g2d.currentStream = new StringWriter();

PDFTextPainter painter = new PDFTextPainter(fontInfo);
painter.preparePainting(g2d);
painter.setInitialTransform(new AffineTransform());
TextPaintInfo tpi = new TextPaintInfo();
tpi.visible = true;
painter.tpi = tpi;
painter.beginTextObject();

Expand All @@ -182,24 +209,35 @@ public void testSingleByteAdjustments() throws Exception {
Font font = fontInfo.getFontInstance(triplet, 12);

FOPGVTFont mockGvtFont = mock(FOPGVTFont.class);
org.apache.fop.fonts.FontMetrics fontMetrics = font.getFontMetrics();
when(mockGvtFont.getFont()).thenReturn(new Font("Times", triplet, fontMetrics, 12));
when(mockGvtFont.getFontKey()).thenReturn("Times");
FontMetrics fontMetrics = font.getFontMetrics();
when(mockGvtFont.getFont()).thenReturn(new Font("F5", triplet, fontMetrics, 12));
when(mockGvtFont.getFontKey()).thenReturn("F5");

when(mockGV.getFont()).thenReturn(mockGvtFont);
when(mockGV.getGlyphPositionAdjustments()).thenReturn(new int[][] {{2, 3, 4, 5}, {6, 7, 8, 9}});
when(mockGV.getGlyphPosition(0)).thenReturn(new Point(0, 0));
when(mockGV.getNumGlyphs()).thenReturn(1);
when(mockGV.getGlyphCode(0)).thenReturn(1);
addGlyphs(mockGV, numberOfGlyphs);

GeneralPath gp = new GeneralPath();

painter.writeGlyphs(mockGV, gp);
Assert.assertEquals("BT\n"
+ "3 Tr\n"
+ "1 0 0 -1 0 0 Tm /Times 0.012 Tf\n"
+ "0.002 0.003 Td\n"
+ "(\\1) Tj\n",
g2d.currentStream.toString());
painter.endTextObject();

assertEquals("Must write a Text Matrix for each glyph and use the glyph adjustments",
expected, g2d.currentStream.toString());
}

private void addGlyphs(FOPGVTGlyphVector mockGV, int max) {
int[][] array = new int[max][4];
for (int i = 0; i < max; i++) {
when(mockGV.getGlyphPosition(i)).thenReturn(new Point(i, i));
when(mockGV.getNumGlyphs()).thenReturn(max);
when(mockGV.getGlyphCode(i)).thenReturn(i);
when(mockGV.isGlyphVisible(i)).thenReturn(true);
array[i][0] = 2 + i * 4;
array[i][1] = 3 + i * 4;
array[i][2] = 4 + i * 4;
array[i][3] = 5 + i * 4;
}

when(mockGV.getGlyphPositionAdjustments()).thenReturn(array);
}
}
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<properties>
<ant.version>1.10.14</ant.version>
<antrun.plugin.version>1.8</antrun.plugin.version>
<batik.version>1.17.0-SNAPSHOT</batik.version>
<batik.version>1.18.0-SNAPSHOT</batik.version>
<build.helper.plugin.version>1.9.1</build.helper.plugin.version>
<checkstyle.plugin.version>2.14</checkstyle.plugin.version>
<commons.io.version>2.17.0</commons.io.version>
Expand Down

0 comments on commit 8c1be2a

Please sign in to comment.