В Corel Draw начиная с 17 версии появилась удобная возможность создавать дополнения не только на VBA, но и на C# VSTA. Так давайте воспользуемся этим и приблизим мечту о кнопке "Сделать красиво".

Дисклеймер

Для программистов –  с 2002 года работаю препресс инженером в типографии. Для меня программирование это хобби: и код, и описание не идеальны. Поэтому буду рад корректуре и критике.

Для полиграфистов – понимаю, что спуски собирают в специализированных программах, но в реалиях моего города это не целесообразно. Тиражи небольшие, а количество макетов наоборот велико и 95% процентов макетов сделано в CorelDraw.

Что понадобится, чтобы магия заработала:

Corel Draw

Visual Studio

Visual Studio Tools for Applications (если хотите писать простые макросы, не обязательно)

Начальные знания C#

Начальные знания WPF

Для удобства написания воспользуемся дополнениями для Visual Studio от “bonus360”:

CorelDraw Addons Templates

CorelDraw Addons Packer


Запустим студию от имени администратора, чтобы при компилировании копировать файлы в системные папки. Создаем новый проект, выбрав в качестве шаблона – “CorelDRAW Docker Addon”. Присваиваем имя, например “MagicUtilites”.

В появившемся окошке присваиваем имя докеру, например  также “MagicUtilites”, и выбираем те версии CorelDraw, под которые будем разрабатывать. Жмем “Done” и наблюдаем как рутина выполняется сама.

Небольшое отступление. Если при первом запуске возникла ошибка, проверьте объявление пространства имен в файле Extensions.cs, оно должно совпадать с названием проекта.

На этом этапе можно нажать F5 и найти в меню CorelDraw - Window - Dockers ваш докер. Сейчас он пустой и ничего не делает, но мы это исправим.


Открываем файл DockerUI.xaml в конструкторе XAML и добавляем кнопку на докер.

<Grid Margin="0,0,0,0">
	<StackPanel>
		<Button Content="Text Convert to Curves" Height="25" Margin="4" Click="Button_Click"/>
	</StackPanel>
</Grid>

В этот раз сделаем не следуя шаблону MVVM (именно им надо пользоваться разрабатывая на платформе WPF), а разместим код в обработчике события нажатия на кнопку. Но больше такого не повторится, обещаю.


Открываем файл DockerUI.xaml.cs

private corel.Application corelApp;

Класс corel.Application представляет приложение, в котором выполняется код докера. Значение полю присваивается в конструкторе.

Отредактируем метод Button_Click.

В начале метода добавим проверку, что в CorelDraw есть открытый файл. И если открытого файла нет, прекращаем выполнение.

private void Button_Click(object sender, RoutedEventArgs e)
{
  if (corelApp.ActiveDocument == null)
		return;
}

Свойство ActiveDocument типа corel.Application возвращает ссылку на активный документ.

Добавим быстродействия программе.

private void Button_Click(object sender, RoutedEventArgs e)
{
	if (corelApp.ActiveDocument == null)
		return;

	corelApp.BeginDraw();

	corelApp.EndDraw();
}

Остальной код должен быть между этими строками. Метод расширения BeginDraw() отключает перерисовку экрана, вызов событий и выделение corel объектов во время выполнения. Метод расширения EndDraw() восстанавливает настройки. 

В активном документе выполним перебор всех страниц. На каждой странице выполним перебор всех corel объектов для поиска текстовых объектов.

private void Button_Click(object sender, RoutedEventArgs e)
{
  if (corelApp.ActiveDocument == null)
    return;

  corelApp.BeginDraw();

  foreach (corel.Page page in corelApp.ActiveDocument.Pages)
  {
    foreach (corel.Shape shape in page.Shapes.All())
    {
      if (shape.Type == corel.cdrShapeType.cdrTextShape)
        shape.ConvertToCurves();
    }
  }

  corelApp.EndDraw();
}

При нахождении текстовых объектов, вызывается метод ConvertToCurves(), который переводит этот corel объект в кривые.

Запустим выполнение. Для проверки кнопки “Text Convert to Curves” нужен открытый документ и текст в нем. Реализованный код работает с любыми текстовыми corel объектами, но если этот объект находится в группе объектов или в PowerClip преобразования в кривые не произойдет. 

