From 9f6cf3ef30404617b9f816bf8edd2c3c5ff64c0b Mon Sep 17 00:00:00 2001 From: Agzamus Date: Sat, 5 Sep 2026 00:13:38 +0300 Subject: [PATCH] fix: #1 - text blocks by words --- .../agzam/vyatsu/data/remote/pdf/Pdf2Table.kt | 6 +- .../data/remote/pdf/PdfScheduleParser.kt | 30 +++++++--- .../vyatsu/data/remote/pdf/PdfTextParser.kt | 55 +++++++++++++++---- 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/agzam/vyatsu/data/remote/pdf/Pdf2Table.kt b/app/src/main/java/agzam/vyatsu/data/remote/pdf/Pdf2Table.kt index ab9b0cb..2b00aff 100644 --- a/app/src/main/java/agzam/vyatsu/data/remote/pdf/Pdf2Table.kt +++ b/app/src/main/java/agzam/vyatsu/data/remote/pdf/Pdf2Table.kt @@ -38,7 +38,7 @@ class Pdf2Table(page: PDPage) : PDFGraphicsStreamEngine(page) { private val svgBuilder = StringBuilder() private val currentPath = StringBuilder() - private val tableSvg = StringBuilder() + val tableSvg = StringBuilder() private val currentSubPathLines = mutableListOf() @@ -52,7 +52,7 @@ class Pdf2Table(page: PDPage) : PDFGraphicsStreamEngine(page) { val width = mediaBox.width val height = mediaBox.height return """ - + ${svgBuilder} ${tableSvg} @@ -211,7 +211,7 @@ class Pdf2Table(page: PDPage) : PDFGraphicsStreamEngine(page) { val bottom = ys[ii+1] cells.add(TableCell(left, top, right-left, bottom-top)) - tableSvg.append("\n") + tableSvg.append("\n") } columns.add(TableColumn(left, right-left, cells)) } diff --git a/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfScheduleParser.kt b/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfScheduleParser.kt index e7bd1cc..a73455d 100644 --- a/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfScheduleParser.kt +++ b/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfScheduleParser.kt @@ -52,12 +52,6 @@ object PdfScheduleParser { svg.processPage(page) val cols = svg.asTable() - /** Вывод таблички из PDF в SVG формате + отладочные жлементы из [Pdf2Table.asTable] **/ - Log.v(SVG_TAG, "====") - for(line in svg.asSvg().split("\n")) { - Log.v(SVG_TAG, line) - } - if(cols.size != 3) throw RuntimeException("Found columns: ${cols.size}/3") // Парсинг текста @@ -67,6 +61,11 @@ object PdfScheduleParser { textStripper.getText(doc) val texts = textStripper.blocks + for(text in texts) { + svg.tableSvg.appendLine("${text.text}") + svg.tableSvg.appendLine("") + } + val daysColumn = cols[0] // Колонка "День", имеет формат "<день> dd.mm.yy" val intervalsColumn = cols[1] // Колонка "Интервал", имеет формат "HH:mm-HH:mm" val lessonsColumn = cols[2] // Колонка с инофрмацией о паре @@ -114,7 +113,10 @@ object PdfScheduleParser { group = content(groupCell) continue } - if(day.size != 2) continue // XXX: добавить защиту от заголовка и выбрасывать исключения вместо пропуска? + if(day.size != 2) { + Log.w(TAG, "Day data is not in format : $day") + continue // XXX: добавить защиту от заголовка и выбрасывать исключения вместо пропуска? + } val date = dateFrom(day[1]) ?: continue val intervals = mutableListOf() @@ -161,7 +163,7 @@ object PdfScheduleParser { val type = LessonType.fromRaw(typeMatch.first().value.trim()) val teacher = raw.substring(typeMatch.last().range.last+1, raw.length - room.length).trim() var subgroup: String? = null - Log.d(TAG, subject + " " + group) + Log.d(TAG, "$subject $group") if(group != null && subject.startsWith("$group,")) { subject = subject.substring(group.length + 1).trim() val subgroupMatch = SUBGROUP_REGEX.find(subject) @@ -192,6 +194,18 @@ object PdfScheduleParser { for (text in texts.filter { it.rotation != 0f }) { Log.v(TAG, "${text.x.roundToInt()}\t${text.y.roundToInt()}\t${text.text}") } + + /** Вывод таблички из PDF в SVG формате + отладочные жлементы из [Pdf2Table.asTable] **/ + Log.v(SVG_TAG, "====") + for(line in svg.asSvg().split("\n")) { + if(line.length > 250) { + Log.v(SVG_TAG, "length: " + line.length) + Log.v(SVG_TAG + "-" + (pageIndex+1), "length: " + line.length) + continue + } + Log.v(SVG_TAG, line) + Log.v(SVG_TAG + "-" + (pageIndex+1), line) + } } } catch (e: Exception) { Log.e(TAG, "Parsing error", e) diff --git a/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfTextParser.kt b/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfTextParser.kt index 1904054..2624999 100644 --- a/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfTextParser.kt +++ b/app/src/main/java/agzam/vyatsu/data/remote/pdf/PdfTextParser.kt @@ -7,6 +7,7 @@ import com.tom_roush.pdfbox.text.TextPosition * Парсер блоков текста из PDF * @author Agzam */ + class PdfTextParser : PDFTextStripper() { data class TextBlock( @@ -25,21 +26,51 @@ class PdfTextParser : PDFTextStripper() { } override fun writeString(string: String?, textPositions: MutableList?) { - if (!string.isNullOrBlank() && !textPositions.isNullOrEmpty()) { - val first = textPositions.first() - val last = textPositions.last() + if (textPositions.isNullOrEmpty()) return - val matrix = first.textMatrix - val rotation = first.dir + val currentWordChars = mutableListOf() - val totalWidth = (last.xDirAdj + last.widthDirAdj) - first.xDirAdj + for (tp in textPositions) { + val unicode = tp.unicode - val x = matrix.translateX - val y = matrix.translateY - first.heightDir - - val height = first.heightDir - - blocks.add(TextBlock(string.trim(), x, currentPage.mediaBox.height - y - height, totalWidth, height, rotation)) + if (unicode == " " || unicode == "\u00A0" || unicode == "\t") { + addWordBlock(currentWordChars) + currentWordChars.clear() + } else { + currentWordChars.add(tp) + } } + + addWordBlock(currentWordChars) + } + + private fun addWordBlock(wordChars: List) { + if (wordChars.isEmpty()) return + + val first = wordChars.first() + val last = wordChars.last() + + val wordText = wordChars.joinToString("") { it.unicode }.trim() + if (wordText.isEmpty()) return + + val matrix = first.textMatrix + val rotation = first.dir + + val totalWidth = (last.xDirAdj + last.widthDirAdj) - first.xDirAdj + + val x = matrix.translateX + val y = matrix.translateY - first.heightDir + val height = first.heightDir + + blocks.add( + TextBlock( + text = wordText, + x = x, + y = currentPage.mediaBox.height - y - height, + width = totalWidth, + height = height, + rotation = rotation + ) + ) } } \ No newline at end of file