Upload
alexander-rusakov
View
89
Download
3
Embed Size (px)
Citation preview
Редактор Mail.ru
Александр Русаков, Ведущий программист
Редактор
• Просмотр документов
• Превью документов
• Редактирование документов
Задачи
• Просмотр документов
• Превью документов
Как решить задачу «в лоб»?
Этапы обработки документа
Получение модели
документа от сервера
Расчёт документа
Генерация HTML
Модель
{
"sections": [{
"elements": [{
"type": "paragraph",
"elements": [{
"type": "text",
"text": "Test\n",
"font": {
"bold": true
}
}],...
Не подходит для редактирования:
• Нельзя разделить на части
• Нет простого механизма адресации по
вложенной структуре
Модель. Минусы
Модель. Адресация
{
"sections": [{
"elements": [{
"type": "paragraph",
"elements": [{
"type": "text",
"text": "Test \n",
"font": {
"bold": true
}
}],...
Как адресовать символ?
Этапы обработки документа
Получение модели
документа от сервера
Расчёт документа
Генерация HTML
Заливка у слов разной высоты
Картинки «вокруг рамки»
Расчет документа. Зачем?
Браузер width
Chrome 38 285.53…
Firefox 33 283.17…
IE 11 286
Opera 12.16 280
Расчет документа. Получение размера
ctx.font = '28pt Arial';
ctx.measureText('Редактор Mail.ru');
Размеры слов и букв различаются между браузерами
Документ в разных браузерах
рассчитывается по разному
Расчет документа. Минусы
Firefox IE11
Этапы обработки документа
Получение модели
документа от сервера
Расчёт документа
Генерация HTML
HTML
• Отображение текста различается между
браузерами
• Низкая скорость манипуляций с DOM
HTML. Минусы
Исполняем js-код на сервере
НО PhantomJS:
• Медленный
• Нестабильный
Превью. PhantomJS
Формулировка проблем
Использовали Проблема
Модель вложенная Нельзя разделить на части
Сложный механизм адресации
Размер шрифта вычисляется в
браузере
Разный размер букв и слов в
разных браузерах
HTML Разное отображение текста
Медленные манипуляции с DOM
PhantomJS Медленный
Нестабильный
Отказываемся от получения модели от сервера.
От сервера принимаем набор команд.
Модель вложенная. Как разделить на части?
Команды – низкоуровневые операции, изменяющие документ.
Например:
• TextInsert (start : number, text : string)
• TextDelete (start : number, end : number)
• ParagraphUpdate (end : number, props: Object)
• …
Команды
Команды. Пример
TextInsert(0,'П')
TextInsert(1,'р')
TextInsert(2,'у')
TextInsert(3,'д')
Команды. Пример
TextDelete(0, 1)
TextInsert(0,'Т')
Приходят с сервера
Создаются на клиенте на каждое действие пользователя:
• Ввод текста
• Изменение свойств (шрифт, цвет и т.д.)
Команды. Откуда?
Отправляются на сервер:
• По таймауту (автосохранение)
• По нажатию кнопки «Сохранить»
Команды. Куда?
Модель вложенная. Как адресовать символ?
Модель вложенная
Параграф Таблица
Текст ЯчейкаТекст Ячейка
ТаблицаПараграф
ЯчейкаТекст
Новая плоская модель
Модель плоская
Параграф 1 Параграф N
Текст
Таблица 1 Таблица M
Ячейка 1 Ячейка L
Команды формируют модель
{
"text": "Hello, world!\n
}
TextInsert(0,'Hello, world!\n')
Команды формируют модель на клиенте
RunUpdate(0, 4, {
bold: true
})
{
"text": "Hello, world!\n",
"runs": [{
"start": 0,
"end": 4,
"bold": true
}]
}
Команды формируют модель на клиенте
{
"text": "Hello, world!\n",
"runs": [{
"start": 0,
"end": 4,
"bold": true
}],
"paragraphs": [{
"end": 13
}]
}
Paragraph(13)
Модель. «Плоская»
{
"text": "Hello, world!\n",
"runs": [{
"start": 0,
"end": 4,
"bold": true
}],
"paragraphs": [{
"end": 13
}]
}
Свойство text – весь текст
документа.
Адресация по индексу
строки text.
Каждый браузер измеряет по-своему
Расчет документа. Разные размеры букв
Каждый браузер измеряет по-своему
Решение. Присылаем размеры шрифта с сервера
Расчет документа. Разные размеры букв
Каждый браузер измеряет по-своему
Решение. Присылаем размеры шрифта с сервера
В HTML нельзя указать размер слова по ширине
Расчет документа. Разные размеры букв
Каждый браузер измеряет по-своему
Решение. Присылаем размеры шрифта с сервера
В HTML нельзя указать размер слова по ширине
Решение. Canvas может принудительно уместить слово
по ширине
Расчет документа. Разные размеры букв
Шрифтов много хороших и разных…
Какие шрифты нужны?
Расчет документа. Шрифты
Расчет документа. Статистика по шрифтам
Times New Roman Calibri
Tahoma Arial
Courier New Symbol
Wingdings Cambria
Verdana
Расчет документа. Размеры шрифта
{
"widths": {
"a": 1139, ...
},
"ascent": 1921,
"descent": -434,
"factor": 2048
}
C сервера приходят JSON c рассчитанными размерами шрифта
Расчет размеров букв теперь не зависят от браузера!
Расчет документа. Итоговая структура
Документ
Секция
Страница
Строка
Слово
Расчет документа. Итоговая структура
Документ
Секция
Страница
Строка
Слово
Расчет документа. Итоговая структура
Документ
Секция
Страница
Строка
Слово
Расчет документа. Итоговая структура
Документ
Секция
Страница
Строка
Слово
Расчет документа. Слова
Простое слово
Пробельное слово
Табуляция
Разрыв страницы
Перенос строки
Расчет документа. Процесс
Слово большее по высоте сдвигает строку вниз
Расчет документа. Процесс
Если слово длиннее строки, то слово разделяется на две части
Расчет документа. Что-то посложнее
Как рассчитать подобное?
Картинка имеет обтекание текстом – «вокруг рамки»
Расчет документа. Что-то посложнее
Сначала располагаем картинку относительно
левого верхнего угла параграфа
Расчет документа. Что-то посложнее
Пытаем получить прямоугольник для строки – пересечение!
Расчет документа. Что-то посложнее
Строка разбивается на несколько допустимых участков
Расчет документа. Что-то посложнее
Размещаем слова по допустимым участкам
Расчет документа. Что-то посложнее
Итак весь параграф
4 аргумент – максимальная ширина текста
Canvas
ctx.fillText(text, x, y, maxWidth);
Canvas может принудительно уместить слово в
заданный размер
• Полный контроль над расположением элементов и их размерами
• Простая логика на уровне View
• Производительность (client side and server side)
• Используем node.js + node-canvas вместо phantomjs
• C согласия пользователя к репорту прикрепляется скриншот
документа
Canvas. Плюсы
Нет DOM:
• Самостоятельная обработка событий пользователя
• Нельзя заменить содержимое тега, поменять стиль => только
перерисовать
Canvas. Минусы
Используем два <canvas> на странице
Canvas. Больше одного на странице
Один – для текста, второй – для выделения и курсора
ctx.fillRect() должен работать с целыми числами, нужно округлять
Canvas. Округление и целые числа
ctx.fillRect(
round(x),
round(y),
round(w),
round(h)
);
Canvas. Округление и целые числа
y = 10.5;
h = 4.7;
round(y) === 11;
round(h) === 5;
Canvas. Округление и целые числа
y = 10.5;
h = 4.7;
round(y) === 11;
round(h) === 5;
round(y) === 11;
round(y + h) - round(y) === 4;
Заливка находится под текстом, но перед фоном страницы
Строка может вылезать за пределы страницы – нужно обрезать
Canvas. Заливка и обрезка
Рисуем фон под текстом (текст + фон за один проход)
Canvas. Off-screen canvas
ctx.globalCompositeOperation = 'destination-over';
Копируем off-screen canvas в основной с помощью drawImage
var offSreenCanvas = document.createElement('canvas');
ctx.drawImage(offSreenCanvas, ...);
Canvas. Превью и печать
Модуль node-canvas – Canvas API для Node.js
Дает возможность генерировать PDF (печать!)
ctx.addPage(width, height);
Canvas. PDF, первый подход
Использование Off-screen canvas растеризовало текст
Отказываемся от off-sreen canvas в пользу метода clip() для обрезки
ctx.save();
ctx.rect(x, y, w, h);
ctx.clip();
// render
ctx.restore();
Canvas. Почему все стало тормозить?!
save() / restore() действительно сбрасывают clip()
Но:clip() оперирует путем (path)
Путь всегда один
Путь не сбрасывается во время restore()
Canvas. Правильное использование clip()
ctx.save();
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.clip();
// render
ctx.restore();
Тень вокруг листа.
В превью не нужна тень!
Убрали тень в генерации
превью (Node.js) – время
снизилось в среднем на
50мс
Canvas. Ускоряем превью
Canvas. Дисплеи retina и devicePixelRatio
ctx.scale(devicePixelRatio, devicePixelRatio);
<canvas width="1486" height="434" style="height: 347px; width:1189px;">
</canvas>
Увеличиваем размер Canvas в devicePixeRatio раз и
сжимаем обратно с помощью CSS
В отрисовке увеличиваем масштаб
Результат
Было Стало
Отображение в разных
браузерах
Разное Идентичное
Генерация превью PhantomJS – 600 мс Node.js – 350 мс
Печать Средствами
браузера
Генерация PDF для
печати на сервере
СПАСИБОЗА ВНИМАНИЕ[email protected]
Инструмент Функции
git + phabricator + arcanist Управление и review кода
Grunt Сборка проекта
SASS + autoprefixer Работаем c css
Karma+PhantomJS, instabul Запуск тестов, code coverage
Что мы используем?
Библиотека Функции
Requirejs AMD модули
jQuery Обработка событий + AJAX
Mail.ru FileAPI Загрузка картинок на сервер
Jasmine Unit тесты
Node-canvas Серверная реализация Canvas API
Что мы используем?