From 0c83da52c61db9e70ad5c941448a2387ecb11a9b Mon Sep 17 00:00:00 2001 From: erjemin Date: Fri, 9 May 2025 21:57:03 +0300 Subject: [PATCH] =?UTF-8?q?add:=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- etpgrf/__init__.py | 14 +++++++ etpgrf/hyphenation.py | 89 +++++++++++++++++++++++++++++++++++++++++++ etpgrf/processor.py | 0 main.py | 8 ++++ 5 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 etpgrf/__init__.py create mode 100755 etpgrf/hyphenation.py create mode 100644 etpgrf/processor.py create mode 100644 main.py diff --git a/README.md b/README.md index d8dd502..c0ea9c3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ | in progress // в процессе разработки | |--------------------------------------| -| 0 | +| -0 | # Типограф для Web diff --git a/etpgrf/__init__.py b/etpgrf/__init__.py new file mode 100644 index 0000000..346cb13 --- /dev/null +++ b/etpgrf/__init__.py @@ -0,0 +1,14 @@ +""" +Typography - библиотека для экранной типографики текста с поддержкой HTML. + +Основные возможности: +- Автоматическая расстановка переносов +- Неразрывные пробелы для союзов и предлогов +- Корректные кавычки в зависимости от языка +- Висячая пунктуация +- Очистка и обработка HTML +""" +__version__ = "0.1.0" + +import regex +from etpgrf import processor, hyphenation \ No newline at end of file diff --git a/etpgrf/hyphenation.py b/etpgrf/hyphenation.py new file mode 100755 index 0000000..89e30e9 --- /dev/null +++ b/etpgrf/hyphenation.py @@ -0,0 +1,89 @@ +import regex + + +def hyphenation_in_word(s: str, max_chunk: int = 14, sep: str = "-") -> str: + """ Расстановка переносов в русском слове с учетом максимальной длины непереносимой группы. + Переносы ставятся половинным делением слова, рекурсивно. + + :param s: Слово, в котором надо расставить переносы + :param max_chunk: Максимальная длина непереносимой группы символов (по умолчанию 14) + :param sep: Символ переноса (по умолчанию "-") + :return: Слово с расставленными переносами + """ + + # Проверка гласных букв + def is_vow(let: str) -> bool: + return let.upper() in ['А', 'О', 'И', 'Е', 'Ё', 'Э', 'Ы', 'У', 'Ю', 'Я'] + + # Проверка согласных букв + def is_cons(let: str) -> bool: + return let.upper() in ['Б', 'В', 'Г', 'Д', 'Ж', 'З', 'К', 'Л', 'М', 'Н', 'П', 'Р', 'С', 'Т', 'Ф', 'Х', 'Ц', + 'Ч', 'Ш', 'Щ'] + + # Поиск допустимой позиции для переноса около заданного индекса + def find_hyphen_point(word: str, start_idx: int) -> int: + vow_indices = [i for i in range(len(word)) if is_vow(word[i])] + if not vow_indices: + # Если в слове нет гласных, то перенос невозможен + return -1 + + # Ищем ближайшую гласную до или после start_idx + for i in vow_indices: + if i >= start_idx - 2 and i + 2 < len(word): # Проверяем, что после гласной есть минимум 2 символа + ind = i + 1 + if (is_cons(word[ind]) or word[ind] in 'йЙ') and not is_vow(word[ind + 1]): + # Й -- полугласная. Перенос после неё только в случае, если дальше идет согласная + # (например, "бой-кий"), но запретить, если идет гласная (например, "ма-йка" не пройдет). + ind += 1 + if ind <= 3 or ind >= len(word) - 3: + # Не отделяем 3 символ с начала или конца (это некрасиво) + continue + if word[ind] in 'ьЬЪъ' or word[-1] in 'ьЬЪъ': + # Пропускаем мягкий/твердый знак. Согласно правилам русской типографики (например, ГОСТ 7.62-2008 + # или рекомендации по набору текста), перенос не должен разрывать слово так, чтобы мягкий или + # твердый знак оказывался в начале или конце строки + continue + return ind + return -1 # Не нашли подходящую позицию + + # Рекурсивное деление слова + def split_word(word: str) -> str: + if len(word) <= max_chunk: # Если длина укладывается в лимит, перенос не нужен + return word + + mid = len(word) // 2 # Середина слова + hyphen_idx = find_hyphen_point(word, mid) # Ищем точку переноса около середины + + if hyphen_idx == -1: # Если не нашли точку переноса + return word + + left_part = word[:hyphen_idx] + right_part = word[hyphen_idx:] + + # Рекурсивно делим левую и правую части + return split_word(left_part) + sep + split_word(right_part) + + # Основная логика + if len(s) <= max_chunk or not any(is_vow(c) for c in s): + # Короткое слово или без гласных + return s + + return split_word(s) + + +def hyphenation_in_text(text: str, min_len_word_hyphenation: int = 14, sep: str = "") -> str: + """ Расстановка переносов в тексте + + :param text: Строка, которую надо обработать (главный аргумент). + :param min_len_word_hyphenation: Минимальная длина слова для расстановки переносов. + :param sep: Символ переноса. + :return: str: + """ + rus_worlds = regex.findall(r'\b[а-яА-Я]+\b', text) # ищем все русскоязычные слова в тексте + rus_worlds = list(set(rus_worlds)) # убираем повторяющиеся слова + for word in rus_worlds: + if len(word) > min_len_word_hyphenation: + hyphenated_word = hyphenation_in_word(word, max_chunk=6, sep=sep) + print(f'{word} -> {hyphenated_word}') + text = text.replace(word, hyphenated_word) + return text diff --git a/etpgrf/processor.py b/etpgrf/processor.py new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py new file mode 100644 index 0000000..4e0869e --- /dev/null +++ b/main.py @@ -0,0 +1,8 @@ +import etpgrf + + +if __name__ == '__main__': + text_in = 'Привет, World! Это тестовый текст для проверки расстановки переносов в словах. Миллион 1000000' + result = etpgrf.hyphenation.hyphenation_in_text(text_in, min_len_word_hyphenation=8, sep='-') + print(result) +