Рассчитать правильную ширину текста

Мне нужно прочитать план, экспортированный AutoCAD в PDF, и разместить на нем несколько маркеров с текстом с помощью PDFBox. Все работает нормально, кроме расчета ширины текста, который написан рядом с маркерами.

Я просмотрел всю спецификацию PDF и подробно прочитал части, которые касаются графики и текста, но безрезультатно. Насколько я понимаю, пространство координат глифа устанавливается в 1/1000 пользовательского пространства координат. Следовательно, ширину необходимо увеличить на 1000, но это все равно часть реальной ширины.

Вот что я делаю, чтобы разместить текст:

float textWidth = font.getStringWidth(marker.id) * 0.043f;
contentStream.beginText();
contentStream.setTextScaling(1, 1, 0, 0);
contentStream.moveTextPositionByAmount(
  marker.endX + marker.getXTextOffset(textWidth, fontPadding),
  marker.endY + marker.getYTextOffset(fontSize, fontPadding));
contentStream.drawString(marker.id);
contentStream.endText();

* 0.043f работает как приближение для одного документа, но не работает для следующего. Нужно ли мне сбрасывать любую другую матрицу преобразования, кроме текстовой?

РЕДАКТИРОВАТЬ: Пример проекта с полной идеей находится на github с тестами и примерами файлов PDF: https://github.com/ascheucher/pdf-stamp-prototype

Спасибо за вашу помощь!


person andreas    schedule 22.12.2014    source источник
comment
Можете ли вы поделиться образцами документов (например, одним из которых работает ваш код, а другим - нет) и другим кодом, особенно в отношении методов маркеров и того, как вы начинаете редактировать поток контента?   -  person mkl    schedule 23.12.2014
comment
@mkl: я отправил код на github. тесты и тестовые данные включены.   -  person andreas    schedule 23.12.2014
comment
Я посмотрю позже. В настоящее время делаю покупки на Рождество. ;)   -  person mkl    schedule 23.12.2014
comment
Не торопитесь, в разгар рождественских сборов. До января все равно не успеем ... Но заранее спасибо! Хорошего Рождества для вас и вашей семьи!   -  person andreas    schedule 23.12.2014
comment
Спасибо. Надеюсь, вы отлично провели время в отпуске. Я сейчас изучаю образец. Я использую maven, а не идею, поэтому потребовалось несколько минутных исправлений. Не могли бы вы указать, какой тест показывает неудачу, а какой - успех? Поскольку вы устанавливаете большинство тестов на @Ignore), предполагаем, что оставшиеся два теста демонстрируют проблему, не так ли?   -  person mkl    schedule 05.01.2015
comment
привет, @mkl. праздник прошел хорошо :) спасибо. Набор тестов для игнорирования либо тестирует другое поведение, либо использует тестовые файлы pdf, которые я не мог включить. Активные тесты рисуют маркер на двух разных планах. У них разный размер, отсюда и разный масштаб. Аннотирующий текст должен быть выровнен по центру верхнего и нижнего маркеров, выровнен по левому краю правого маркера и выровнен по правому краю левого маркера. Выравнивание у меня не работает, так как font.getSTringWidth (..) возвращает только часть того, чем кажется. И расхождение кажется разным в обоих PDF-файлах.   -  person andreas    schedule 05.01.2015
comment
Какую программу просмотра вы используете? Я пытаюсь просмотреть результаты теста с помощью Adobe Reader XI, но он сообщает мне: На этой странице существует ошибка. Acrobat может некорректно отображать страницу. После этого он показывает только исходные планы, по крайней мере, я не вижу маркеров вообще.   -  person mkl    schedule 06.01.2015
comment
А, причина этой ошибки в том, что вы используете цветовое пространство CalRGB без WhitePoint (что является обязательным значением). Однако, поскольку вы после этого будете использовать DeviceRGB цвета, это не имеет значения.   -  person mkl    schedule 06.01.2015
comment
Ok. Я использовал родную программу просмотра PDF-файлов Ubuntu. Он не жаловался, но это полезно знать.   -  person andreas    schedule 06.01.2015


Ответы (1)


К сожалению, вопрос и комментарии просто включают (путем запуска образца проекта) фактический результат для двух исходных документов и описание.