Для решения этой проблемы, разберем как Corel Draw представляет объекты в коде.

Класс corel.Shape содержит свойства и методы для взаимодействия с corel объектами. Свойство Type возвращает именованную константу которая определяет тип corel объекта. Если corel.Shape представляет группу corel объектов, свойство Type вернёт константу cdrGroupShape. Тогда обратившись к свойству Shapes, получим коллекцию corel объектов из группы.

Также с другими типами corel объектов. Если свойство Type возвращает cdrBitmapShape, то свойство Bitmap возвращает ссылку на картинку. Если свойство Type возвращает cdrGuidelineShape, свойство Guide возвращает ссылку на направляющую. 

Узнать что corel объект – PowerClip так не получится. Чтобы проверить является ли corel объект PowerClip-ом, проверьте свойство PowerClip на null.

Вернемся к коду.

Выделим перебор corel объектов в два отдельных метода. 

private void MakeToAllPages()
{
  if (corelApp.ActiveDocument == null)
    return;
  corelApp.BeginDraw();
  foreach (corel.Page page in corelApp.ActiveDocument.Pages)
  {
    MakeToShapeRange(page.Shapes.All());
  }
  corelApp.EndDraw();
}

Метод MakeToAllPages перебирает все страницы документа.

private void MakeToShapeRange(corel.ShapeRange sr)
{
  foreach (corel.Shape shape in sr)
  {
    if (shape.Type == corel.cdrShapeType.cdrGroupShape)
      MakeToShapeRange(shape.Shapes.All());

    if (shape.PowerClip != null)
      MakeToShapeRange(shape.PowerClip.Shapes.All());

    if (shape.Type == corel.cdrShapeType.cdrTextShape)
      shape.ConvertToCurves();
  }
}

Метод MakeToShapeRange рекурсивно перебирает переданную коллекцию corel объектов.

В первом условии проверяем является ли corel объект группой и если да, запускаем проверку corel объектов в группе. Во втором проверяем является ли corel объект PowerClip-ом и если да, запускаем проверку corel объектов которые он содержит. В третьем условии проверяем является ли corel объект текстом и если да, переводим его в кривые.

private void Button_Click(object sender, RoutedEventArgs e)
{
  MakeToAllPages();
}

В методе Button_Click остается только вызов метода MakeToAllPages.

Запустим выполнение. Теперь текст обрабатывается в группах и PowerClip.


Но докер с одной кнопкой, это не интересно, добавим больше кнопок.

<StackPanel>
  <Button Content="Text convert to curves" Height="25" Margin="4" Click="ConvertToCurves"/>
  <Separator Margin="4"/>
  <Button Content="Uniform fill to CMYK" Height="25" Margin="4" Click="UniformFillToCMYK"/>
  <Button Content="Outline fill to CMYK" Height="25" Margin="4" Click="OutlineFillToCMYK"/>
  <Button Content="Fountain fill to CMYK" Height="25" Margin="4" Click="FountainFillToCMYK"/>
  <Separator Margin="4"/>
  <Button Content="Bitmap to CMYK" Height="25" Margin="4" Click="BitmapToCMYK"/>
  <Button Content="Resample Bitmap to 300 dpi" Height="25" Margin="4" Click="ResampleBitmap"/>
</StackPanel>

Добавим обработчики нажатия для этих кнопок.

private void ConvertToCurves(object sender, RoutedEventArgs e){}

private void BitmapToCMYK(object sender, RoutedEventArgs e){}

private void UniformFillToCMYK(object sender, RoutedEventArgs e){}

private void OutlineFillToCMYK(object sender, RoutedEventArgs e){}

private void FountainFillToCMYK(object sender, RoutedEventArgs e){}

private void ResampleBitmap(object sender, RoutedEventArgs e){}

Сейчас вся работа с corel объектом происходит в методе MakeToShapeRange. Но теперь нам надо находить не только текст, но и картинки и определять есть ли заливка или обводка у corel объекта. Чтобы много раз не копировать код метода MakeToShapeRange, воспользуемся делегатами.

Изменим сигнатуру метода MakeToAllPages() на MakeToAllPages(Action<corel.Shape> action). Так как вся работа происходит в методе MakeToShapeRange изменим и его сигнатуру. А в методе MakeToAllPages изменим его вызов.

