2025-04-21 21:14:48 +07:00
using System ;
using System.Collections.Generic ;
using System.Drawing ;
using System.Drawing.Printing ;
using System.Linq ;
using System.Net.Http ;
using System.Net.Http.Headers ;
using System.Security.Cryptography ; // Для шифрования/дешифрования
using System.Text ;
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
using System.Windows.Forms ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
using SKLADm.Properties ; // Для доступа к Settings.Default
using SKLADm ;
using ZXing ;
using ZXing.Common ;
using ZXing.Windows.Compatibility ;
namespace OzonInternalLabelPrinter // Убедитесь, что это ваше пространство имен
{
2025-05-01 22:46:44 +07:00
public partial class Form1 : MaterialSkin . Controls . MaterialForm // <-- Должно быть так
2025-04-21 21:14:48 +07:00
{
private HttpClient _httpClient ;
// Bitmap больше не нужен как поле класса
// private Bitmap _labelBitmap;
private string _currentProductName ;
private string _currentSku ;
private string _currentBarcode ;
private List < OzonStore > _availableStores ;
// Поле для хранения экземпляра документа между настройкой и печатью
private PrintDocument _printDocForSetup ; // Используем для PageSetup и Print
// Конструктор формы
public Form1 ( )
{
InitializeComponent ( ) ; // Инициализирует компоненты из ДИЗАЙНЕРА
InitializeHttpClient ( ) ;
// Инициализируем PrintDocument один раз
_printDocForSetup = new PrintDocument ( ) ;
// Устанавливаем обработчик печати один раз
_printDocForSetup . PrintPage + = new PrintPageEventHandler ( pd_PrintPage ) ;
// Загружаем магазины при запуске ПОСЛЕ инициализации _printDocForSetup
LoadStoresToComboBox ( ) ;
}
// Инициализация HTTP клиента
private void InitializeHttpClient ( )
{
_httpClient = new HttpClient
{
BaseAddress = new Uri ( "https://api-seller.ozon.ru/" )
} ;
_httpClient . DefaultRequestHeaders . Accept . Clear ( ) ;
_httpClient . DefaultRequestHeaders . Accept . Add ( new MediaTypeWithQualityHeaderValue ( "application/json" ) ) ;
}
// --- Логика работы с магазинами и ключами ---
private static readonly byte [ ] s_entropy = null ; // Энтропия для шифрования
// Дешифрование Api-Key
private string DecryptApiKey ( string encryptedKeyBase64 )
{
if ( string . IsNullOrEmpty ( encryptedKeyBase64 ) ) return string . Empty ;
try
{
byte [ ] encryptedBytes = Convert . FromBase64String ( encryptedKeyBase64 ) ;
byte [ ] decryptedBytes = ProtectedData . Unprotect ( encryptedBytes , s_entropy , DataProtectionScope . CurrentUser ) ;
return Encoding . UTF8 . GetString ( decryptedBytes ) ;
}
catch ( Exception ex )
{
System . Diagnostics . Debug . WriteLine ( $"Ошибка дешифрования ключа: {ex.Message}" ) ;
MessageBox . Show ( $"Н е удалось дешифровать Api-Key для магазина '{cmbStores.Text}'. Возможно, настройки повреждены или созданы под другим пользователем.\n\nО шиб ка : {ex.Message}" ,
"Ошибка ключа" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
return null ;
}
}
// Загрузка магазинов из настроек в ComboBox
private void LoadStoresToComboBox ( )
{
string json = Settings . Default . SavedStoresJson ;
_availableStores = new List < OzonStore > ( ) ;
if ( ! string . IsNullOrEmpty ( json ) )
{
try
{
_availableStores = JsonConvert . DeserializeObject < List < OzonStore > > ( json ) ? ? new List < OzonStore > ( ) ;
}
catch ( Exception ex )
{
MessageBox . Show ( $"Ошибка загрузки списка магазинов: {ex.Message}" , "Ошибка" , MessageBoxButtons . OK , MessageBoxIcon . Warning ) ;
_availableStores = new List < OzonStore > ( ) ;
}
}
cmbStores . DataSource = null ;
cmbStores . DataSource = _availableStores ;
cmbStores . DisplayMember = "StoreName" ;
bool storesExist = _availableStores . Any ( ) ;
cmbStores . Enabled = storesExist ;
txtOfferId . Enabled = storesExist ;
btnPageSetup . Enabled = storesExist ; // Включаем настройку, если есть магазины
// Кнопка GetData включится при вводе артикула
if ( storesExist )
{
cmbStores . SelectedIndex = 0 ;
// Установим принтер по умолчанию для _printDocForSetup, если возможно
TrySetDefaultPrinterForDocument ( ) ;
}
else
{
cmbStores . SelectedIndex = - 1 ;
// Показываем сообщение, если нужно
// MessageBox.Show("Нет сохраненных магазинов...", "Внимание", MessageBoxButtons.OK, Information);
}
ResetProductData ( ) ;
// Обновляем состояние кнопки GetData после загрузки
txtOfferId_TextChanged ( txtOfferId , EventArgs . Empty ) ;
}
// Попытка установить принтер по умолчанию для PrintDocument
private void TrySetDefaultPrinterForDocument ( )
{
string thermalPrinterName = "Xprinter XP-365B" ; // Или другое имя по умолчанию
foreach ( string printerName in PrinterSettings . InstalledPrinters )
{
if ( printerName . Equals ( thermalPrinterName , StringComparison . OrdinalIgnoreCase ) )
{
try
{
_printDocForSetup . PrinterSettings . PrinterName = printerName ;
System . Diagnostics . Debug . WriteLine ( $"Принтер по умолчанию '{printerName}' установлен для PrintDocument." ) ;
}
catch ( Exception ex )
{
System . Diagnostics . Debug . WriteLine ( $"Ошибка установки принтера по умолчанию '{printerName}': {ex.Message}" ) ;
}
return ; // Выходим после нахождения
}
}
System . Diagnostics . Debug . WriteLine ( $"Принтер по умолчанию '{thermalPrinterName}' не найден." ) ;
}
// --- Обработчики событий UI ---
private void btnManageStores_Click ( object sender , EventArgs e )
{
using ( FormManageStores manageForm = new FormManageStores ( ) )
{
manageForm . ShowDialog ( this ) ;
LoadStoresToComboBox ( ) ; // Перезагружаем список
}
}
private void cmbStores_SelectedIndexChanged ( object sender , EventArgs e )
{
ResetProductData ( ) ;
txtOfferId_TextChanged ( txtOfferId , EventArgs . Empty ) ; // Обновляем состояние кнопки GetData
// Можно попробовать обновить принтер по умолчанию для _printDocForSetup,
// если у разных магазинов могут быть разные принтеры (но это усложнит логику)
}
private void txtOfferId_TextChanged ( object sender , EventArgs e )
{
btnGetData . Enabled = cmbStores . SelectedItem ! = null & & ! string . IsNullOrEmpty ( txtOfferId . Text . Trim ( ) ) ;
if ( string . IsNullOrEmpty ( txtOfferId . Text . Trim ( ) ) )
{
ResetProductData ( ) ;
}
}
// --- Кнопка "Настройка принтера" ---
private void btnPageSetup_Click ( object sender , EventArgs e )
{
// Убедимся, что принтер в _printDocForSetup актуален (если не нашли Xprinter при запуске,
// или если у пользователя несколько принтеров)
if ( ! PrinterSettings . InstalledPrinters . Cast < string > ( ) . Contains ( _printDocForSetup . PrinterSettings . PrinterName ) )
{
// Если текущий принтер недоступен, сбрасываем на принтер по умолчанию Windows
try
{
_printDocForSetup . PrinterSettings = new PrinterSettings ( ) ; // С б р о с на настройки по умолчанию
System . Diagnostics . Debug . WriteLine ( "Текущий принтер в PrintDocument был недоступен, сброшен на принтер по умолчанию Windows." ) ;
}
catch { }
}
pageSetupDialog1 . Document = _printDocForSetup ; // Связываем диалог с нашим PrintDocument
pageSetupDialog1 . MinMargins = new Margins ( 0 , 0 , 0 , 0 ) ; // Минимальные поля
pageSetupDialog1 . AllowMargins = true ; // Разрешаем менять поля
pageSetupDialog1 . AllowOrientation = false ; // Запрещаем менять ориентацию
pageSetupDialog1 . AllowPaper = true ; // Разрешаем менять размер бумаги
pageSetupDialog1 . AllowPrinter = true ; // Разрешаем выбирать другой принтер
if ( pageSetupDialog1 . ShowDialog ( this ) = = DialogResult . OK )
{
// Настройки применились к _printDocForSetup
lblStatus . Text = "Настройки принтера применены." ;
System . Diagnostics . Debug . WriteLine ( $"PageSetupDialog OK: New PaperSize={_printDocForSetup.DefaultPageSettings.PaperSize.PaperName}, Printer={_printDocForSetup.PrinterSettings.PrinterName}" ) ;
}
else
{
lblStatus . Text = "Настройка принтера отменена." ;
}
}
// --- Получение данных из API ---
private async void btnGetData_Click ( object sender , EventArgs e )
{
OzonStore selectedStore = cmbStores . SelectedItem as OzonStore ;
if ( selectedStore = = null ) { MessageBox . Show ( "Выберите магазин." , "Ошибка" , MessageBoxButtons . OK , MessageBoxIcon . Warning ) ; return ; }
string clientId = selectedStore . ClientId ;
string apiKey = DecryptApiKey ( selectedStore . EncryptedApiKey ) ;
if ( apiKey = = null ) { lblStatus . Text = "Ошибка ключа API." ; return ; }
string rawOfferId = txtOfferId . Text . Trim ( ) ;
if ( string . IsNullOrEmpty ( rawOfferId ) ) { MessageBox . Show ( "Введите Артикул." , "Ошибка" , MessageBoxButtons . OK , MessageBoxIcon . Warning ) ; return ; }
string offerId = Regex . Replace ( rawOfferId , @"[^a-zA-Zа -яА-Я0-9_-]" , "" ) ;
if ( string . IsNullOrEmpty ( offerId ) ) { MessageBox . Show ( "Артикул некорректен." , "Ошибка" , MessageBoxButtons . OK , MessageBoxIcon . Warning ) ; return ; }
if ( offerId ! = rawOfferId ) { txtOfferId . Text = offerId ; }
ResetProductData ( ) ;
lblStatus . Text = "Запрос данных..." ;
Application . DoEvents ( ) ;
_httpClient . DefaultRequestHeaders . Remove ( "Client-Id" ) ;
_httpClient . DefaultRequestHeaders . Remove ( "Api-Key" ) ;
_httpClient . DefaultRequestHeaders . Add ( "Client-Id" , clientId ) ;
_httpClient . DefaultRequestHeaders . Add ( "Api-Key" , apiKey ) ;
string responseBody = string . Empty ;
try
{
var requestData = new { offer_id = new [ ] { offerId } } ;
var jsonRequest = JsonConvert . SerializeObject ( requestData ) ;
var content = new StringContent ( jsonRequest , Encoding . UTF8 , "application/json" ) ;
HttpResponseMessage response = await _httpClient . PostAsync ( "/v3/product/info/list" , content ) ;
responseBody = await response . Content . ReadAsStringAsync ( ) ;
System . Diagnostics . Debug . WriteLine ( $"API Response ({response.StatusCode}): {responseBody}" ) ;
if ( response . IsSuccessStatusCode )
{
JObject jsonResponse = JObject . Parse ( responseBody ) ;
JToken itemsArray = jsonResponse [ "items" ] ;
if ( itemsArray ! = null & & itemsArray . Type = = JTokenType . Array & & itemsArray . HasValues )
{
JToken firstItem = itemsArray [ 0 ] ;
if ( firstItem ! = null )
{
_currentProductName = firstItem [ "name" ] ? . ToString ( ) ? ? "N/A" ;
_currentSku = firstItem [ "offer_id" ] ? . ToString ( ) ? ? offerId ;
_currentBarcode = firstItem [ "barcode" ] ? . ToString ( ) ;
if ( string . IsNullOrEmpty ( _currentBarcode ) )
{
JToken barcodesToken = firstItem [ "barcodes" ] ;
if ( barcodesToken ! = null & & barcodesToken . Type = = JTokenType . Array & & barcodesToken . HasValues )
{ _currentBarcode = barcodesToken . First ? . ToString ( ) ; }
}
_currentBarcode = _currentBarcode ? ? "N/A" ;
GenerateLabelPreview ( ) ; // Обновляем UI и включаем кнопку печати
}
else { HandleApiError ( "Нет данных о товаре в ответе." , responseBody ) ; }
}
else { HandleApiError ( "Ответ API не содержит списка товаров." , responseBody ) ; }
}
else { HandleApiError ( $"Ошибка API: {response.StatusCode}" , responseBody ) ; }
}
catch ( JsonReaderException jsonEx ) { MessageBox . Show ( $"Ошибка JSON: {jsonEx.Message}\n{responseBody}" , "Ошибка" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ; ResetProductData ( ) ; }
catch ( HttpRequestException httpEx ) { MessageBox . Show ( $"Ошибка сети: {httpEx.Message}" , "Ошибка" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ; ResetProductData ( ) ; }
catch ( Exception ex ) { MessageBox . Show ( $"Ошибка: {ex.Message}" , "Ошибка" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ; ResetProductData ( ) ; }
finally { apiKey = null ; }
}
// Обработка ошибок API
private void HandleApiError ( string messagePrefix , string responseBody )
{
string errorMessage = $"{messagePrefix}" ;
try
{
JObject errorJson = JObject . Parse ( responseBody ) ;
string ozonError = errorJson [ "message" ] ? . ToString ( ) ? ? errorJson [ "error" ] ? [ "message" ] ? . ToString ( ) ;
errorMessage = string . IsNullOrEmpty ( ozonError ) ? $"{messagePrefix}\n{responseBody}" : $"{messagePrefix}\n{ozonError}" ;
}
catch { errorMessage = $"{messagePrefix}\n{responseBody}" ; }
MessageBox . Show ( errorMessage , "Ошибка API Ozon" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
lblStatus . Text = "Ошибка API." ;
ResetProductData ( ) ;
}
// С б р о с данных о товаре и состояния UI
private void ResetProductData ( )
{
_currentProductName = null ;
_currentSku = null ;
_currentBarcode = null ;
lblProductName . Text = "Название:" ;
lblProductSku . Text = "Артикул:" ;
lblProductBarcode . Text = "Штрихкод:" ;
// Очищаем превью, если оно есть
if ( this . Controls . ContainsKey ( "picLabelPreview" ) )
( this . Controls [ "picLabelPreview" ] as PictureBox ) . Image = null ;
btnPrintLabel . Enabled = false ;
// lblStatus.Text = "Статус:";
}
// Генерация (только обновление UI)
private void GenerateLabelPreview ( )
{
bool dataAvailable = ! string . IsNullOrEmpty ( _currentSku ) & &
! string . IsNullOrEmpty ( _currentProductName ) & &
! string . IsNullOrEmpty ( _currentBarcode ) & &
_currentBarcode ! = "N/A" ;
if ( ! dataAvailable ) { ResetProductData ( ) ; return ; }
lblProductName . Text = $"Название: {_currentProductName}" ;
lblProductSku . Text = $"Артикул: {_currentSku}" ;
lblProductBarcode . Text = $"Штрихкод: {_currentBarcode}" ;
// Очистка превью, если оно есть
if ( this . Controls . ContainsKey ( "picLabelPreview" ) )
( this . Controls [ "picLabelPreview" ] as PictureBox ) . Image = null ;
btnPrintLabel . Enabled = true ;
lblStatus . Text = "Данные получены, готово к печати." ;
}
// --- Печать ---
// Кнопка "Печать этикетки"
private void btnPrintLabel_Click ( object sender , EventArgs e )
{
if ( string . IsNullOrEmpty ( _currentSku ) | | string . IsNullOrEmpty ( _currentProductName ) | | string . IsNullOrEmpty ( _currentBarcode ) | | _currentBarcode = = "N/A" )
{
MessageBox . Show ( "Нет данных для печати." , "Ошибка" , MessageBoxButtons . OK , MessageBoxIcon . Warning ) ;
return ;
}
PrintDialog printDialog = new PrintDialog ( ) ;
printDialog . Document = _printDocForSetup ; // Используем настроенный документ
printDialog . AllowSomePages = false ;
printDialog . AllowSelection = false ;
printDialog . UseEXDialog = true ;
if ( printDialog . ShowDialog ( this ) = = DialogResult . OK )
{
// _printDocForSetup уже содержит принтер и настройки страницы, выбранные в PrintDialog или PageSetupDialog
// Переустанавливаем поля на случай, если PrintDialog их сбросил
_printDocForSetup . DefaultPageSettings . Margins = new Margins ( 0 , 0 , 0 , 0 ) ;
_printDocForSetup . OriginAtMargins = false ;
try
{
lblStatus . Text = $"Печать на {_printDocForSetup.PrinterSettings.PrinterName}..." ;
_printDocForSetup . Print ( ) ; // Печатаем с текущими настройками
lblStatus . Text = "Отправлено на печать." ;
}
catch ( InvalidPrinterException )
{
MessageBox . Show ( $"Ошибка: Принтер '{_printDocForSetup.PrinterSettings.PrinterName}' недоступен или не найден.\nПр о ве р ьте подключение или выберите другой принтер в настройках." , "Ошибка принтера" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
lblStatus . Text = "Ошибка принтера." ;
}
catch ( Exception ex )
{
MessageBox . Show ( $"Ошибка печати: {ex.Message}" , "Ошибка" , MessageBoxButtons . OK , MessageBoxIcon . Error ) ;
lblStatus . Text = "Ошибка печати." ;
}
}
else { lblStatus . Text = "Печать отменена." ; }
printDialog . Dispose ( ) ;
}
// Метод отрисовки страницы для печати (динамический)
private void pd_PrintPage ( object sender , PrintPageEventArgs e )
{
// Проверка данных
if ( string . IsNullOrEmpty ( _currentSku ) | | string . IsNullOrEmpty ( _currentProductName ) | | string . IsNullOrEmpty ( _currentBarcode ) | | _currentBarcode = = "N/A" )
{ System . Diagnostics . Debug . WriteLine ( "PrintPage ABORTED - Missing data!" ) ; e . Cancel = true ; return ; }
Graphics g = e . Graphics ;
// Получаем РЕАЛЬНЫЕ границы печатаемой области в единицах Graphics (обычно сотые дюйма)
// Н е PageBounds, а PrintableArea! PageBounds - это физический размер листа.
RectangleF printArea = e . PageSettings . PrintableArea ;
// Отступы от К Р А Е В ПЕЧАТАЕМОЙ ОБЛАСТИ (в единицах Graphics)
// Подбирайте эти значения, чтобы получить рамку внутри печатаемой зоны
float marginX = 5 * g . DpiX / 100f ; // Пример: 5/100 дюйма -> в единицах Graphics
float marginY = 3 * g . DpiY / 100f ; // Пример: 3/100 дюйма -> в единицах Graphics
// Область для рисования контента внутри отступов
float drawableX = printArea . Left + marginX ;
float drawableY = printArea . Top + marginY ;
float drawableWidth = printArea . Width - ( marginX * 2 ) ;
float drawableHeight = printArea . Height - ( marginY * 2 ) ;
float currentY = drawableY ; // Текущая позиция по Y
// Проверка на валидность области
if ( drawableWidth < = 0 | | drawableHeight < = 0 ) { System . Diagnostics . Debug . WriteLine ( "PrintPage ABORTED - Invalid drawable area!" ) ; e . Cancel = true ; return ; }
System . Diagnostics . Debug . WriteLine ( "--- Printing Page (Dynamic) ---" ) ;
System . Diagnostics . Debug . WriteLine ( $"Paper: {e.PageSettings.PaperSize.PaperName} ({e.PageSettings.PaperSize.Width}x{e.PageSettings.PaperSize.Height}) hund.inch" ) ;
System . Diagnostics . Debug . WriteLine ( $"PrintableArea ({g.PageUnit}): X={printArea.X:F2}, Y={printArea.Y:F2}, W={printArea.Width:F2}, H={printArea.Height:F2}" ) ;
System . Diagnostics . Debug . WriteLine ( $"Drawable Area ({g.PageUnit}): X={drawableX:F2}, Y={drawableY:F2}, W={drawableWidth:F2}, H={drawableHeight:F2}" ) ;
// Шрифты (размер в пунктах)
float baseFontSizePoints = 6f ; // Базовый размер, можно менять
Font titleFont = new Font ( "Arial" , baseFontSizePoints + 1 , FontStyle . Bold ) ;
Font regularFont = new Font ( "Arial" , baseFontSizePoints ) ;
Font barcodeTextFont = new Font ( "Arial" , baseFontSizePoints - 1 ) ;
try
{
// --- Рисуем Название ---
string productName = _currentProductName ? ? "N/A" ;
SizeF titleSize = g . MeasureString ( productName , titleFont , ( int ) drawableWidth ) ;
// Ограничиваем высоту, чтобы поместилось остальное
float maxTitleHeight = drawableHeight * 0.3f ;
float actualTitleHeight = Math . Min ( titleSize . Height , maxTitleHeight ) ;
g . DrawString ( productName , titleFont , Brushes . Black , new RectangleF ( drawableX , currentY , drawableWidth , actualTitleHeight ) ) ;
currentY + = actualTitleHeight + ( 2 * g . DpiY / 72f ) ; // Отступ 2 пункта
// --- Рисуем Артикул ---
string skuText = $"Артикул: {_currentSku ?? " N / A "}" ;
SizeF skuSize = g . MeasureString ( skuText , regularFont , ( int ) drawableWidth ) ;
if ( currentY + skuSize . Height < printArea . Bottom - marginY - 15 * 100f / g . DpiY ) // Оставляем ~15px под ШК + текст
{
g . DrawString ( skuText , regularFont , Brushes . Black , drawableX , currentY ) ;
currentY + = skuSize . Height + ( 3 * g . DpiY / 72f ) ; // Отступ 3 пункта
}
// --- Рисуем Штрихкод ---
float remainingHeight = ( printArea . Bottom - marginY ) - currentY ; // Оставшаяся высота
if ( remainingHeight > 15 * 100f / g . DpiY ) // Если осталось хотя бы ~15px
{
try
{
// --- Расчет размеров ШК ---
float barcodeTextHeight = g . MeasureString ( _currentBarcode , barcodeTextFont ) . Height ;
float barcodePrintHeight = Math . Max ( 10 * 100f / g . DpiY , remainingHeight - barcodeTextHeight - ( 2 * g . DpiY / 72f ) ) ; // Вычитаем текст и отступ
float barcodePrintWidth = drawableWidth * 0.98f ;
int barcodePixelHeight = ( int ) ( barcodePrintHeight * g . DpiY / 100f ) ;
int barcodePixelWidth = ( int ) ( barcodePrintWidth * g . DpiX / 100f ) ;
if ( barcodePixelHeight < 10 | | barcodePixelWidth < 40 ) throw new Exception ( "Слишком мало места для ШК." ) ;
BarcodeFormat barcodeFormat = BarcodeFormat . CODE_128 ;
// ... (определение barcodeFormat) ...
if ( _currentBarcode . Length = = 13 & & long . TryParse ( _currentBarcode , out _ ) ) barcodeFormat = BarcodeFormat . EAN_13 ;
else if ( _currentBarcode . Length = = 8 & & long . TryParse ( _currentBarcode , out _ ) ) barcodeFormat = BarcodeFormat . EAN_8 ;
var barcodeWriter = new ZXing . Windows . Compatibility . BarcodeWriter { Format = barcodeFormat , Options = new EncodingOptions { Height = barcodePixelHeight , Width = barcodePixelWidth , PureBarcode = true , Margin = 1 } } ;
using ( var barcodeBitmap = barcodeWriter . Write ( _currentBarcode ) )
{
float barcodeX = drawableX + ( drawableWidth - barcodePrintWidth ) / 2.0f ; // Центрируем
g . DrawImage ( barcodeBitmap , barcodeX , currentY , barcodePrintWidth , barcodePrintHeight ) ;
currentY + = barcodePrintHeight + ( 1 * g . DpiY / 72f ) ; // Отступ 1 пункт
}
// --- Рисуем текст ШК ---
SizeF barcodeTextSize = g . MeasureString ( _currentBarcode , barcodeTextFont ) ;
if ( currentY + barcodeTextSize . Height < = printArea . Bottom - marginY )
{
float textX = drawableX + ( drawableWidth - barcodeTextSize . Width ) / 2.0f ; // Центрируем
g . DrawString ( _currentBarcode , barcodeTextFont , Brushes . Black , textX , currentY ) ;
}
else { System . Diagnostics . Debug . WriteLine ( "SKIPPING Barcode text - no space" ) ; }
}
catch ( Exception exBarcode )
{
System . Diagnostics . Debug . WriteLine ( $"Error drawing barcode: {exBarcode.Message}" ) ;
g . DrawString ( "Ошибка ШК" , regularFont , Brushes . Red , drawableX , currentY ) ; // Рисуем ошибку вместо ШК
}
}
else { System . Diagnostics . Debug . WriteLine ( "SKIPPING Barcode - not enough vertical space" ) ; }
}
catch ( Exception exDraw ) { System . Diagnostics . Debug . WriteLine ( $"Error drawing page: {exDraw}" ) ; }
finally { titleFont . Dispose ( ) ; regularFont . Dispose ( ) ; barcodeTextFont . Dispose ( ) ; }
e . HasMorePages = false ;
}
} // Конец класса Form1
} // Конец namespace