Аннотирующий текст должен быть выровнен по центру верхнего и нижнего маркеров, выровнен по левому краю правого маркера и выровнен по правому краю левого маркера. Выравнивание у меня не работает, так как font.getSTringWidth (..) возвращает только часть того, чем кажется. И расхождение кажется разным в обоих PDF-файлах.

но не конкретный образец несоответствия для ремонта.

Однако в коде есть несколько проблем, которые могут привести к таким наблюдениям (и другим!). Исправить их следует в первую очередь; это может уже решить проблемы, обнаруженные OP.

Какую коробку взять

Код OP получает несколько значений из медиа-бокса:

PDRectangle pageSize = page.findMediaBox();
float pageWidth = pageSize.getWidth();
float pageHeight = pageSize.getHeight();
float lineWidth = Math.max(pageWidth, pageHeight) / 1000;
float markerRadius = lineWidth * 10;
float fontSize = Math.min(pageWidth, pageHeight) / 20;
float fontPadding = Math.max(pageWidth, pageHeight) / 100;

Кажется, что они выбраны так, чтобы они были оптически приятными по отношению к размеру страницы. Но как правило, мультимедийный блок не является окончательным размером отображаемой или распечатанной страницы, а размер кадрирования. Таким образом, должно быть

PDRectangle pageSize = page.findCropBox();

(На самом деле рамка обрезки, предполагаемые размеры готовой страницы после обрезки, могут быть даже более подходящими; рамка обрезки по умолчанию соответствует рамке обрезки. Подробнее читайте здесь.)

Это не имеет отношения к приведенным примерам документов, поскольку они не содержат явных определений поля обрезки, поэтому поле обрезки по умолчанию устанавливается на поле мультимедиа. Однако это может быть актуально для других документов, например те, которые OP не может включить.

Какой конструктор PDPageContentStream использовать

Код OP добавляет поток контента на страницу, используя этот конструктор:

PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true);

Этот конструктор добавляет (первый true) и сжимает (второй true), но, к сожалению, он продолжает работать в графическом состоянии, оставленном ранее существовавшим содержимым.

Подробная информация о графическом состоянии важности для рассматриваемых наблюдений:

  • Матрица преобразования - она ​​могла быть изменена для масштабирования (или поворота, наклона, перемещения ...) любого добавленного нового содержимого.
  • Межсимвольный интервал - возможно, он был изменен, чтобы добавлять новые символы ближе или дальше друг от друга.
  • Межсловный интервал - возможно, он был изменен, чтобы добавлять новые слова ближе или дальше друг от друга.
  • Горизонтальное масштабирование - возможно, оно было изменено для масштабирования любых добавленных новых символов.
  • Подъем текста - возможно, он был изменен для смещения любых новых символов, добавленных по вертикали

Таким образом, следует выбрать конструктор, который также сбрасывает состояние графики:

PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true, true);

Третий true указывает PDFBox сбросить состояние графики, то есть окружить прежний контент парой операторов сохранения-состояния / восстановления-состояния.

Это актуально для данных образцов документов, по крайней мере, изменилась матрица преобразования.

Настройка и использование цветового пространства CalRGB

Код OP устанавливает обводку и обводку цветовых пространств на откалиброванное цветовое пространство:

contentStream.setStrokingColorSpace(new PDCalRGB());
contentStream.setNonStrokingColorSpace(new PDCalRGB());

К сожалению, new PDCalRGB() не создает действительный объект цветового пространства CalRGB, его необходимое значение WhitePoint отсутствует. Таким образом, прежде чем выбирать откалиброванное цветовое пространство, правильно его инициализируйте.

После этого код OP устанавливает цвета, используя

contentStream.setStrokingColor(marker.color.r, marker.color.g, marker.color.b);
contentStream.setNonStrokingColor(marker.color.r, marker.color.g, marker.color.b);

Эти (int, int, int) перегрузки, к сожалению, используют операторы RG и rg, неявно выбирая цветовое пространство DeviceRGB. Чтобы не перезаписывать текущее цветовое пространство, используйте вместо этого перегрузки (float[]) с нормализованными (0..1) значениями.

Хотя это не имеет отношения к наблюдаемой проблеме, программа просмотра PDF-файлов выводит сообщения об ошибках.

Расчет ширины нарисованной струны

Код OP вычисляет ширину нарисованной строки, используя