private void MakeToAllPages(Action<corel.Shape> action)
{
  if (corelApp.ActiveDocument == null)
    return;
  corelApp.BeginDraw();
  foreach (corel.Page page in corelApp.ActiveDocument.Pages)
  {
    MakeToShapeRange(page.Shapes.All(), action);
  }
  corelApp.EndDraw();
}

private void MakeToShapeRange(corel.ShapeRange sr, Action<corel.Shape> action)
{
  foreach (corel.Shape shape in sr)
  {
    if (shape.Type == corel.cdrShapeType.cdrGroupShape)
      MakeToShapeRange(shape.Shapes.All(), action);

    if (shape.PowerClip != null)
      MakeToShapeRange(shape.PowerClip.Shapes.All(), action);

    action(shape);
  }
}

Не забываем изменить аргументы в рекурсивном вызове метода.

Теперь в методах обработчиках нажатия можно воспользоваться вызовом метода MakeToAllPages с анонимным делегатом в качестве аргумента.

Далее уточнения будут в комментариях кода.

private void ConvertToCurves(object sender, RoutedEventArgs e)
{
  MakeToAllPages((s) =>
  {
    if (s.Type == corel.cdrShapeType.cdrTextShape) // если текст
      s.ConvertToCurves(); // перевести в кривые
  });
}

private void BitmapToCMYK(object sender, RoutedEventArgs e)
{
  MakeToAllPages((s) =>
  {
    if (s.Type == corel.cdrShapeType.cdrBitmapShape) // если картинка
      if (s.Bitmap.Mode != corel.cdrImageType.cdrCMYKColorImage) // цветовая модель не CMYK
        s.Bitmap.ConvertTo(corel.cdrImageType.cdrCMYKColorImage); // конвертировать в CMYK
  });
}

private void UniformFillToCMYK(object sender, RoutedEventArgs e)
{
  MakeToAllPages((s) =>
  {
    if (s.CanHaveFill) // у объекта может быть заливка
      if (s.Fill.Type == corel.cdrFillType.cdrUniformFill) // заливка сплошная
        if (s.Fill.UniformColor.Type != corel.cdrColorType.cdrColorCMYK) // цветовая модель не CMYK
          s.Fill.UniformColor.ConvertToCMYK(); // конвертировать в CMYK
  });
}

private void OutlineFillToCMYK(object sender, RoutedEventArgs e)
{
  MakeToAllPages((s) =>
  {
    if (s.CanHaveOutline) // у объекта может быть обводка
      if (s.Outline.Type == corel.cdrOutlineType.cdrOutline) // обводка есть
        if (s.Outline.Color.Type != corel.cdrColorType.cdrColorCMYK) // цветовая модель не CMYK
          s.Outline.Color.ConvertToCMYK(); // конвертировать в CMYK
  });
}

private void FountainFillToCMYK(object sender, RoutedEventArgs e)
{
  MakeToAllPages((s) =>
  {
    if (s.CanHaveFill) // у объекта может быть заливка
      if (s.Fill.Type == corel.cdrFillType.cdrFountainFill) // заливка градиент
      {
        foreach (corel.FountainColor c in s.Fill.Fountain.Colors) // перебор всех ключей в градиенте
        {
          if (c.Color.Type != corel.cdrColorType.cdrColorCMYK) // цветовая модель не CMYK
            c.Color.ConvertToCMYK(); // конвертировать в CMYK
        }
      }
  });
}

private void ResampleBitmap(object sender, RoutedEventArgs e)
{
  MakeToAllPages((s) =>
  {
    int resolution = 300;
    if (s.Type == corel.cdrShapeType.cdrBitmapShape) // если картинка
      if (s.Bitmap.ResolutionX != resolution || s.Bitmap.ResolutionY != resolution) // разрешение не совпадает с заданным
        s.Bitmap.Resample(0, 0, true, resolution, resolution); // изменяем разрешение на заданное
  });
}

Запустим выполнение.


На этом сеанс практической магии на сегодня закончен.

В следующей статье хочу описать создание докера для автоматического рисования меток реза на спуске.


Пользуясь случаем, рекомендую очень классные и бесплатные интерактивные онлайн-курсы по программированию от фирмы Контур. Так же рекомендую канал Павла Шмачилина по WPF, это лучшее что я видел на YouTube по этой теме.