UsefullCode.net


Visual Studio 2005/2008/2010やandroid SDK/NDKでの開発者向けに便利なソースコードを提供
This site provide you with useful source codes under 'USEFULLCODE license'.

第19回 ユニコードテキストを読み込む

2016年07月11日 17:20
0件のコメント

2016/7/7に小説家になろう機能の不具合修正ということで、ダウンロードできるPDFの仕様が変更されました。

これまではPDF内のテキストはシフトJISでした。これがUniJIS-UTF16-V(ユニコード)になり、それに伴いツールが正常に動作しません。
今回はこの変更への対応を行います。

これまではPDFRawReader.ReadLine()でPDF内の情報をstringとして読み取っていました。
今回の仕様変更で1行の中にアスキーとユニコードの2つのエンコードが混在することになったため、byte[]を取得できるように変更します。
シフトJISのときは文字列のエスケープのことは気にしなくとも実用上は問題ありませんでした。しかしユニコードでは文字コード中に0x0dなどが頻繁に出てくるためエスケープ処理を入れておきます。
通常であれば単純にエスケープ処理するだけで済むのですが、PDFテキスト(BT〜ET)の丸カッコ内ではエスケープしていない0x0aなどが入っていることがあるため、BT〜ETだけ別処理で改行検出しています。

あとはテキスト情報の取得部分でそのbyte[]の情報を抜き出してユニコードとして処理すれば対応終了です。

■PDFRawReader.cs
		/// <summary>
		/// 1行ずつ読み込む
		/// bSkipCommentLine == trueならコメント行(%から始まる行)は読み取らない
		/// </summary>
		public string ReadLine(Encoding enc, bool bSkipCommentLine, bool bDescape = false)
		{
			byte[] pcbRawData;
			return ReadLine(enc, bSkipCommentLine, out pcbRawData, bDescape);
		}


		/// <summary>
		/// 1行ずつ読み込む
		/// bSkipCommentLine == trueならコメント行(%から始まる行)は読み取らない
		/// bDescape == trueならエスケープを外す
		/// </summary>
		public string ReadLine(Encoding enc, bool bSkipCommentLine, out byte[] pcbRawData, bool bDescape = false)
		{
			do
			{
				byte tmp;
				pcbRawData = new byte[0];
				int n = 0;

				try
				{
					while (true)
					{
						tmp = ReadByte();

						if (bDescape && tmp == 0x5c)	//'\\'(0x5c)ならエスケープチェック						{
							tmp = ReadByte();
							if (tmp == 0x6e)		//'n'
								tmp = 0x0a;
							else if (tmp == 0x72)	//'r'
								tmp = 0x0d;
							else if (tmp == 0x74)	//'t'
								tmp = 0x09;
							else if (tmp == 0x62)	//'b'
								tmp = 0x08;
							else if (tmp == 0x66)	//'f'
								tmp = 0x0c;
							else if (tmp == 0x28)	//'('
							{
							}
							else if (tmp == 0x29)	//')'
							{
							}
							else if (tmp == 0x5c)	//'\\'
							{
							}

							Array.Resize(ref pcbRawData, pcbRawData.Length + 1);
							pcbRawData[n] = tmp;
							n++;
							continue;
						}

						if (tmp == 0x0a || tmp == 0x0d)
						{
							if (pcbRawData.Length < 6		//6文字以下なら処理
								|| (pcbRawData[0] != 0x42 || pcbRawData[1] != 0x54 || pcbRawData[2] != 0x20)	//「BT 」から始まらないなら処理
								|| (pcbRawData[pcbRawData.Length - 3] == 0x20 && pcbRawData[pcbRawData.Length - 2] == 0x45 && pcbRawData[pcbRawData.Length - 1] == 0x54))	//〜「 ET」となるテキストエリアなら処理
							{
								//改行コードチェック(\r、\r\n、\nの3通りあり得る)
								if (tmp == 0x0d)	//\rなら、\r\nか\rかをチェック								{
									tmp = ReadByte();
									if (tmp == 0x0a)		//\r\nだった
										break;

									//\r\nではなく、\rだったので一文字戻してから抜ける
									BaseStream.Seek(-1, SeekOrigin.Current);
									break;
								}
								if (tmp == 0x0a)
									break;
							}
						}

						Array.Resize(ref pcbRawData, pcbRawData.Length + 1);
						pcbRawData[n] = tmp;
						n++;
					}
				}
				catch (Exception)
				{
				}

				string ret = enc.GetString(pcbRawData);

				if (bSkipCommentLine == false || string.IsNullOrEmpty(ret) || ret[0] != _cbCommentChar)
					return ret;
			}
			while (true);
		}
