В 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 по этой теме.