Скругление углов в скрипте для Иллюстратора

Каждый раз, изобретая один и тот же велосипед,
мы изобретаем ещё что-то новое.

Древнегреческий философ (нет).

Эта запись в блоге — просто на свою «тригонометрическую память». Ничего особо полезного тут не будет, наверно. Я даже не могу выгрузить этот скрипт отдельным файлом, потому что он сильно встроен в экстеншен.

В начале — несколько просто красивых картинок и скрины на память:

end-1
process-1
process-2

Задача

В проекте «Генератор дорожных указателей» встречается шаблон с наклонной правой стороной — это указатель направления съезда. В гайде по этому знаку указан угол наклона — 70 градусов. Определив ширину знака по его содержимому, можно без проблем рассчитать точки будущей трапеции. И после этого необходимо скруглить углы.

sign

Казалось бы — такая простая задача: скруглить углы у многоугольника. Тем более, что производить это надо в таком мощном графическом редакторе, как Иллюстратор. Но проблема кроется в том, что скругления углов нет в скриптах для Адоба Иллюстратора 🤓.

Вернее в скриптовании Иллюстратора есть скругление углов, но только при создании прямоугольника. Но так как у нас трапеция, то придётся заниматься этим в коде уже после создания формы. Вариант с созданием прямоугольника (с скруглениями) и последующим переносом точек не подходит, потому что тогда управляющие точки — «усики» — изменили бы форму углов.

Решение

Задача по шаблону состояла из 2 частей: рассчитать точки трапеции и потом уже скруглить углы.

Инструмент должен быть гибким, поэтому я принял наклон в 70 градусов за переменную. Если вдруг дизайнер поменяет гайд, система должна работать и при 70, и при 72 и даже при 89, если вдруг такое понадобится. Значение угла наклона я вынес в параметр элемента вёрстки шаблона, то есть менеджер проекта без привлечения программиста может поменять настройки конкретного знака:

layout-item

Трапеция

Рассчитать трапецию — простая геометрическая задача. Зная высоту и ширину знака, мы просто смещаем нижнюю точку на нужный угол. Единственная сложность была — это то, что правую сторону нужно считать от наклонённой стрелки. Но это тоже небольшая тригонометрическая проблема.

Скругление

Конечно, в интернете есть скрипты, которые отлично скругляют углы. Тем более, что это очень простая форма — трапеция. Но не хотелось запихивать в экстеншен огромный скрипт для Иллюстратора. Поэтому решил сделать свой мини-скрипт.

Знак в форме трапеции — это единичный случай, поэтому не зацикливался на этой задаче и возвращался к ней периодически для смены деятельности, так сказать.

Принципиально не хотел лезть в параметрические уравнения (которые описывают кривые Безье), потому что для этой задачи я был уверен, что это не нужно. Так оно и вышло: взяв один случай (радиус и угол из шаблона), обсчитал его и ввёл константы для моего диапазона. «Тригонометрия» получилась такая:

trigo-1
trigo-2

К сожалению не смог найти бумажку, на которой все эти отрезки подписаны: dD, dY и так далее.

Потом вывел значения отрезков. Получилась такая «кухня»:

    var
      dD = cornerRadius / Math.sin(ANGLE_RAD),
      dY = cornerRadius * Math.sin(ANGLE_RAD),
      dH = cornerRadius * Math.cos(ANGLE_RAD),
      dAY = cornerRadius + dH,
      dCX = dAY * Math.tan(ANTI_ANGLE_RAD),
      dKX = cornerRadius * Math.sin(ANGLE_RAD),
      dAX = dCX + dKX,

      dXC2 = cornerRadius * Math.tan(ANTI_ANGLE_RAD),
      dAX2 = cornerRadius / Math.sin(ANGLE_RAD),
      dDK2 = cornerRadius * Math.tan(ANTI_ANGLE_RAD),
      dYK2 = dDK2 * Math.sin(ANGLE_RAD),
      dYC2 = cornerRadius - dYK2,
      dXK2 = dDK2 * Math.cos(ANGLE_RAD),
      dXCH2 = dAX2 - dXC2

И вот у нас 8 точек будущей трапеции:

    const pathPoints = [
      // 0
      [
        this.point[0] + cornerRadius,
        this.point[1],
      ],
      // 1
      [
        this.point[0] + width - dAX,
        this.point[1],
      ],
      // 2
      [
        this.point[0] + width - dCX,
        this.point[1] - dAY,
      ],
      // 3
      [
        this.point[0] + width - dWidth + (dXC2 - dXK2),
        this.point[1] - height + dYC2,
      ],
      // 4
      [
        this.point[0] + width - dWidth - dXCH2,
        this.point[1] - height,
      ],
      // 5
      [
        this.point[0] + cornerRadius,
        this.point[1] - height,
      ],
      // 6
      [
        this.point[0],
        this.point[1] - height + cornerRadius,
      ],
      // 7
      [
        this.point[0],
        this.point[1] - cornerRadius,
      ],
    ];

Для расчёта координат управляющих точек (усиков) уже пришлось прибегать к константам. В процессе расчета выяснилось, что там всего 2 коэффициента: для острого и тупого угла. После проверки на разных радиусах и углах я убедился, что точность высокая и задача решена. Я не буду тут показывать этот код, потому что это не имеет смысла.

Результат

Получился инструмент, готовый к изменениям шаблонов: и по углу наклона, и по радиусу скругления. В диапазоне от 0 до 40 градусов от вертикали получается отличный результат — этого хватает с запасом на возможные изменения. При 50 градусах появляется артефакт в остром угле (зелёным отмечен целевой контур), а при 60 — уже совсем вытягивается верх и «тупится» низ:

artefact-1
artefact-2

Так как угол наклона вынесен в параметры, можно экспериментировать. И оказалось, что можно наклонять сторону знака и в другую сторону. Проверка тоже прошла удачно — скрипт отлично наклонил знак и в другую сторону. Искажения заметны стали так же на 50º от вертикали:

minus-1
minus-2

Размеры трапеций разные, потому что её размер зависит от верха знака — там размещается стрелка направления.

В результате «изобретения этого велосипеда» получились красивые картинки и удобный инструмент для примерки угла наклона.