Поскольку я больше дома с Java, чем с .Net, я проанализировал проблему и создал первый обходной путь в PDF Clown / Java;Я попробую портировать его на .Net позже.Тем не менее, это не должно быть слишком сложно, сделать это самостоятельно.
Проблема
Приведенный вами пример файла ясно показывает проблему при запуске его через PDF Clown TextInfoExtractionSample
.
Снимок экрана edit9.pdf
:
Снимок экрана edit9.pdf
после применения TextInfoExtractionSample
:
TextInfoExtractionSample">
Вертикальный текст
Все выглядит хорошо.
Перевернутый текст
Поля для отдельных символов (зеленый) выглядит хорошо, но поле для всей строки "inverted_text" (пунктирный черный) исключает самые крайние символы.
Вертикальный текст
Размер отдельных полей для символов сокращается до 0x0 прямоугольников (невидимых вснимок экрана, но очевидный в анализе потока контента).Поле для всей строки уменьшается до линии (пунктирная черная) на базовой линии строки, в которой отсутствует длина в битах.
Текст под углами между
Поля для символов расположены вертикально, параллельно границам страницы, с сегментом базовой линии внутри рамки.Поскольку текст расположен под углом, верхняя и нижняя части символов частично находятся за пределами своего соответствующего поля символов, а соседние символы частично находятся внутри.
Блоки для целых строк также параллельны странице.
В двух словах
Текстовые поля и строковые блоки работают правильно только для вертикального текста.
В источниках
Это соответствует тому, что можно найти висходный код:
Классы Java Rectangle2D
и .Net RectangleF
, используемые для блоков символов при разработке, предназначены для прямоугольников, параллельных осям системы координат, и используются в нихманера в PDF клоун.Таким образом, они не могут правильно представлять ширину и высоту символов под произвольными углами.
Классы PDF Clown не включают атрибут Angle
для представления поворотасимвол.
При расчете размеров блока символов учитываются только значения на главной диагонали агрегированной матрицы преобразования, т. е. ScaleX
и ScaleY
, и игнорируется ShearX
и ShearY
.Для текста, который не является вертикальным или перевернутым, однако, ShearX
и ShearY
важны, для вертикального текста ScaleX
и ScaleY
равны 0.
Переход отБазовая линия (собственный способ позиционирования текста в формате PDF) и верхняя часть символа (позиционирование текста в формате PDF Clown) выполняется только путем изменения координаты y и, следовательно, работает правильно только для текста в вертикальном и обратном направлениях.
Обходное решение
Реальное решение проблемы потребовало бы использования совершенно другого класса для блоков символов и строк, класса, который моделирует прямоугольники под произвольными углами.
Aоднако более быстрый обходной путь может заключаться в добавлении члена angle
к классу TextChar
и к ITextString
и реализациям, а затем к рассмотрению этого угла при обработке блоков.Этот обходной путь реализован здесь.
Как уже упоминалось выше, обходной путь сначала реализуется в Java.
В Java
Firstмы добавляем угловой элемент к TextChar
, вычисляем правильные значения для размеров блока и угла в ShowText
классе операций и корректно устанавливаем эти значения в ContentScanner.TextStringWrapper
.
Затем добавляем получатель угла кTextStringWrapper
(и ITextString
в целом), который возвращает угол первого текстового символа строки.И мы улучшаем метод TextStringWrapper
getBox
, чтобы учитывать угол символов текста при определении строкового блока.
Наконец, мы расширим TextInfoExtractionSample
, чтобы учитывать значения углов, когдарисование коробок.
Я назвал этот угловой элемент Alpha
, как я назвал этот угол α в своих эскизах.Оглядываясь назад, Theta
или просто Angle
было бы более уместным.
TextChar
Новая переменная-член alpha
private final double alpha;
Новый иизмененный конструктор
// <constructors>
public TextChar(
char value,
Rectangle2D box,
TextStyle style,
boolean virtual
)
{
this(value, box, 0, style, virtual);
}
public TextChar(
char value,
Rectangle2D box,
double alpha,
TextStyle style,
boolean virtual
)
{
this.value = value;
this.box = box;
this.alpha = alpha;
this.style = style;
this.virtual = virtual;
}
// </constructors>
метод получения угла
public double getAlpha() {
return alpha;
}
( TextChar.java )
ShowText
Обновление внутреннего интерфейса IScanner
метод scanChar
для переноса угла
void scanChar(
char textChar,
Rectangle2D textCharBox,
double alpha
);
( ShowText.java внутренний интерфейс IScanner
)
Обновлен метод scan
для правильного расчета размеров и угла прямоугольника и их перенаправления в IScanner
реализацию
[...]
for(char textChar : textString.toCharArray())
{
double charWidth = font.getWidth(textChar) * scaledFactor;
if(textScanner != null)
{
/*
NOTE: The text rendering matrix is recomputed before each glyph is painted
during a text-showing operation.
*/
AffineTransform trm = (AffineTransform)ctm.clone(); trm.concatenate(tm);
double charHeight = font.getHeight(textChar,fontSize);
// vvv--- changed
double ascent = font.getAscent(fontSize);
double x = trm.getTranslateX() + ascent * trm.getShearX();
double y = contextHeight - trm.getTranslateY() - ascent * trm.getScaleY();
double dx = charWidth * trm.getScaleX();
double dy = charWidth * trm.getShearY();
double alpha = Math.atan2(dy, dx);
double w = Math.sqrt(dx*dx + dy*dy);
dx = charHeight * trm.getShearX();
dy = charHeight * trm.getScaleY();
double h = Math.sqrt(dx*dx + dy*dy);
Rectangle2D charBox = new Rectangle2D.Double(x, y, w, h);
textScanner.scanChar(textChar,charBox, alpha);
// ^^^--- changed
}
/*
NOTE: After the glyph is painted, the text matrix is updated
according to the glyph displacement and any applicable spacing parameter.
*/
tm.translate(charWidth + charSpace + (textChar == ' ' ? wordSpace : 0), 0);
}
[...]
( ShowText.java )
Внутренний класс ContentScanner TextStringWrapper
Обновить TextStringWrapper
конструктор ShowText.IScanner
обратный вызов, чтобы принять аргумент угла и использовать его для построения TextChar
getBaseDataObject().scan(
state,
new ShowText.IScanner()
{
@Override
public void scanChar(
char textChar,
Rectangle2D textCharBox,
double alpha
)
{
textChars.add(
new TextChar(
textChar,
textCharBox,
alpha,
style,
false
)
);
}
}
);
Получатель для угла
public double getAlpha() {
return textChars.isEmpty() ? 0 : textChars.get(0).getAlpha();
}
A getBox
реализация, учитывающая угол
public Rectangle2D getBox(
)
{
if(box == null)
{
AffineTransform rot = null;
Rectangle2D tempBox = null;
for(TextChar textChar : textChars)
{
Rectangle2D thisBox = textChar.getBox();
if (rot == null) {
rot = AffineTransform.getRotateInstance(textChar.getAlpha(), thisBox.getX(), thisBox.getY());
tempBox = (Rectangle2D)thisBox.clone();
} else {
Point2D corner = new Point2D.Double(thisBox.getX(), thisBox.getY());
rot.transform(corner, corner);
tempBox.add(new Rectangle2D.Double(corner.getX(), corner.getY(), thisBox.getWidth(), thisBox.getHeight()));
}
}
if (tempBox != null) {
try {
Point2D corner = new Point2D.Double(tempBox.getX(), tempBox.getY());
rot.invert();
rot.transform(corner, corner);
box = new Rectangle2D.Double(corner.getX(), corner.getY(), tempBox.getWidth(), tempBox.getHeight());
} catch (NoninvertibleTransformException e) {
e.printStackTrace();
}
}
}
return box;
}
( ContentScanner.java внутренний класс TextStringWrapper
)
ITextString
Новый метод получения углов
public double getAlpha();
( ITextString.java)
TextExtractor внутренний класс TextString
Новый получатель угла
public double getAlpha() {
return textChars.isEmpty() ? 0 : textChars.get(0).getAlpha();
}
( TextExtractor.java )
TextInfoExtractionSample
Меняется на extract
для правильного использования угла при выделении полей
[...]
for (ContentScanner.TextStringWrapper textString : text.getTextStrings())
{
Rectangle2D textStringBox = textString.getBox();
System.out.println("Text [" + "x:" + Math.round(textStringBox.getX()) + "," + "y:" + Math.round(textStringBox.getY()) + "," + "w:"
+ Math.round(textStringBox.getWidth()) + "," + "h:" + Math.round(textStringBox.getHeight()) + "] [font size:"
+ Math.round(textString.getStyle().getFontSize()) + "]: " + textString.getText());
// Drawing text character bounding boxes...
colorIndex = (colorIndex + 1) % textCharBoxColors.length;
composer.setStrokeColor(textCharBoxColors[colorIndex]);
for (TextChar textChar : textString.getTextChars())
{
// vvv--- changed
Rectangle2D box = textChar.getBox();
composer.beginLocalState();
AffineTransform rot = AffineTransform.getRotateInstance(textChar.getAlpha());
composer.applyMatrix(rot.getScaleX(), rot.getShearY(), rot.getShearX(), rot.getScaleY(),
box.getX(), composer.getScanner().getContextSize().getHeight() - box.getY());
composer.add(new DrawRectangle(0, - box.getHeight(), box.getWidth(), box.getHeight()));
composer.stroke();
composer.end();
// ^^^--- changed
}
// Drawing text string bounding box...
composer.beginLocalState();
composer.setLineDash(new LineDash(new double[] { 5 }));
composer.setStrokeColor(textStringBoxColor);
// vvv--- changed
AffineTransform rot = AffineTransform.getRotateInstance(textString.getAlpha());
composer.applyMatrix(rot.getScaleX(), rot.getShearY(), rot.getShearX(), rot.getScaleY(),
textStringBox.getX(), composer.getScanner().getContextSize().getHeight() - textStringBox.getY());
composer.add(new DrawRectangle(0, - textStringBox.getHeight(), textStringBox.getWidth(), textStringBox.getHeight()));
// ^^^--- changed
composer.stroke();
composer.end();
}
[...]
( TextInfoExtractionSample методextract
)
Результат
И текстовые поля, и строковые блоки теперь соответствуют назначению:
Значения ширины и высоты теперь тоже в порядке:
Text [x:415,y:104,w:138,h:23] [font size:-24]: inverted_text
Text [x:247,y:365,w:128,h:23] [font size:0]: vertical_text
Text [x:364,y:131,w:180,h:23] [font size:0]: vertical_minus90