すべての単語とその位置座標をPDFファイルから取得しようとしています。私は.NET
のAcrobat APIを使用して成功しました。今、iTextSharp(.NET
バージョン)などの無料APIを使用して同じ結果を得ようとしています。私はPRTokeniser
でテキストを(行ごとに)得ることができますが、私は行の座標を得る方法については考えていません。itextsharpを使用して、Pdfファイルからテキストとテキストの四角形の座標を抽出します。
答えて
com.itextpdf.text.pdf.parserパッケージクラスを使用します。現在の変換、色、フォントなどを追跡します。
悲しいことに、これらのクラスは新しい本では扱われていなかったので、JavaDocを残しておき、それをJavaからC#に精神的に変換します。
LocationTextExtractionStrategy
をPdfTextExtractor
に差し込みます。
の文字列は、のように表示されます。それを言葉として解釈するのはあなた次第です(必要な場合は段落)。
PDFはテキストレイアウトについて何も知らないことに注意してください。すべての文字を個別に配置することができます。誰かがそんなに傾けられていれば(そうするためにコンボプラッターに手が届かないタコスをいくつか持っていなければならない)、すべてのaを所定のページに描画し、次にすべてのbを描画することができます。
もっと現実的には、誰かがFontAを使用するページのすべてのテキストを描画し、次にFontBのすべてを描画することがあります。これにより、より効率的なコンテンツストリームが生成されます。 イタリックおよび太字(および太字イタリック)はすべて別々のフォントであることに注意してください。もし誰かが単語の一部だけを太字(あるいは何でも)としてマークするならば、その論理ワードは少なくとも2つの描画コマンドに分解される必要があります。
しかし、たくさんの人が論理的な順序でテキストをPDFに書き出しています...それはパースしようとしている人にとってはとても便利ですが、あなたはそれを期待する必要はありません。なぜなら、あなたはいつもそうではない奇妙なものに遭遇するからです。
Mark Storerの回答には、私のアカウントは新しい回答です。
私はLocationTextExtracationStrategyを直接使用することができませんでした(私は間違っていると思います)。 LocationTextExtracationStrategyを使用するとテキストを取得できましたが、各文字列(または文字列)の座標を取得する方法を理解できませんでした。
私はLocationTextExtracationStrategyをサブクラス化し、内部的に必要なデータを公開しました。
私もそれを望んでいました.net ...ここでは、私がまとめたものの控えめなC#バージョンです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using iTextSharp.text.pdf.parser;
namespace PdfHelper
{
/// <summary>
/// Taken from http://www.java-frameworks.com/java/itext/com/itextpdf/text/pdf/parser/LocationTextExtractionStrategy.java.html
/// </summary>
class LocationTextExtractionStrategyEx : LocationTextExtractionStrategy
{
private List<TextChunk> m_locationResult = new List<TextChunk>();
private List<TextInfo> m_TextLocationInfo = new List<TextInfo>();
public List<TextChunk> LocationResult
{
get { return m_locationResult; }
}
public List<TextInfo> TextLocationInfo
{
get { return m_TextLocationInfo; }
}
/// <summary>
/// Creates a new LocationTextExtracationStrategyEx
/// </summary>
public LocationTextExtractionStrategyEx()
{
}
/// <summary>
/// Returns the result so far
/// </summary>
/// <returns>a String with the resulting text</returns>
public override String GetResultantText()
{
m_locationResult.Sort();
StringBuilder sb = new StringBuilder();
TextChunk lastChunk = null;
TextInfo lastTextInfo = null;
foreach (TextChunk chunk in m_locationResult)
{
if (lastChunk == null)
{
sb.Append(chunk.Text);
lastTextInfo = new TextInfo(chunk);
m_TextLocationInfo.Add(lastTextInfo);
}
else
{
if (chunk.sameLine(lastChunk))
{
float dist = chunk.distanceFromEndOf(lastChunk);
if (dist < -chunk.CharSpaceWidth)
{
sb.Append(' ');
lastTextInfo.addSpace();
}
//append a space if the trailing char of the prev string wasn't a space && the 1st char of the current string isn't a space
else if (dist > chunk.CharSpaceWidth/2.0f && chunk.Text[0] != ' ' && lastChunk.Text[lastChunk.Text.Length - 1] != ' ')
{
sb.Append(' ');
lastTextInfo.addSpace();
}
sb.Append(chunk.Text);
lastTextInfo.appendText(chunk);
}
else
{
sb.Append('\n');
sb.Append(chunk.Text);
lastTextInfo = new TextInfo(chunk);
m_TextLocationInfo.Add(lastTextInfo);
}
}
lastChunk = chunk;
}
return sb.ToString();
}
/// <summary>
///
/// </summary>
/// <param name="renderInfo"></param>
public override void RenderText(TextRenderInfo renderInfo)
{
LineSegment segment = renderInfo.GetBaseline();
TextChunk location = new TextChunk(renderInfo.GetText(), segment.GetStartPoint(), segment.GetEndPoint(), renderInfo.GetSingleSpaceWidth(), renderInfo.GetAscentLine(), renderInfo.GetDescentLine());
m_locationResult.Add(location);
}
public class TextChunk : IComparable, ICloneable
{
string m_text;
Vector m_startLocation;
Vector m_endLocation;
Vector m_orientationVector;
int m_orientationMagnitude;
int m_distPerpendicular;
float m_distParallelStart;
float m_distParallelEnd;
float m_charSpaceWidth;
public LineSegment AscentLine;
public LineSegment DecentLine;
public object Clone()
{
TextChunk copy = new TextChunk(m_text, m_startLocation, m_endLocation, m_charSpaceWidth, AscentLine, DecentLine);
return copy;
}
public string Text
{
get { return m_text; }
set { m_text = value; }
}
public float CharSpaceWidth
{
get { return m_charSpaceWidth; }
set { m_charSpaceWidth = value; }
}
public Vector StartLocation
{
get { return m_startLocation; }
set { m_startLocation = value; }
}
public Vector EndLocation
{
get { return m_endLocation; }
set { m_endLocation = value; }
}
/// <summary>
/// Represents a chunk of text, it's orientation, and location relative to the orientation vector
/// </summary>
/// <param name="txt"></param>
/// <param name="startLoc"></param>
/// <param name="endLoc"></param>
/// <param name="charSpaceWidth"></param>
public TextChunk(string txt, Vector startLoc, Vector endLoc, float charSpaceWidth, LineSegment ascentLine, LineSegment decentLine)
{
m_text = txt;
m_startLocation = startLoc;
m_endLocation = endLoc;
m_charSpaceWidth = charSpaceWidth;
AscentLine = ascentLine;
DecentLine = decentLine;
m_orientationVector = m_endLocation.Subtract(m_startLocation).Normalize();
m_orientationMagnitude = (int)(Math.Atan2(m_orientationVector[Vector.I2], m_orientationVector[Vector.I1]) * 1000);
// see http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
// the two vectors we are crossing are in the same plane, so the result will be purely
// in the z-axis (out of plane) direction, so we just take the I3 component of the result
Vector origin = new Vector(0, 0, 1);
m_distPerpendicular = (int)(m_startLocation.Subtract(origin)).Cross(m_orientationVector)[Vector.I3];
m_distParallelStart = m_orientationVector.Dot(m_startLocation);
m_distParallelEnd = m_orientationVector.Dot(m_endLocation);
}
/// <summary>
/// true if this location is on the the same line as the other text chunk
/// </summary>
/// <param name="textChunkToCompare">the location to compare to</param>
/// <returns>true if this location is on the the same line as the other</returns>
public bool sameLine(TextChunk textChunkToCompare)
{
if (m_orientationMagnitude != textChunkToCompare.m_orientationMagnitude) return false;
if (m_distPerpendicular != textChunkToCompare.m_distPerpendicular) return false;
return true;
}
/// <summary>
/// Computes the distance between the end of 'other' and the beginning of this chunk
/// in the direction of this chunk's orientation vector. Note that it's a bad idea
/// to call this for chunks that aren't on the same line and orientation, but we don't
/// explicitly check for that condition for performance reasons.
/// </summary>
/// <param name="other"></param>
/// <returns>the number of spaces between the end of 'other' and the beginning of this chunk</returns>
public float distanceFromEndOf(TextChunk other)
{
float distance = m_distParallelStart - other.m_distParallelEnd;
return distance;
}
/// <summary>
/// Compares based on orientation, perpendicular distance, then parallel distance
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public int CompareTo(object obj)
{
if (obj == null) throw new ArgumentException("Object is now a TextChunk");
TextChunk rhs = obj as TextChunk;
if (rhs != null)
{
if (this == rhs) return 0;
int rslt;
rslt = m_orientationMagnitude - rhs.m_orientationMagnitude;
if (rslt != 0) return rslt;
rslt = m_distPerpendicular - rhs.m_distPerpendicular;
if (rslt != 0) return rslt;
// note: it's never safe to check floating point numbers for equality, and if two chunks
// are truly right on top of each other, which one comes first or second just doesn't matter
// so we arbitrarily choose this way.
rslt = m_distParallelStart < rhs.m_distParallelStart ? -1 : 1;
return rslt;
}
else
{
throw new ArgumentException("Object is now a TextChunk");
}
}
}
public class TextInfo
{
public Vector TopLeft;
public Vector BottomRight;
private string m_Text;
public string Text
{
get { return m_Text; }
}
/// <summary>
/// Create a TextInfo.
/// </summary>
/// <param name="initialTextChunk"></param>
public TextInfo(TextChunk initialTextChunk)
{
TopLeft = initialTextChunk.AscentLine.GetStartPoint();
BottomRight = initialTextChunk.DecentLine.GetEndPoint();
m_Text = initialTextChunk.Text;
}
/// <summary>
/// Add more text to this TextInfo.
/// </summary>
/// <param name="additionalTextChunk"></param>
public void appendText(TextChunk additionalTextChunk)
{
BottomRight = additionalTextChunk.DecentLine.GetEndPoint();
m_Text += additionalTextChunk.Text;
}
/// <summary>
/// Add a space to the TextInfo. This will leave the endpoint out of sync with the text.
/// The assumtion is that you will add more text after the space which will correct the endpoint.
/// </summary>
public void addSpace()
{
m_Text += ' ';
}
}
}
}
は、私はあなたのバウンディングボックスを与えるために使用することができ、それらの行のテキストの行+ coordsの一覧(左上と右下)を手の甲TextLocationInfoプロパティを追加しました。
私はまた、私の最初の遊びで何か変わったものを見ました。私がstartPoint & endPointをベースラインから引っ張った場合、私は同じコードを持っているように見えました(私は正しいことをしていましたが、私がしたことはそれらのポイントをascentLineとDecentLineから引っ張ることでした)。私の最初のパスは、私はちょうどベースラインを使用しました。奇妙なことに、結果の指針に違いは見られませんでした。念のため言葉を...私が提供している指針が正しいかどうかは分かりません。
こんにちは、あなたのクラスは信じられないほど役に立ちます。見つかったテキストの場所に基づいてPDFに名前付きの宛先を追加するにはどうすればよいですか? – Jason
名前付きの目的地が何であるか分かりません。私はあなたがm_TextLocationInfoに追加する前に 'named destination'を作成するようにGetResultantTextを変更できると思います。または、クラスをそのまま使用してテキストと場所を解析し、テキストの場所のリストを繰り返して、それぞれの名前付きの目的地を作成することができます。 – greenhat
これは、すべての文字の位置を与えるために簡単に変更できますか? – d456
iTextとiTextSharpは、商用アプリケーションで使用している場合は無料ではありません。 –