Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use operators Ts and TJ for glyph layout. Some refactorings. #1114

Merged
merged 5 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

name: OpenPDF maven build
on:
workflow_dispatch:
push:
branches: [ '*' ]
pull_request:
Expand Down
1 change: 1 addition & 0 deletions openpdf/src/main/java/com/lowagie/text/pdf/BaseFont.java
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ public static BaseFont createFont(String name, String encoding,
if (cached) {
fontFound = fontCache.get(key);
if (fontFound != null) {
LayoutProcessor.loadFont(fontFound, name);
return fontFound;
}
}
Expand Down
47 changes: 41 additions & 6 deletions openpdf/src/main/java/com/lowagie/text/pdf/FontDetails.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import com.lowagie.text.Utilities;
import java.awt.font.GlyphVector;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

Expand Down Expand Up @@ -155,6 +156,18 @@ void putFillerCmap(Integer key, int[] value) {
fillerCmap.put(key, value);
}

void addMissingCmapEntries(String text, GlyphVector glyphVector, BaseFont baseFont) {

if (baseFont instanceof TrueTypeFontUnicode trueTypeFont && getFillerCmap() != null) {
int[][] localCmap = trueTypeFont.getSentenceMissingCmap(text, glyphVector);

for (int[] ints : localCmap) {
putFillerCmap(ints[0], new int[]{ints[0], ints[1]});
}
}
}


/**
* Gets the indirect reference to this font.
*
Expand Down Expand Up @@ -291,6 +304,32 @@ private byte[] getCJKEncodingBytes(int[] glyph, int size) {
return result;
}

/**
* Convert a glyph code to bytes
*
* @param glyphCode
* @return byte array with one or two bytes as UTF-16BE representation of the glyph code
* @see <CODE>convertToBytes(GlyphVector glyphVector,...)</CODE>
*/
byte[] convertToBytes(final int glyphCode) {
if (fontType != BaseFont.FONT_TYPE_TTUNI) {
throw new UnsupportedOperationException("Only supported for True Type Unicode fonts");
}
if (glyphCode == 0xFFFE || glyphCode == 0xFFFF) {
// considered non-glyphs by AWT
return new byte[]{};
}
if (!longTag.containsKey(glyphCode)) {
int glyphWidth = ttu.getGlyphWidth(glyphCode);
Integer charCode = ttu.getCharacterCode(glyphCode);
int[] metrics = charCode != null ? new int[]{glyphCode, glyphWidth, charCode} : new int[]{
glyphCode, glyphWidth};
longTag.put(glyphCode, metrics);
}
String s = new String(Character.toChars(glyphCode));
return s.getBytes(StandardCharsets.UTF_16BE);
}

byte[] convertToBytes(GlyphVector glyphVector, int beginIndex, int endIndex) {
if (fontType != BaseFont.FONT_TYPE_TTUNI || symbolic) {
throw new UnsupportedOperationException("Only supported for True Type Unicode fonts");
Expand All @@ -305,7 +344,7 @@ byte[] convertToBytes(GlyphVector glyphVector, int beginIndex, int endIndex) {
continue;
}

glyphs[glyphCount++] = (char) code; // FIXME supplementary plane?
glyphCount += Character.toChars(code, glyphs, glyphCount);

Integer codeKey = code;
if (!longTag.containsKey(codeKey)) {
Expand All @@ -318,11 +357,7 @@ byte[] convertToBytes(GlyphVector glyphVector, int beginIndex, int endIndex) {
}

String s = new String(glyphs, 0, glyphCount);
try {
return s.getBytes(CJKFont.CJK_ENCODING);
} catch (UnsupportedEncodingException e) {
throw new ExceptionConverter(e);
}
return s.getBytes(StandardCharsets.UTF_16BE);
}


Expand Down
140 changes: 127 additions & 13 deletions openpdf/src/main/java/com/lowagie/text/pdf/LayoutProcessor.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* LayoutProcessor.java
*
* Copyright 2020-2022 Volker Kunert.
* Copyright 2020-2024 Volker Kunert.
*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -65,6 +65,13 @@
*/
public class LayoutProcessor {

public enum Version {
ONE,
TWO
}

private static Version version = Version.TWO;

private static final int DEFAULT_FLAGS = -1;
private static final Map<BaseFont, java.awt.Font> awtFontMap = new ConcurrentHashMap<>();

Expand All @@ -74,6 +81,8 @@ public class LayoutProcessor {
private static boolean enabled = false;
private static int flags = DEFAULT_FLAGS;

private static boolean writeActualText;

private LayoutProcessor() {
throw new UnsupportedOperationException("static class");
}
Expand Down Expand Up @@ -132,6 +141,17 @@ public static boolean isEnabled() {
return enabled;
}

/**
* Set version
*
* @param version to set
* @deprecated To be used *only*, if version two produces incorrect PDF - please file an issue if this occurs
*/
@Deprecated
public static void setVersion(Version version) {
LayoutProcessor.version = version;
}

/**
* Set kerning
*
Expand Down Expand Up @@ -208,8 +228,8 @@ private static void setRunDirection(com.lowagie.text.Font font, Boolean runDirec
*
* @param font The font for which kerning is to be turned on
* @param textAttributes Map of text attributes to be set
* @see <a href="https://docs.oracle.com/javase/tutorial/2d/text/textattributes.html" >Oracle: The Java™ Tutorials,
* Using Text Attributes to Style Text</a>
* @see <a href="https://docs.oracle.com/javase/tutorial/2d/text/textattributes.html">
* Oracle: The Java™ Tutorials, Using Text Attributes to Style Text</a>
*/
private static void setTextAttributes(com.lowagie.text.Font font, Map<TextAttribute, Object> textAttributes) {
BaseFont baseFont = font.getBaseFont();
Expand All @@ -220,10 +240,26 @@ private static void setTextAttributes(com.lowagie.text.Font font, Map<TextAttrib
}
}

/**
* Include ACTUALTEXT in PDF
*/
public static void setWriteActualText() {
writeActualText = true;
}

public static int getFlags() {
return flags;
}

/**
* Returns the currennt version
*
* @return current version
*/
public static Version getVersion() {
return LayoutProcessor.version;
}

public static boolean isSet(int queryFlags) {
return flags != DEFAULT_FLAGS && (flags & queryFlags) == queryFlags;
}
Expand All @@ -240,7 +276,7 @@ public static boolean supportsFont(BaseFont baseFont) {
* @throws RuntimeException if font can not be loaded
*/
public static void loadFont(BaseFont baseFont, String filename) {
if (!enabled) {
if (!enabled || awtFontMap.get(baseFont) != null) {
return;
}

Expand Down Expand Up @@ -328,7 +364,7 @@ public static GlyphVector computeGlyphVector(BaseFont baseFont, float fontSize,
* @param glyphVector glyph vector containing the positions
* @return true, if the glyphVector contains adjustments
*/
private static boolean hasAdjustments(GlyphVector glyphVector) {
private static boolean noAdjustments(GlyphVector glyphVector) {
boolean retVal = false;
float lastX = 0f;
float lastY = 0f;
Expand All @@ -348,7 +384,7 @@ private static boolean hasAdjustments(GlyphVector glyphVector) {
lastX = (float) p.getX();
lastY = (float) p.getY();
}
return retVal;
return !retVal;
}

/**
Expand All @@ -361,8 +397,26 @@ private static boolean hasAdjustments(GlyphVector glyphVector) {
* @return layout position correction to correct the start of the next line
*/
public static Point2D showText(PdfContentByte cb, BaseFont baseFont, float fontSize, String text) {

if (LayoutProcessor.version == Version.ONE) {
return showText1(cb, baseFont, fontSize, text);
} else {
return showText2(cb, baseFont, fontSize, text);
}
}


private static void completeCmap(PdfContentByte cb, BaseFont baseFont, String text, GlyphVector glyphVector) {
cb.state.fontDetails.addMissingCmapEntries(text, glyphVector, baseFont);
}


@Deprecated
private static Point2D showText1(PdfContentByte cb, BaseFont baseFont, float fontSize, String text) {
GlyphVector glyphVector = computeGlyphVector(baseFont, fontSize, text);
if (!hasAdjustments(glyphVector)) {
completeCmap(cb, baseFont, text, glyphVector);

if (noAdjustments(glyphVector)) {
cb.showText(glyphVector);
Point2D p = glyphVector.getGlyphPosition(glyphVector.getNumGlyphs());
float dx = (float) p.getX();
Expand Down Expand Up @@ -391,21 +445,81 @@ public static Point2D showText(PdfContentByte cb, BaseFont baseFont, float fontS
float dy = (float) p.getY() - lastY;
cb.moveTextBasic(dx, -dy);

if (baseFont instanceof TrueTypeFontUnicode trueTypeFont && cb.state.fontDetails.getFillerCmap() != null) {
int[][] localCmap = trueTypeFont.getSentenceMissingCmap(text.toCharArray(), glyphVector);
return new Point2D.Double(-p.getX(), p.getY());
}

for (int[] ints : localCmap) {
cb.state.fontDetails.putFillerCmap(ints[0], new int[]{ints[0], ints[1]});
}

private static Point2D showText2(PdfContentByte cb, BaseFont baseFont, float fontSize, String text) {
GlyphVector glyphVector = computeGlyphVector(baseFont, fontSize, text);
completeCmap(cb, baseFont, text, glyphVector);

if (writeActualText) {
PdfDictionary d = new PdfDictionary();
d.put(PdfName.ACTUALTEXT, new PdfString(text, PdfObject.TEXT_UNICODE));
cb.beginMarkedContentSequence(PdfName.SPAN, d, true);
}
if (noAdjustments(glyphVector)) {
cb.showText(glyphVector);
} else {
adjustAndShowText(cb, fontSize, glyphVector);
}
if (writeActualText) {
cb.endMarkedContentSequence();
}
return new Point2D.Double(0.0, 0.0);
}

return new Point2D.Double(-p.getX(), p.getY());

private static void adjustAndShowText(PdfContentByte cb, final float fontSize, final GlyphVector glyphVector) {

final float deltaY = 0.001f;
final float deltaX = deltaY * 1000f / fontSize;
final float factorX = 1000f / fontSize;

float lastX = 0f;

PdfGlyphArray ga = new PdfGlyphArray();

for (int i = 0; i < glyphVector.getNumGlyphs(); i++) {
Point2D p = glyphVector.getGlyphPosition(i);
float ax = (i == 0) ? 0.0f : glyphVector.getGlyphMetrics(i - 1).getAdvanceX();
float dx = (float) p.getX() - lastX - ax;
float py = (float) p.getY();

if (Math.abs(py) >= deltaY) {
if (!ga.isEmpty()) {
cb.showText(ga);
ga.clear();
}
cb.setTextRise(-py);
}
if (Math.abs(dx) >= deltaX) {
ga.add(-dx * factorX);
}
ga.add(glyphVector.getGlyphCode(i));
if (Math.abs(py) >= deltaY) {
cb.showText(ga);
ga.clear();
cb.setTextRise(0.0f);
}
lastX = (float) p.getX();
}
Point2D p = glyphVector.getGlyphPosition(glyphVector.getNumGlyphs());
float ax = (glyphVector.getNumGlyphs() == 0) ? 0.0f : glyphVector.getGlyphMetrics(glyphVector.getNumGlyphs() - 1).getAdvanceX();
float dx = (float) p.getX() - lastX - ax;
if (Math.abs(dx) >= deltaX) {
ga.add(-dx * factorX);
}
cb.showText(ga);
ga.clear();
}

public static void disable() {
enabled = false;
flags = DEFAULT_FLAGS;
awtFontMap.clear();
globalTextAttributes.clear();
writeActualText = false;
setVersion(Version.TWO);
}
}
Loading
Loading