float textWidth = font.getStringWidth(marker.id) * 0.043f;

и ОП удивлен

* 0.043f работает как приближение для одного документа, но не работает для следующего.

Это «магическое» число складывается из двух факторов:

  • Как заметил OP, координатное пространство глифа установлено в 1/1000 пользовательского координатного пространства, и это число находится в пространстве глифа, то есть коэффициент 0,001.

  • Поскольку OP проигнорировал, он хочет ширину строки, используя размер шрифта, который он выбрал. Но объект шрифта не знает текущего размера шрифта и возвращает ширину для размера шрифта 1. Поскольку OP выбирает размер шрифта динамически как Math.min(pageWidth, pageHeight) / 20, этот коэффициент меняется. В случае двух приведенных образцов документов около 42, но, вероятно, в других документах они совершенно разные.

Размещение текста

Код OP позиционирует текст следующим образом, начиная с матриц идентификационного текста:

contentStream.moveTextPositionByAmount(
    marker.endX + marker.getXTextOffset(textWidth, fontPadding),
    marker.endY + marker.getYTextOffset(fontSize, fontPadding));

используя методы getXTextOffset и getYTextOffset:

public float getXTextOffset(float textWidth, float fontPadding) {
    if (getLocation() == Location.TOP)
        return (textWidth / 2 + fontPadding) * -1;
    else if (getLocation() == Location.BOTTOM)
        return (textWidth / 2 + fontPadding) * -1;
    else if (getLocation() == Location.RIGHT)
        return 0 + fontPadding;
    else
        return (textWidth + fontPadding) * -1;
}

public float getYTextOffset(float fontSize, float fontPadding) {
    if (getLocation() == Location.TOP)
        return 0 + fontPadding;
    else if (getLocation() == Location.BOTTOM)
        return (fontSize + fontPadding) * -1f;
    else
        return fontSize / 2 * -1;
}

В случае getXTextOffset я сомневаюсь, что добавление fontPadding для Location.TOP и Location.BOTTOM имеет смысл, особенно в свете желания ОП

The annotating text should be center aligned on the top and bottom marker

Чтобы текст располагался по центру, он не должен смещаться относительно центра.

Случай getYTextOffset сложнее. Код OP построен на двух недоразумениях: он предполагает

  • что позиция текста, выбранная moveTextPositionByAmount, - это нижний левый угол, и
  • что размер шрифта - это высота символа.

Фактически позиция текста располагается на базовой линии, начало координат следующего нарисованного глифа будет расположено там, например

Происхождение глифа, ширина и ограничивающая рамка для 'g'

Таким образом, положение y необходимо либо скорректировать, чтобы учесть спуск (для центрирования по всей высоте глифа), либо использовать только подъем (для центрирования по высоте глифа над базовой линией).

И размер шрифта не обозначает фактическую высоту символа, а организован таким образом, чтобы номинальная высота близко расположенных строк текста составляла 1 единицу для размера шрифта 1. "Плотный интервал" означает, что небольшое количество дополнительный межстрочный интервал содержится в размере шрифта.

По сути, для центрирования по вертикали нужно решить, на чем центрировать: всю высоту или высоту над базовой линией, только первую букву, всю метку или все глифы шрифта. PDFBox не предоставляет необходимую информацию для всех случаев, но такие методы, как PDFont.getFontBoundingBox(), должны помочь.

person mkl    schedule 06.01.2015
comment
спасибо, есть только один момент, который мне непонятен. Что такое ограничивающая рамка шрифта. Понятно, что описывает ограничивающая рамка глифов, но шрифт не знает, какие символы он описывает и какой размер шрифта. Второй, конечно, можно масштабировать с помощью рассчитанного размера шрифта, но я не понимаю ограничивающую рамку шрифта. - person andreas; 07.01.2015
comment
Согласно спецификации: Прямоугольник (см. 7.9.5, Прямоугольники), выраженный в системе координат глифа, который должен определять ограничивающую рамку шрифта. Это должен быть наименьший прямоугольник, охватывающий фигуру, которая образовалась бы, если бы все глифы шрифта были размещены так, чтобы их начало совпадало, а затем закрашено. - person mkl; 07.01.2015
comment
Желаю, чтобы у Stackoverflow был какой-то способ вознаградить такие отличные ответы, как этот - person Edi; 09.03.2016