В Corel Draw начиная с 17 версии появилась удобная возможность создавать дополнения не только на VBA, но и на C# VSTA. Так давайте воспользуемся этим и приблизим мечту о кнопке "Сделать красиво".
ДисклеймерДля программистов с 2002 года работаю препресс инженером в типографии. Для меня программирование это хобби: и код, и описание не идеальны. Поэтому буду рад корректуре и критике.
Для полиграфистов понимаю, что спуски собирают в специализированных программах, но в реалиях моего города это не целесообразно. Тиражи небольшие, а количество макетов наоборот велико и 95% процентов макетов сделано в CorelDraw.
Что понадобится, чтобы магия заработала:
Visual Studio Tools for Applications (если хотите писать простые макросы, не обязательно)
Начальные знания C#
Начальные знания WPF
Для удобства написания воспользуемся дополнениями для Visual Studio от bonus360:
Запустим студию от имени администратора, чтобы при компилировании копировать файлы в системные папки. Создаем новый проект, выбрав в качестве шаблона 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 по этой теме.