Web Word Processor («Web Word») - привлекательное программное обеспечение, которое позволяет пользователям редактировать документы в любом месте, если у них есть доступ к браузеру. Он не поддерживает все функции собственного текстового процессора, но я надеюсь, что он будет поддерживать это в ближайшем будущем. За два года работы над этим я пережил трудные времена, разрабатывая Web Word, но смотрю только на положительные стороны. Я испытал множество острых ощущений и волнений в процессе реализации каждой функции. Желаю, чтобы вы чувствовали то же, что читаете эту статью.

Моя предыдущая статья объясняла критерии категоризации веб-слов, необходимость и сложность реализации страниц, contentEditable и принципы отображения и размещения макетов страниц в HTML. В этой статье я расскажу о способах реализации простых просмотров страниц и функций редактирования с использованием реального кода.

Что реализовать

Как обсуждалось в предыдущей статье, простые требования для реализации просмотров страниц заключаются в следующем:

  • Когда абзац выступает между страницами, его можно будет разделить на две отдельные страницы.
  • Текст должен обновляться в реальном времени по мере того, как пользователь вводит или удаляет символы.

Когда дело доходит до реализации таблиц, требуется гораздо больше реализаций и соображений. В этой статье обсуждается, в основном, с упором на тексты. В CSS элемент, обрабатываемый как display: inline;, является целью для макета.

Давайте начнем

В предыдущей статье я объяснил шаги по реализации Layout. Этот процесс выполняется следующим образом:

  • Разделение абзаца на две страницы
  • Разделение абзаца на строки
  • События, снова требующие макетов страницы

Давайте посмотрим на реальный код, реализующий эти шаги.

Разделение абзаца на две страницы

У каждой страницы есть свои поля, и текст можно вводить в любой области внутри полей. Таким образом, фактическая область отображения текста находится где угодно, кроме полей. В приведенном ниже коде это элемент с class="page-body". Проще говоря, я назову это pageBodyElement.

Фактический размер листа A4 - 210mm x 297mm, но он слишком велик, чтобы уместиться в пространстве этой статьи, поэтому для нашего удобства я буду использовать произвольный размер 150mm x 80 мм.

Напишите HTML-код для реализации страниц, как показано ниже:

Настройка pageBodyElement

contentEditable="true” установлен в pageBodyElement, поэтому в данный момент он находится в редактируемом режиме. style="outline: 0px;" используется для удаления контуров, видимых в режиме редактирования. Я использовал тег p для обозначения абзацев. Для пустого абзаца я добавил фиктивный (тег br) для отображения курсора.

Теперь у нас есть очень простой редактор документов.

Теперь мы переместим оставшуюся часть абзаца на следующую страницу.

Если в этом состоянии вводятся дополнительные тексты, они будут выходить за пределы поля, как показано ниже.

Если бы мы собирались создать текстовый процессор, не требующий реализации страницы, мы могли бы просто указать overflow-y: hidden или overflow-y: scroll. Однако, поскольку наша цель выше, давайте посмотрим, как с этим справиться.

Проще говоря, мы собираемся найти все элементы абзаца, нижнее значение которых больше, чем страница 1, а затем переместить их все на следующую страницу.

Эта задача выполняется во-первых, выясняя, существует ли такой абзац, с помощью _findExceedParagraph().

Как ясно показано в коде, только теги p в настоящее время рассматриваются как абзацы. Поскольку существует гораздо больше типов элементов блочного уровня, посетите MDN, чтобы узнать, что еще вы можете добавить.

Вторая задача - найти все лишние абзацы (_getExceedAllParagraphs()) и переместить их на следующую страницу (_insertParagraphsToBodyAtFirst()).

Обратите внимание на наличие строки кода в функции _getExceedAllParagraphs(), которая обрабатывает случай, когда высота отдельного абзаца больше, чем высота страницы.

Это происходит, когда высота первого абзаца больше, чем высота страницы. Когда это произойдет, будет создано бесконечное количество страниц, если оно не будет должным образом обработано в потоке макета. Если оставить такой негабаритный абзац как есть, текст будет пересекать поле, как показано ниже. Мы позаботимся об этой проблеме в разделе Dividing a Paragraph into Lines. На самом деле, абзац, содержащий большое изображение или высокую таблицу, может вызвать эту проблему. В этом случае нам потребуется более сложный процесс обработки макета.

