首先,在您的问题中,您只谈论 如何旋转特定文本,但在您的示例中,您还旋转了一个红色矩形。这个答案侧重于旋转文本。猜测哪些图形可能与文本相关并因此可能应该旋转的过程本身就是一个主题。
您还提到您正在寻找解决方案使用 itext 或 pdfbox 并使用标签 itext、pdfbox 和 itext7。对于这个答案,我选择了 iText 7。
您没有解释要旋转的文本类型,但提供了一个具有代表性的 PDF 示例。在该示例中,我看到要旋转的文本是使用单个文本显示指令绘制的,这是页面内容流中包含的文本对象中唯一的此类指令。因此,为了使答案中的代码保持简单,我可以假设要旋转的文本是在页面内容流中的文本对象中显示指令的连续文本序列中绘制的,这些指令不是显示文本的指令。这是您的案例的概括。
此外,您没有提到旋转中心。根据您的示例文件,我假设它大约是要旋转的文本基线的开始。
一个简单的实现
在编辑 PDF 内容流时,了解每条指令的当前图形状态会很有帮助,例如要正确识别由文本显示操作绘制的文本,需要知道当前字体以将字符代码映射到 Unicode 字符。 iText 中的文本提取框架已经包含遵循图形状态的代码。因此,在this answer 中,在文本提取框架之上开发了一个基础PdfCanvasEditor 类。
在一个小的扩展之后,我们可以在该类上为手头的任务提供一个解决方案;该类最初将文本提取事件侦听器设置为虚拟实现,但在这里我们需要一个自定义的。所以我们需要添加一个额外的构造函数来接受这样一个自定义事件监听器作为参数:
public PdfCanvasEditor(IEventListener listener)
{
super(listener);
}
(额外的PdfCanvasEditor构造函数)
基于这个扩展的PdfCanvasEditor,我们可以通过逐条检查现有的页面内容流来实现任务。对于显示指令的连续文本序列,我们检索序列前后的文本矩阵,如果序列绘制的文本结果是要旋转的文本,我们在该序列之前插入一条指令,将初始文本矩阵设置为其自身的旋转版本和该序列之后的另一个版本,将文本矩阵设置回原来的位置。
我们的实现LimitedTextRotater 接受代表所需旋转的Matrix 和匹配要旋转的字符串的Predicate。
public class LimitedTextRotater extends PdfCanvasEditor {
public LimitedTextRotater(Matrix rotation, Predicate<String> textMatcher) {
super(new TextRetrievingListener());
((TextRetrievingListener)getEventListener()).limitedTextRotater = this;
this.rotation = rotation;
this.textMatcher = textMatcher;
}
@Override
protected void write(PdfCanvasProcessor processor, PdfLiteral operator, List<PdfObject> operands) {
String operatorString = operator.toString();
if (TEXT_SHOWING_OPERATORS.contains(operatorString)) {
recentTextOperations.add(new ArrayList<>(operands));
} else {
if (!recentTextOperations.isEmpty()) {
boolean rotate = textMatcher.test(text.toString());
if (rotate)
writeSetTextMatrix(processor, rotation.multiply(initialTextMatrix));
for (List<PdfObject> recentOperation : recentTextOperations) {
super.write(processor, (PdfLiteral) recentOperation.get(recentOperation.size() - 1), recentOperation);
}
if (rotate)
writeSetTextMatrix(processor, finalTextMatrix);
recentTextOperations.clear();
text.setLength(0);
initialTextMatrix = null;
}
super.write(processor, operator, operands);
}
}
void writeSetTextMatrix(PdfCanvasProcessor processor, Matrix textMatrix) {
PdfLiteral operator = new PdfLiteral("Tm\n");
List<PdfObject> operands = new ArrayList<>();
operands.add(new PdfNumber(textMatrix.get(Matrix.I11)));
operands.add(new PdfNumber(textMatrix.get(Matrix.I12)));
operands.add(new PdfNumber(textMatrix.get(Matrix.I21)));
operands.add(new PdfNumber(textMatrix.get(Matrix.I22)));
operands.add(new PdfNumber(textMatrix.get(Matrix.I31)));
operands.add(new PdfNumber(textMatrix.get(Matrix.I32)));
operands.add(operator);
super.write(processor, operator, operands);
}
void eventOccurred(TextRenderInfo textRenderInfo) {
Matrix textMatrix = textRenderInfo.getTextMatrix();
if (initialTextMatrix == null)
initialTextMatrix = textMatrix;
finalTextMatrix = new Matrix(textRenderInfo.getUnscaledWidth(), 0).multiply(textMatrix);
text.append(textRenderInfo.getText());
}
static class TextRetrievingListener implements IEventListener {
@Override
public void eventOccurred(IEventData data, EventType type) {
if (data instanceof TextRenderInfo) {
limitedTextRotater.eventOccurred((TextRenderInfo) data);
}
}
@Override
public Set<EventType> getSupportedEvents() {
return null;
}
LimitedTextRotater limitedTextRotater;
}
final static List<String> TEXT_SHOWING_OPERATORS = Arrays.asList("Tj", "'", "\"", "TJ");
final Matrix rotation;
final Predicate<String> textMatcher;
final List<List<PdfObject>> recentTextOperations = new ArrayList<>();
final StringBuilder text = new StringBuilder();
Matrix initialTextMatrix = null;
Matrix finalTextMatrix = null;
}
(LimitedTextRotater)
您可以将它应用到这样的文档中:
try ( PdfReader pdfReader = new PdfReader(...);
PdfWriter pdfWriter = new PdfWriter(...);
PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter) )
{
PdfCanvasEditor editor = new LimitedTextRotater(new Matrix(0, -1, 1, 0, 0, 0), text -> true);
for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++){
editor.editPage(pdfDocument, i);
}
}
(RotateText 测试testBeforeAkhilNagaSai)
这里使用的Predicate 是匹配任何文本的text -> true。如果您的示例 PDF 可以,因为要旋转的文本是唯一的文本。一般来说,您可能需要更具体的检查,例如text -> text.equals("The text to be rotated")。不过,一般尽量不要太具体,因为提取的文本可能会稍微偏离预期,例如通过额外的空格。
结果:
如您所见,文本已旋转。但是,与您的 After.pdf 相比,红色矩形没有旋转。原因是——正如开头已经提到的——那个矩形绝不是文本的一部分。
一些想法
首先,有PdfCanvasEditor 到iText 5(this answer 中的PdfContentStreamEditor)和PDFBox(this answer 中的PdfContentStreamEditor)的端口。因此,如果您最终希望切换到这些 PDF 库中的任何一个,您可以创建等效的实现。
然后,如果假设 要旋转的文本是在页面内容流中的文本对象中显示指令的连续文本序列中绘制的,该文本对象中的指令框架不是显示文本的指令为你保留,你可以在这里概括一下实现。查看this answer 中的SimpleTextRemover 以获取灵感,该灵感基于iText 5 的PdfContentStreamEditor。这里还处理从一个文本中某处开始显示指令并在另一个文本中某处结束的文本,这需要一些更详细的信息现有文本绘图指令的数据保存和拆分。
此外,如果您想旋转图形以及人类查看者可能认为与之关联的文本(例如示例文件中的红色矩形),您可以尝试相应地扩展示例,例如通过还提取旋转文本的坐标,并在第二次运行中尝试猜测这些坐标周围的哪些图形是相关的并沿旋转图形。不过,这并非微不足道。
最后注意,构造函数中提供的Matrix不限于旋转,它可以表示任意的仿射变换。因此,除了旋转文本,您还可以移动它、缩放它或倾斜它,...