Да и нет.
Сначала нет.Формат PDF не имеет понятия текстовых структур, таких как абзацы, предложения или даже слова, он просто содержит текстовые фрагменты.Тот факт, что два ряда текста находятся рядом друг с другом, так что мы думаем о них как о структурированных, является человеческой вещью.Когда вы видите что-то похожее на трехстрочный абзац в PDF, на самом деле программа, которая сгенерировала PDF, фактически делала задачу, разбивая текст на три несвязанные текстовые строки и затем рисуя каждую строку в определенных координатах x, y.И что еще хуже, в зависимости от того, что хочет дизайнер, каждая строка текста может состоять из небольших строк, которые могут быть словами или даже просто символами.Так что это может быть draw "the cat in the hat" at 10,10
или draw "t" at 10,10, then draw "h" at 14,10, then draw "e" at 18,10
и так далее.На самом деле это довольно часто встречается в PDF-файлах из таких программ, как Adobe InDesign.
Теперь да.На самом деле это возможно.Если вы хотите немного поработать, вы можете заставить iTextSharp делать то, что вы ищете.Существует класс с именем PdfTextExtractor
, у которого есть метод с именем GetTextFromPage
, который получает весь необработанный текст со страницы.Последний параметр этого метода - это объект, который реализует интерфейс ITextExtractionStrategy
.Если вы создаете свой собственный класс, который реализует этот интерфейс, вы можете обрабатывать каждый прогон текста и выполнять свою собственную логику.
В этом интерфейсе есть метод с именем RenderText
, который вызывается для каждого прогона текста.Вам будет предоставлен объект iTextSharp.text.pdf.parser.TextRenderInfo
, из которого вы можете получить необработанный текст из цикла, а также другие вещи, такие как текущие координаты, с которых он начинается, текущий шрифт и т. Д. Поскольку визуальная строка текста может состоять изнесколько прогонов, вы можете использовать этот метод для сравнения базовой линии прогона (начальная координата x) с предыдущим прогоном, чтобы определить, является ли он частью той же визуальной линии.
Ниже приведен пример реализации этогоинтерфейс:
public class TextAsParagraphsExtractionStrategy : iTextSharp.text.pdf.parser.ITextExtractionStrategy {
//Text buffer
private StringBuilder result = new StringBuilder();
//Store last used properties
private Vector lastBaseLine;
//Buffer of lines of text and their Y coordinates. NOTE, these should be exposed as properties instead of fields but are left as is for simplicity's sake
public List<string> strings = new List<String>();
public List<float> baselines = new List<float>();
//This is called whenever a run of text is encountered
public void RenderText(iTextSharp.text.pdf.parser.TextRenderInfo renderInfo) {
//This code assumes that if the baseline changes then we're on a newline
Vector curBaseline = renderInfo.GetBaseline().GetStartPoint();
//See if the baseline has changed
if ((this.lastBaseLine != null) && (curBaseline[Vector.I2] != lastBaseLine[Vector.I2])) {
//See if we have text and not just whitespace
if ((!String.IsNullOrWhiteSpace(this.result.ToString()))) {
//Mark the previous line as done by adding it to our buffers
this.baselines.Add(this.lastBaseLine[Vector.I2]);
this.strings.Add(this.result.ToString());
}
//Reset our "line" buffer
this.result.Clear();
}
//Append the current text to our line buffer
this.result.Append(renderInfo.GetText());
//Reset the last used line
this.lastBaseLine = curBaseline;
}
public string GetResultantText() {
//One last time, see if there's anything left in the buffer
if ((!String.IsNullOrWhiteSpace(this.result.ToString()))) {
this.baselines.Add(this.lastBaseLine[Vector.I2]);
this.strings.Add(this.result.ToString());
}
//We're not going to use this method to return a string, instead after callers should inspect this class's strings and baselines fields.
return null;
}
//Not needed, part of interface contract
public void BeginTextBlock() { }
public void EndTextBlock() { }
public void RenderImage(ImageRenderInfo renderInfo) { }
}
Чтобы вызвать его, мы сделаем:
PdfReader reader = new PdfReader(workingFile);
TextAsParagraphsExtractionStrategy S = new TextAsParagraphsExtractionStrategy();
iTextSharp.text.pdf.parser.PdfTextExtractor.GetTextFromPage(reader, 1, S);
for (int i = 0; i < S.strings.Count; i++) {
Console.WriteLine("Line {0,-5}: {1}", S.baselines[i], S.strings[i]);
}
На самом деле мы отбрасываем значение из GetTextFromPage
и вместо этого проверяем работника baselines
и strings
поля массива.Следующим шагом для этого будет сравнение базовых линий и попытка определить, как сгруппировать строки, чтобы они стали абзацами.
Следует отметить, что не все абзацы имеют интервалы, которые отличаются от отдельных строк текста.Например, если вы запустите PDF-файл, созданный ниже, с помощью приведенного выше кода, вы увидите, что каждая строка текста находится на расстоянии 18 пунктов друг от друга, независимо от того, образует ли линия новый абзац или нет.Если вы откроете PDF-файл, созданный в Acrobat, и закроете все, кроме первой буквы каждой строки, вы увидите, что ваш глаз даже не различит разницу между разрывом строки и разрывом абзаца.
using (FileStream fs = new FileStream(workingFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
using (Document doc = new Document(PageSize.LETTER)) {
using (PdfWriter writer = PdfWriter.GetInstance(doc, fs)) {
doc.Open();
doc.Add(new Paragraph("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna."));
doc.Add(new Paragraph("This"));
doc.Add(new Paragraph("Is"));
doc.Add(new Paragraph("A"));
doc.Add(new Paragraph("Test"));
doc.Close();
}
}
}