_insertParagraphsToBodyAtFirst() перемещает все лишние абзацы на следующую страницу. Если следующая страница пуста, мы могли бы просто добавить элемент абзаца в pageBodyElement. Если страница не пустая, мы можем вставить ее вверху страницы. Любые ранее разделенные абзацы должны быть снова объединены в один на этом этапе. В противном случае мы увидим два отдельных абзаца, которые должны были быть одним целым.

Применение макета к странице приводит к увеличению количества страниц. Макет должен применяться к вновь созданным страницам до самой последней страницы. Давайте посмотрим на код всего макета страницы.

_layout() применяет макет страницы от первой до самой последней страницы. _layoutPage() использует функцию, упомянутую ранее, для применения макета к указанной странице.

Это изображение применения макета ко всем страницам. Я применил небольшую задержку, чтобы увидеть процесс макета страницы в действии.

Разделение абзаца на строки

Сейчас самое время позаботиться о абзацах, высота которых больше высоты страницы. Это показано на изображении.

Как видите, последняя линия пересекла границу. Как я упоминал ранее, вы не можете получить координаты букв только с помощью Text Node; вам действительно нужно обернуть все текстовые узлы тегами span. Здесь нужно знать два момента. Первый - to detect lines в абзаце, а второй - to split a paragraph that goes out of the page. Давайте посмотрим на реальный код.

_splitParagraph() был добавлен. Если есть абзац, выходящий за пределы поля, нам нужно разделить абзац на две части, начиная с верхней строки абзаца. Все остальные абзацы, разделенные в результате выполнения _getExceedAllParagraphs(), также собираются и перемещаются на следующую страницу.

Чтобы лучше понять, как переносить текстовые узлы промежутками, я выделил перенос каждой буквы красным. (На самом деле границы не должны отображаться, чтобы избежать искажения абзаца.)

Еще одна вещь, на которую вам нужно обратить внимание, - это то, как удерживается курсор. Я предположил, что курсор - это collapsed, но с этим нужно иметь дело, чтобы сохранить текущий курсор. (Фактически, для того, чтобы реализовать это в одиночку, потребуется много работы.)

На этом этапе определяется количество строк в абзаце.

Это шаг, на котором обнаруживаются превышающие строки в абзаце.

Здесь лишние абзацы разделяются на два. Сохраните идентификатор разделенного абзаца, чтобы его можно было объединить позже.

Удалите обертывающие теги диапазона, сохраните курсор и normalize() разделенные тексты.

Теперь, когда текст выступает между страницами, его можно будет перенести по строкам и отобразить на следующей странице.

До

После

События, снова требующие макетов страницы

В этом примере я создал макет, который будет применяться, начиная с первой страницы, когда вводится буква события keyup. Нам также необходимо обрабатывать другие события, такие как Copy & Paste и Delete.

Макет страницы при вводе текста

Закрытие

Мы рассмотрели, как реализовать страницы, что очень важно при разработке веб-Word. Я не призываю вас сразу переходить к разработке Web Word. Скорее будьте осторожны и проявляйте осторожность, потому что есть еще много функций, которые необходимо учитывать, а именно:

  • Добавление дополнительных элементов уровня блока
  • Обработка элементов уровня блока, когда они находятся в глубоких местах дерева DOM
  • В этом случае абзацы должны быть разделены до тех пор, пока pageBodyElement не станет родительским.
  • Обработка одного абзаца, не умещающегося в оставшейся области страницы (изображения, таблицы и т. Д.).
  • Разделение и повторное соединение таблицы, которая превысила границы предыдущей страницы, на следующей странице (разделение ячеек - довольно сложная задача).
  • Для корейских букв с сохранением курсора и компоновки текста
  • И позаботьтесь об этих тонких различиях в 1 пиксель!

По моему опыту, вам лучше заранее обсудить с клиентом необходимые функции в Web Word. В противном случае ваше программное обеспечение будет постоянно сравниваться с родным текстовым процессором. На мой взгляд, если будет добавлена ​​спецификация предоставления координат из Text Node или будет поддерживаться поддержка доступа к DOM в WebAssembly, разработка и производительность вашего Web Word будут намного лучше.

Если вы фронтенд-разработчик, который осмелится выложиться на полную, несмотря на все трудности, попробуйте, смельчак! Вот мои маленькие подарки: исходный код (html-page-layout) и демо.