■PDFTextReader.cs
		/// <summary>
		/// テキスト情報の取り出し
		/// 
		/// 取り出したテキスト情報はPDFPageへ格納する
		///
		/// shift-jisのみ対応
		/// ( ) で囲まれたテキストのみを処理し、¥表記や、<>表記のテキストには対応しない
		/// </summary>
		void AnalyzeContents(byte[] pcbContents, PDFPage page)
		{
			//byte[] data;

			////エスケープシーケンスを外す
			////「文章」以外の部分も処理することになるけど気にしない
			//using (MemoryStream ms1 = new MemoryStream(pcbContents))
			//using (BinaryReader br = new BinaryReader(ms1))
			//using (MemoryStream ms2 = new MemoryStream())
			//{
			//	while (ms1.Position != ms1.Length)
			//	{
			//		byte tmp = br.ReadByte();
			//		if (tmp != 0x5c)		//「\\」
			//		{
			//			ms2.WriteByte(tmp);
			//			continue;
			//		}
			//		tmp = br.ReadByte();
			//		ms2.WriteByte(tmp);
			//	}
			//	ms2.Flush();

			//	data = ms2.ToArray();
			//}

			using (MemoryStream ms = new MemoryStream(pcbContents))
			using (PDFRawReader pr = new PDFRawReader(ms))
			{
				//"小説家になろう"の縦書きPDFはshift-jis → UniJIS-UTF16-Vに変更(2016/7/7)
				Encoding enc = Encoding.ASCII;

				string strFontObj = "";
				float fFontPoint = 0;

				while (true)
				{
					if (ms.Position == ms.Length)
						break;

					byte[] pcbRawData;

					string strLine = pr.ReadLine(enc, false, out pcbRawData, true);
					LogOut(strLine);

					//フォントobject名とフォントサイズの取得
					{
						Regex re = new Regex(@"BT /(.+) ([\d\.]+) Tf", RegexOptions.IgnoreCase);
						Match m = re.Match(strLine);
						while (m.Success)
						{
							try
							{
								strFontObj = m.Groups[1].Value;

								string strSize = m.Groups[2].Value;
								fFontPoint = float.Parse(strSize);
							}
							catch (Exception)
							{
								strFontObj = "";
								fFontPoint = 0;
							}

							m = m.NextMatch();
						}
					}


					//テキストの取得
					{
						//表示座標とテキストだけ抜き出す
						// ( ) で囲まれたテキストのみを処理し、¥表記や、<>表記のテキストには対応しない
						Regex re = new Regex(@"BT ([\d\.]+) ([\d\.]+) Td \((.+)\) Tj ET", RegexOptions.IgnoreCase | RegexOptions.Singleline);
						Match m = re.Match(strLine);
						while (m.Success)
						{
							try
							{
								string strX = m.Groups[1].Value;
								string strY = m.Groups[2].Value;
								string strText = m.Groups[3].Value;

								float x = float.Parse(strX);
								float y = float.Parse(strY);

								//F9以外のフォントはbigendian unicode。F9はascii
								if (strFontObj != "F9")
								{
									int nStart = -1;
									int nEnd = -1;

									//byte[]から()内のテキストデータ部分を抜き出して変換
									for (int i = 0; i < pcbRawData.Length; i++)
									{
										if (pcbRawData[i] != '(')
											continue;
										nStart = i;
										break;
									}
									for (int i = pcbRawData.Length - 1; i >= 0; i--)
									{
										if (pcbRawData[i] != ')')
											continue;
										nEnd = i;
										break;
									}

									if (nStart > 0 && nEnd > 0)
									{
										byte[] pcbText = new byte[nEnd - nStart - 1];

										int j = 0;
										for (int i = nStart + 1; i < nEnd; i++, j++)
										{
											pcbText[j] = pcbRawData[i];		//ここでエスケープ解除するべき
										}
										strText = System.Text.Encoding.BigEndianUnicode.GetString(pcbText);
									}
								}

								PDFText text = new PDFText(strFontObj, fFontPoint, x, y, strText);
								page.Items.Add(text);
							}
							catch (Exception)
							{
							}

							m = m.NextMatch();
						}
					}

				}
			}
		}


プロジェクトファイルをダウンロード


第04回 府県天気予報XMLを整形する
トップページに戻る
issei.