Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
014896aaea | ||
|
|
9f6cf3ef30 |
@@ -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<PdfLine>()
|
||||
@@ -52,7 +52,7 @@ class Pdf2Table(page: PDPage) : PDFGraphicsStreamEngine(page) {
|
||||
val width = mediaBox.width
|
||||
val height = mediaBox.height
|
||||
return """
|
||||
<svg width="$width" height="$height" viewBox="0 0 $width $height" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<svg width="$width" height="$height" viewBox="0 0 $width $height" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style='font-size: 50%;'>
|
||||
<g>${svgBuilder}</g>
|
||||
<g>${tableSvg}</g>
|
||||
</svg>
|
||||
@@ -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("<rect fill='${if (i % 2 == 0) "#f" else "#0"}${if (ii % 2 == 0) "f0" else "00"}' x='$left' y='$top' width='${right-left}' height='${bottom-top}'/>\n")
|
||||
tableSvg.append("<rect fill='${if (i % 2 == 0) "#ff" else "#9e"}${if (ii % 2 == 0) "ff9e" else "9e9e"}' x='$left' y='$top' width='${right-left}' height='${bottom-top}'/>\n")
|
||||
}
|
||||
columns.add(TableColumn(left, right-left, cells))
|
||||
}
|
||||
|
||||
@@ -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 x='${text.x}' y='${text.y}'>${text.text}</text>")
|
||||
svg.tableSvg.appendLine("<circle cx='${text.x}' cy='${text.y}' r='1' fill='red'/>")
|
||||
}
|
||||
|
||||
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> <date>: $day")
|
||||
continue // XXX: добавить защиту от заголовка и выбрасывать исключения вместо пропуска?
|
||||
}
|
||||
|
||||
val date = dateFrom(day[1]) ?: continue
|
||||
val intervals = mutableListOf<TimeSlot>()
|
||||
@@ -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)
|
||||
|
||||
@@ -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<TextPosition>?) {
|
||||
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<TextPosition>()
|
||||
|
||||
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<TextPosition>) {
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package agzam.vyatsu.ui.schedule
|
||||
|
||||
import agzam.vyatsu.domain.model.Day
|
||||
import agzam.vyatsu.domain.model.LessonType
|
||||
import agzam.vyatsu.domain.model.SchedulePeriod
|
||||
import agzam.vyatsu.domain.model.TimeSlot
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -285,27 +286,48 @@ private fun LessonSlot(slotIndex: Int, slot: TimeSlot) {
|
||||
} else {
|
||||
slot.lessons.forEach { lesson ->
|
||||
Column(Modifier.fillMaxWidth().padding(vertical = 2.dp)) {
|
||||
Text(
|
||||
lesson.type.label,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
Text(
|
||||
lesson.subject?.takeIf { it.isNotBlank() } ?: lesson.raw,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
Text(
|
||||
listOfNotNull(
|
||||
lesson.subgroup,
|
||||
lesson.teacher,
|
||||
lesson.room
|
||||
var any = false
|
||||
if(lesson.type != LessonType.OTHER) {
|
||||
Text(
|
||||
lesson.type.label,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
.filter { it.isNotBlank() }
|
||||
.joinToString(" · "),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
any = true
|
||||
}
|
||||
|
||||
val subject = lesson.subject?.takeIf { it.isNotBlank() } ?: lesson.raw
|
||||
if(subject.isNotEmpty()) {
|
||||
Text(
|
||||
subject,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
any = true
|
||||
}
|
||||
|
||||
val meta = listOfNotNull(
|
||||
lesson.subgroup,
|
||||
lesson.teacher,
|
||||
lesson.room
|
||||
).filter { it.isNotBlank() }.joinToString(" · ")
|
||||
|
||||
if(meta.isNotEmpty()) {
|
||||
Text(
|
||||
meta,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
any = true
|
||||
}
|
||||
|
||||
if(!any) {
|
||||
Text(
|
||||
"-",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user