commit 19d1c39370d2b3b6a15479f542a4046c9142d07a Author: yoshikazu Date: Fri Jun 5 17:27:29 2026 +0900 Initial commit: OtozaiPrinter (AveryDennisonUtil) ラベル/レシート レイアウト設計・印刷 MFC アプリ - 用紙設計キャンバス(1/100mm 基準)・テキスト/画像/矩形/直線アイテム - コピー/複製/Zオーダー/整列/グリッドスナップ、原紙(下絵)読込と印刷可否 - GDI 直接印刷、.otz 保存/再読込(版数つき) - MFC/CRT 静的リンクで VS2022 IDE から F5 デバッグ可 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1d2d4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# エディタ/ツールのローカル設定 +.claude/ + +# ビルド成果物 +/bin/ +/obj/ +*.user +*.aps +*.suo +.vs/ +ipch/ +*.pdb +*.ilk +*.idb diff --git a/AveryDennisonUtil.cpp b/AveryDennisonUtil.cpp new file mode 100644 index 0000000..d9a3e53 --- /dev/null +++ b/AveryDennisonUtil.cpp @@ -0,0 +1,31 @@ +// AveryDennisonUtil.cpp : アプリケーション クラスの実装(ダイアログベース) +#include "pch.h" +#include "AveryDennisonUtil.h" +#include "MainDialog.h" + +// アプリケーションの唯一のインスタンス(MFC の作法に従いグローバルに1つ置く) +CAveryDennisonUtilApp theApp; + +// コンストラクタ。特別な初期化は InitInstance で行うため、ここでは何もしない。 +CAveryDennisonUtilApp::CAveryDennisonUtilApp() +{ +} + +// アプリ起動時に1度だけ呼ばれる初期化処理。 +// 本アプリはダイアログベースなので、メインダイアログをモーダル表示し、 +// それが閉じられたらメッセージループへ入らずそのまま終了する。 +BOOL CAveryDennisonUtilApp::InitInstance() +{ + CWinApp::InitInstance(); + + // 設定の保存先(レジストリ)キー。将来の設定永続化に備えて指定しておく。 + SetRegistryKey(_T("Otozai")); + + // メイン ダイアログを表示(ダイアログベース アプリ) + CMainDialog dlg; + m_pMainWnd = &dlg; + dlg.DoModal(); + + // ダイアログが閉じられたらメッセージ ポンプを開始せずに終了する + return FALSE; +} diff --git a/AveryDennisonUtil.h b/AveryDennisonUtil.h new file mode 100644 index 0000000..48c0ed3 --- /dev/null +++ b/AveryDennisonUtil.h @@ -0,0 +1,19 @@ +// AveryDennisonUtil.h : アプリケーション クラス(ダイアログベース) +#pragma once + +#ifndef __AFXWIN_H__ +#error "include 'pch.h' before including this file for PCH" +#endif + +#include "resource.h" + +// アプリケーション本体。CWinApp を継承し、起動・終了を司る。 +// ダイアログベースのため UI 本体は CMainDialog 側にある。 +class CAveryDennisonUtilApp : public CWinApp +{ +public: + CAveryDennisonUtilApp(); + BOOL InitInstance() override; // 起動時の初期化(メインダイアログ表示) +}; + +extern CAveryDennisonUtilApp theApp; diff --git a/AveryDennisonUtil.rc b/AveryDennisonUtil.rc new file mode 100644 index 0000000..3f26fce --- /dev/null +++ b/AveryDennisonUtil.rc @@ -0,0 +1,148 @@ +// AveryDennisonUtil.rc : リソース スクリプト(ダイアログベース) +#include "resource.h" +#include "afxres.h" +#include "verrsrc.h" + +#pragma code_page(65001) + +#ifdef _WIN32 +LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT +#endif + +///////////////////////////////////////////////////////////////////////////// +// メイン ダイアログ +// +IDD_MAIN_DIALOG DIALOGEX 0, 0, 474, 424 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "AveryDennisonUtil" +FONT 9, "MS UI Gothic", 0, 0, 0x1 +BEGIN + PUSHBUTTON "新規(&N)", ID_FILE_NEW, 7, 7, 64, 14 + PUSHBUTTON "開く(&O)...", ID_FILE_OPEN, 7, 24, 64, 14 + PUSHBUTTON "保存(&S)", ID_FILE_SAVE, 7, 41, 64, 14 + PUSHBUTTON "名前を付けて保存",ID_FILE_SAVE_AS, 7, 58, 64, 14 + PUSHBUTTON "テキスト(&T)...", ID_INSERT_TEXT, 7, 82, 64, 14 + PUSHBUTTON "画像(&G)...", ID_INSERT_IMAGE, 7, 99, 64, 14 + PUSHBUTTON "矩形枠(&R)", ID_INSERT_RECT, 7, 116, 64, 14 + PUSHBUTTON "直線(&L)", ID_INSERT_LINE, 7, 133, 64, 14 + PUSHBUTTON "プロパティ(&P)...",ID_EDIT_PROPERTIES,7, 157, 64, 14 + PUSHBUTTON "削除(&D)", ID_EDIT_DELETE_ITEM,7,174, 64, 14 + PUSHBUTTON "コピー(&C)", ID_ITEM_COPY, 7, 191, 31, 14 + PUSHBUTTON "貼付(&V)", ID_ITEM_PASTE, 40, 191, 31, 14 + PUSHBUTTON "複製", ID_ITEM_DUPLICATE, 7, 208, 64, 14 + PUSHBUTTON "前面", ID_ITEM_FRONT, 7, 225, 31, 14 + PUSHBUTTON "背面", ID_ITEM_BACK, 40, 225, 31, 14 + PUSHBUTTON "整列...", ID_ALIGN_MENU, 7, 242, 64, 14 + CONTROL "グリッド/スナップ",IDC_CHECK_GRID,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,261,64,12 + PUSHBUTTON "原紙(&K)...", ID_TEMPLATE_LOAD, 7, 278, 31, 14 + PUSHBUTTON "消去", ID_TEMPLATE_CLEAR, 40, 278, 31, 14 + CONTROL "原紙を印刷", IDC_CHECK_PRINT_TEMPLATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,295,64,12 + PUSHBUTTON "用紙サイズ...", ID_PAPER_SIZE, 7, 314, 64, 14 + PUSHBUTTON "拡大(&I)", ID_VIEW_ZOOMIN, 7, 331, 31, 14 + PUSHBUTTON "縮小(&U)", ID_VIEW_ZOOMOUT, 40, 331, 31, 14 + PUSHBUTTON "印刷(&R)...", ID_FILE_PRINT, 7, 355, 64, 14 + PUSHBUTTON "バージョン情報...",ID_APP_ABOUT, 7, 379, 64, 14 + PUSHBUTTON "終了(&X)", IDCANCEL, 7, 403, 64, 14 + CONTROL "",IDC_CANVAS,"Static",SS_BLACKFRAME,78,7,389,410 +END + +///////////////////////////////////////////////////////////////////////////// +// バージョン情報ダイアログ +// +IDD_ABOUTBOX DIALOGEX 0, 0, 250, 80 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "バージョン情報" +FONT 9, "MS UI Gothic", 0, 0, 0x1 +BEGIN + LTEXT "AveryDennisonUtil — ラベル/レシート レイアウト設計ツール",IDC_STATIC,10,12,230,12 + LTEXT "Copyright (C) 2026",IDC_STATIC,10,30,230,10 + DEFPUSHBUTTON "OK",IDOK,100,56,50,16,WS_GROUP +END + +IDD_PAPER_SIZE DIALOGEX 0, 0, 230, 152 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "用紙サイズ (全印刷領域)" +FONT 9, "MS UI Gothic", 0, 0, 0x1 +BEGIN + LTEXT "プリセット:",IDC_STATIC,12,14,50,10 + COMBOBOX IDC_COMBO_PRESET,70,12,148,80,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "幅 (mm):",IDC_STATIC,12,40,50,10 + EDITTEXT IDC_EDIT_WIDTH,70,38,60,14,ES_AUTOHSCROLL + LTEXT "高さ (mm):",IDC_STATIC,12,60,50,10 + EDITTEXT IDC_EDIT_HEIGHT,70,58,60,14,ES_AUTOHSCROLL + LTEXT "解像度 (dpi):",IDC_STATIC,12,80,50,10 + EDITTEXT IDC_EDIT_DPI,70,78,60,14,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "余白 (mm):",IDC_STATIC,12,100,50,10 + EDITTEXT IDC_EDIT_MARGIN,70,98,60,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,108,126,50,16,WS_GROUP + PUSHBUTTON "キャンセル",IDCANCEL,166,126,50,16 +END + +IDD_TEXT_PROPS DIALOGEX 0, 0, 260, 200 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "テキストのプロパティ" +FONT 9, "MS UI Gothic", 0, 0, 0x1 +BEGIN + LTEXT "テキスト:",IDC_STATIC,12,12,50,10 + EDITTEXT IDC_EDIT_TEXT,12,26,236,70,ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL + LTEXT "フォント:",IDC_STATIC,12,104,50,10 + EDITTEXT IDC_EDIT_FONT,70,102,120,14,ES_AUTOHSCROLL + LTEXT "サイズ (pt):",IDC_STATIC,12,124,50,10 + EDITTEXT IDC_EDIT_PTSIZE,70,122,50,14,ES_AUTOHSCROLL + LTEXT "配置:",IDC_STATIC,12,144,50,10 + COMBOBOX IDC_COMBO_ALIGN,70,142,80,80,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "太字",IDC_CHECK_BOLD,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,160,144,60,12 + DEFPUSHBUTTON "OK",IDOK,138,172,50,16,WS_GROUP + PUSHBUTTON "キャンセル",IDCANCEL,196,172,50,16 +END + +///////////////////////////////////////////////////////////////////////////// +// バージョン情報 +// +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "041104b0" + BEGIN + VALUE "CompanyName", "Otozai" + VALUE "FileDescription", "AveryDennisonUtil" + VALUE "FileVersion", "1.0.0.0" + VALUE "InternalName", "AveryDennisonUtil.exe" + VALUE "OriginalFilename", "AveryDennisonUtil.exe" + VALUE "ProductName", "AveryDennisonUtil" + VALUE "ProductVersion", "1.0.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x411, 1200 + END +END + +///////////////////////////////////////////////////////////////////////////// +// 文字列テーブル +// +STRINGTABLE +BEGIN + AFX_IDS_APP_TITLE "AveryDennisonUtil" + AFX_IDS_IDLEMESSAGE "準備完了" +END + +///////////////////////////////////////////////////////////////////////////// +// MFC 標準リソース(共通ダイアログの文字列等) +// +#ifndef APSTUDIO_INVOKED +#include "afxres.rc" +#endif diff --git a/AveryDennisonUtil.sln b/AveryDennisonUtil.sln new file mode 100644 index 0000000..a2eddb2 --- /dev/null +++ b/AveryDennisonUtil.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35222.181 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AveryDennisonUtil", "AveryDennisonUtil.vcxproj", "{A1B2C3D4-E5F6-47A8-9B0C-1D2E3F405162}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1B2C3D4-E5F6-47A8-9B0C-1D2E3F405162}.Debug|x64.ActiveCfg = Debug|x64 + {A1B2C3D4-E5F6-47A8-9B0C-1D2E3F405162}.Debug|x64.Build.0 = Debug|x64 + {A1B2C3D4-E5F6-47A8-9B0C-1D2E3F405162}.Release|x64.ActiveCfg = Release|x64 + {A1B2C3D4-E5F6-47A8-9B0C-1D2E3F405162}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AveryDennisonUtil.vcxproj b/AveryDennisonUtil.vcxproj new file mode 100644 index 0000000..bd8b88f --- /dev/null +++ b/AveryDennisonUtil.vcxproj @@ -0,0 +1,175 @@ + + + + + + + Debug + x64 + + + Release + x64 + + + + + + 17.0 + {A1B2C3D4-E5F6-47A8-9B0C-1D2E3F405162} + AveryDennisonUtil + 10.0 + MFCProj + + false + + + + + + + + Application + true + v143 + Unicode + Static + + + + Application + false + v143 + true + Unicode + Static + + + + + + + + + + + + + + + + + + + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)obj\AveryDennisonUtil\$(Configuration)\ + AveryDennisonUtil + + + + + + Level3 + Disabled + true + stdcpp17 + /utf-8 %(AdditionalOptions) + MultiThreadedDebug + WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions) + Use + pch.h + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x0411 + + + Windows + true + + + + + + + Level3 + MaxSpeed + true + true + true + stdcpp17 + /utf-8 %(AdditionalOptions) + MultiThreaded + WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) + Use + pch.h + + + NDEBUG;%(PreprocessorDefinitions) + 0x0411 + + + Windows + true + true + true + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AveryDennisonUtil.vcxproj.filters b/AveryDennisonUtil.vcxproj.filters new file mode 100644 index 0000000..310fec6 --- /dev/null +++ b/AveryDennisonUtil.vcxproj.filters @@ -0,0 +1,57 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;cxx;cc + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;bmp + + + {1a2b3c4d-0001-0002-0003-000405060708} + + + {1a2b3c4d-0011-0012-0013-001405060708} + + + + ソース ファイル + ソース ファイル + ソース ファイル + ソース ファイル + ソース ファイル + ソース ファイル + ソース ファイル + ソース ファイル + ソース ファイル\items + ソース ファイル\items + ソース ファイル\items + ソース ファイル\items + + + ヘッダー ファイル + ヘッダー ファイル + ヘッダー ファイル + ヘッダー ファイル + ヘッダー ファイル + ヘッダー ファイル + ヘッダー ファイル + ヘッダー ファイル + ヘッダー ファイル + ヘッダー ファイル + ヘッダー ファイル + ヘッダー ファイル\items + ヘッダー ファイル\items + ヘッダー ファイル\items + ヘッダー ファイル\items + + + リソース ファイル + + diff --git a/Canvas.cpp b/Canvas.cpp new file mode 100644 index 0000000..76ff989 --- /dev/null +++ b/Canvas.cpp @@ -0,0 +1,583 @@ +// Canvas.cpp +#include "pch.h" +#include "Canvas.h" +#include "LabelLayout.h" +#include "RenderScene.h" +#include "TextItem.h" +#include "ShapeItem.h" +#include "ImageItem.h" +#include "TextPropsDlg.h" + +BEGIN_MESSAGE_MAP(CCanvas, CWnd) + ON_WM_PAINT() + ON_WM_ERASEBKGND() + ON_WM_LBUTTONDOWN() + ON_WM_LBUTTONUP() + ON_WM_MOUSEMOVE() + ON_WM_LBUTTONDBLCLK() + ON_WM_SETCURSOR() + ON_WM_KEYDOWN() + ON_WM_GETDLGCODE() +END_MESSAGE_MAP() + +// コンストラクタ。ズーム3倍・スナップ有効・グリッド1mm を既定値に初期化。 +CCanvas::CCanvas() + : m_zoom(3.0) + , m_pLayout(nullptr) + , m_pSelected(nullptr) + , m_pClipboard(nullptr) + , m_dirty(FALSE) + , m_snap(TRUE) + , m_gridHu(100) // 1mm + , m_drag(Drag::None) + , m_dragHandle(-1) + , m_dragStartHu(0, 0) + , m_origRect(0, 0, 0, 0) +{ +} + +// デストラクタ。コピー用クリップボードのアイテムを解放する。 +CCanvas::~CCanvas() +{ + delete m_pClipboard; +} + +// 値(1/100mm)を最も近いグリッド線へ丸める。スナップ移動・リサイズで使用。 +int CCanvas::SnapHu(int hu) const +{ + if (m_gridHu <= 0) return hu; + int half = m_gridHu / 2; + return ((hu + (hu >= 0 ? half : -half)) / m_gridHu) * m_gridHu; +} + +// 編集キャンバス(子ウィンドウ)を生成する。ダブルクリック受信・背景灰色。 +BOOL CCanvas::Create(CWnd* pParent, const CRect& rc, UINT id) +{ + LPCTSTR cls = AfxRegisterWndClass( + CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW, + ::LoadCursor(nullptr, IDC_ARROW), + (HBRUSH)::GetStockObject(LTGRAY_BRUSH)); + return CWnd::Create(cls, _T(""), + WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, + rc, pParent, id); +} + +// 指定 DPI と現在のズーム・余白から座標変換器(CoordMap)を作る。 +CoordMap CCanvas::MakeMap(int dpiX, int dpiY) const +{ + return CoordMap(dpiX, dpiY, m_zoom, kMargin, kMargin); +} + +// 画面 DC の実 DPI を使って座標変換器を作る(上のオーバーロードの簡易版)。 +CoordMap CCanvas::MakeMap() const +{ + CClientDC dc(const_cast(this)); + return MakeMap(dc.GetDeviceCaps(LOGPIXELSX), dc.GetDeviceCaps(LOGPIXELSY)); +} + +// クライアント座標(ピクセル)を内容座標(1/100mm)へ変換する。 +CPoint CCanvas::HuFromClient(CPoint pt) +{ + return MakeMap().DevToMm(pt); // スクロール無し: クライアント=内容座標 +} + +// ---- 描画 ---------------------------------------------------------------- + +BOOL CCanvas::OnEraseBkgnd(CDC* /*pDC*/) +{ + return TRUE; // OnPaint で全面描画(ちらつき防止) +} + +// 再描画。ちらつき防止のためメモリ DC(ダブルバッファ)に一旦描いてから転送する。 +// 用紙外を灰色で塗り、Render::DrawLayout で用紙・原紙・グリッド・アイテムを描く。 +void CCanvas::OnPaint() +{ + CPaintDC dc(this); + CRect client; + GetClientRect(client); + + // ダブルバッファ + CDC mem; + mem.CreateCompatibleDC(&dc); + CBitmap bmp; + bmp.CreateCompatibleBitmap(&dc, client.Width(), client.Height()); + CBitmap* pOldBmp = mem.SelectObject(&bmp); + + // 用紙外(灰色) + mem.FillSolidRect(client, RGB(200, 200, 200)); + + if (m_pLayout) + { + CoordMap map = MakeMap(mem.GetDeviceCaps(LOGPIXELSX), mem.GetDeviceCaps(LOGPIXELSY)); + Render::DrawLayout(mem, *m_pLayout, map, /*screen=*/true, m_pSelected, + m_snap ? m_gridHu : 0); + } + + dc.BitBlt(0, 0, client.Width(), client.Height(), &mem, 0, 0, SRCCOPY); + mem.SelectObject(pOldBmp); +} + +// ---- ズーム -------------------------------------------------------------- + +// 表示倍率を設定する(0.25〜30倍にクランプ)。再描画も行う。 +void CCanvas::SetZoom(double z) +{ + if (z < 0.25) z = 0.25; + if (z > 30.0) z = 30.0; + m_zoom = z; + Invalidate(); +} + +// 用紙全体がキャンバスに収まる倍率へ自動調整する。 +void CCanvas::FitZoom() +{ + if (!m_pLayout) return; + CRect client; + GetClientRect(client); + CClientDC dc(this); + int dpiX = dc.GetDeviceCaps(LOGPIXELSX); + int dpiY = dc.GetDeviceCaps(LOGPIXELSY); + // zoom=1 のときの用紙デバイス幅/高さ + double pw = (double)m_pLayout->m_paperWidthHu * dpiX / 2540.0; + double ph = (double)m_pLayout->m_paperHeightHu * dpiY / 2540.0; + double zx = (client.Width() - kMargin * 2) / max(1.0, pw); + double zy = (client.Height() - kMargin * 2) / max(1.0, ph); + SetZoom(min(zx, zy)); +} + +// ---- マウス -------------------------------------------------------------- + +// 左ボタン押下。選択アイテムのハンドル上ならリサイズ開始、 +// アイテム本体上なら選択+移動開始、何も無ければ選択解除。 +void CCanvas::OnLButtonDown(UINT nFlags, CPoint point) +{ + SetFocus(); + if (!m_pLayout) return; + CPoint huPt = HuFromClient(point); + + if (m_pSelected) + { + CoordMap map = MakeMap(); + CRect devRect = map.MmToDev(m_pSelected->Rect()); + int handle = CPrintItem::HitHandle(devRect, point); + if (handle >= 0) + { + m_drag = Drag::Resize; + m_dragHandle = handle; + m_dragStartHu = huPt; + m_origRect = m_pSelected->Rect(); + SetCapture(); + return; + } + } + + CPrintItem* hit = m_pLayout->HitTest(huPt); + if (hit != m_pSelected) + { + m_pSelected = hit; + Invalidate(); + } + if (hit) + { + m_drag = Drag::Move; + m_dragStartHu = huPt; + m_origRect = hit->Rect(); + SetCapture(); + } + else + { + m_drag = Drag::None; + } + CWnd::OnLButtonDown(nFlags, point); +} + +// マウス移動。ドラッグ中なら選択アイテムを移動/リサイズする。 +// スナップ有効時は結果の座標をグリッドへ吸着。最小サイズも保証。 +void CCanvas::OnMouseMove(UINT nFlags, CPoint point) +{ + if (m_drag != Drag::None && m_pSelected) + { + CPoint huPt = HuFromClient(point); + int dx = huPt.x - m_dragStartHu.x; + int dy = huPt.y - m_dragStartHu.y; + + CRect r = m_origRect; + if (m_drag == Drag::Move) + { + r.OffsetRect(dx, dy); + if (m_snap) + { + // 左上をグリッドへスナップ(サイズは保つ) + r.OffsetRect(SnapHu(r.left) - r.left, SnapHu(r.top) - r.top); + } + } + else + { + switch (m_dragHandle) + { + case 0: r.left += dx; r.top += dy; break; + case 1: r.top += dy; break; + case 2: r.right += dx; r.top += dy; break; + case 3: r.right += dx; break; + case 4: r.right += dx; r.bottom += dy; break; + case 5: r.bottom += dy; break; + case 6: r.left += dx; r.bottom += dy; break; + case 7: r.left += dx; break; + } + if (m_snap) + { + r.left = SnapHu(r.left); r.top = SnapHu(r.top); + r.right = SnapHu(r.right); r.bottom = SnapHu(r.bottom); + } + if (r.Width() < 50) r.right = r.left + 50; + if (r.Height() < 50) r.bottom = r.top + 50; + } + m_pSelected->SetRect(r); + m_dirty = TRUE; + Invalidate(); + } + CWnd::OnMouseMove(nFlags, point); +} + +// 左ボタン離し。ドラッグ(移動/リサイズ)を終了しキャプチャを解放する。 +void CCanvas::OnLButtonUp(UINT nFlags, CPoint point) +{ + if (m_drag != Drag::None) + { + m_drag = Drag::None; + m_dragHandle = -1; + if (GetCapture() == this) + ReleaseCapture(); + } + CWnd::OnLButtonUp(nFlags, point); +} + +// ダブルクリック。アイテム上ならそれを選択してプロパティ編集を開く。 +void CCanvas::OnLButtonDblClk(UINT nFlags, CPoint point) +{ + if (m_pLayout) + { + CPoint huPt = HuFromClient(point); + CPrintItem* hit = m_pLayout->HitTest(huPt); + if (hit) + { + m_pSelected = hit; + EditSelectedProperties(); + Invalidate(); + return; + } + } + CWnd::OnLButtonDblClk(nFlags, point); +} + +// マウスカーソル形状の制御。選択アイテムのハンドル上では方向に応じた +// リサイズカーソル(↔ ↕ ⤡ ⤢)に切り替える。 +BOOL CCanvas::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) +{ + if (nHitTest == HTCLIENT && m_pSelected) + { + CPoint pt; + GetCursorPos(&pt); + ScreenToClient(&pt); + CoordMap map = MakeMap(); + CRect devRect = map.MmToDev(m_pSelected->Rect()); + int h = CPrintItem::HitHandle(devRect, pt); + if (h >= 0) + { + LPCTSTR cur = IDC_SIZEALL; + switch (h) + { + case 0: case 4: cur = IDC_SIZENWSE; break; + case 2: case 6: cur = IDC_SIZENESW; break; + case 1: case 5: cur = IDC_SIZENS; break; + case 3: case 7: cur = IDC_SIZEWE; break; + } + ::SetCursor(::LoadCursor(nullptr, cur)); + return TRUE; + } + } + return CWnd::OnSetCursor(pWnd, nHitTest, message); +} + +// キー操作。Delete=削除、Ctrl+C/V/D=コピー/貼付/複製、Ctrl+]/[ =前面/背面、 +// 矢印=微調整(スナップ時はグリッド幅・通常0.1mm、Shiftで10倍)。 +void CCanvas::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) +{ + BOOL ctrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0; + BOOL shift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0; + + switch (nChar) + { + case VK_DELETE: + DeleteSelected(); + return; + case 'C': if (ctrl) { CopySelected(); return; } break; + case 'V': if (ctrl) { Paste(); return; } break; + case 'D': if (ctrl) { DuplicateSelected(); return; } break; + case VK_OEM_6: if (ctrl) { BringSelectedForward(); return; } break; // ']' + case VK_OEM_4: if (ctrl) { SendSelectedBackward(); return; } break; // '[' + case VK_LEFT: case VK_RIGHT: case VK_UP: case VK_DOWN: + if (m_pSelected) + { + int step = m_snap ? m_gridHu : 10; // 10hu = 0.1mm + if (shift) step *= 10; + int dx = 0, dy = 0; + if (nChar == VK_LEFT) dx = -step; + else if (nChar == VK_RIGHT) dx = step; + else if (nChar == VK_UP) dy = -step; + else dy = step; + CRect r = m_pSelected->Rect(); + r.OffsetRect(dx, dy); + m_pSelected->SetRect(r); + m_dirty = TRUE; + Invalidate(); + return; + } + break; + } + CWnd::OnKeyDown(nChar, nRepCnt, nFlags); +} + +// このコントロールが欲しいキー種別を返す。矢印キーがダイアログのフォーカス移動に +// 奪われないよう DLGC_WANTARROWS を返し、矢印での微調整を確実に受け取る。 +UINT CCanvas::OnGetDlgCode() +{ + return DLGC_WANTARROWS | DLGC_WANTCHARS; +} + +// ---- 挿入・編集 ---------------------------------------------------------- + +// アイテムをレイアウトへ追加し、それを選択状態にして再描画する(挿入系の共通処理)。 +void CCanvas::AddAndSelect(CPrintItem* p) +{ + m_pLayout->AddItem(p); + m_pSelected = p; + m_dirty = TRUE; + Invalidate(); +} + +// テキストアイテムを追加。用紙中央に置き、プロパティ入力ダイアログで +// 内容を確定したら追加する(キャンセル時は破棄)。 +void CCanvas::InsertText() +{ + if (!m_pLayout) return; + CTextItem* p = new CTextItem(); + int w = 3000, h = 800; + int x = (m_pLayout->m_paperWidthHu - w) / 2; + int y = (m_pLayout->m_paperHeightHu - h) / 2; + p->SetRect(CRect(x, y, x + w, y + h)); + + CTextPropsDlg dlg(this); + dlg.m_text = p->m_text; + dlg.m_fontName = p->m_fontName; + dlg.m_pointSize = p->m_pointSize; + dlg.m_align = p->m_align; + dlg.m_bold = p->m_bold; + if (dlg.DoModal() == IDOK) + { + p->m_text = dlg.m_text; + p->m_fontName = dlg.m_fontName; + p->m_pointSize = dlg.m_pointSize; + p->m_align = dlg.m_align; + p->m_bold = dlg.m_bold; + AddAndSelect(p); + } + else + { + delete p; + } +} + +// 矩形枠アイテムを追加(用紙の約6割×4割サイズで中央配置)。 +void CCanvas::InsertRect() +{ + if (!m_pLayout) return; + CShapeItem* p = new CShapeItem(); + p->m_shape = 0; + int w = m_pLayout->m_paperWidthHu * 6 / 10; + int h = m_pLayout->m_paperHeightHu * 4 / 10; + int x = (m_pLayout->m_paperWidthHu - w) / 2; + int y = (m_pLayout->m_paperHeightHu - h) / 2; + p->SetRect(CRect(x, y, x + w, y + h)); + AddAndSelect(p); +} + +// 直線アイテムを追加(用紙中央の水平線)。 +void CCanvas::InsertLine() +{ + if (!m_pLayout) return; + CShapeItem* p = new CShapeItem(); + p->m_shape = 1; + int y = m_pLayout->m_paperHeightHu / 2; + p->SetRect(CRect(m_pLayout->m_paperWidthHu / 10, y, + m_pLayout->m_paperWidthHu * 9 / 10, y)); + AddAndSelect(p); +} + +// 画像アイテムを追加。ファイル選択→読み込み成功なら用紙中央に半分サイズで配置。 +void CCanvas::InsertImage() +{ + if (!m_pLayout) return; + CFileDialog dlg(TRUE, nullptr, nullptr, + OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, + _T("画像ファイル (*.png;*.jpg;*.jpeg;*.bmp;*.gif)|*.png;*.jpg;*.jpeg;*.bmp;*.gif|すべて (*.*)|*.*||"), + this); + if (dlg.DoModal() != IDOK) + return; + + CImageItem* p = new CImageItem(); + if (!p->LoadFromFile(dlg.GetPathName())) + { + delete p; + AfxMessageBox(_T("画像を読み込めませんでした。")); + return; + } + int w = m_pLayout->m_paperWidthHu / 2; + int h = m_pLayout->m_paperHeightHu / 2; + int x = (m_pLayout->m_paperWidthHu - w) / 2; + int y = (m_pLayout->m_paperHeightHu - h) / 2; + p->SetRect(CRect(x, y, x + w, y + h)); + AddAndSelect(p); +} + +// 選択アイテムのプロパティを編集する。現状はテキストのみ編集ダイアログを持つ。 +void CCanvas::EditSelectedProperties() +{ + if (!m_pSelected) return; + if (m_pSelected->Type() == ItemType::Text) + { + CTextItem* t = static_cast(m_pSelected); + CTextPropsDlg dlg(this); + dlg.m_text = t->m_text; + dlg.m_fontName = t->m_fontName; + dlg.m_pointSize = t->m_pointSize; + dlg.m_align = t->m_align; + dlg.m_bold = t->m_bold; + if (dlg.DoModal() == IDOK) + { + t->m_text = dlg.m_text; + t->m_fontName = dlg.m_fontName; + t->m_pointSize = dlg.m_pointSize; + t->m_align = dlg.m_align; + t->m_bold = dlg.m_bold; + m_dirty = TRUE; + Invalidate(); + } + } + else + { + AfxMessageBox(_T("この種類のアイテムには編集可能なプロパティがありません。")); + } +} + +// 選択中アイテムを削除する。 +void CCanvas::DeleteSelected() +{ + if (!m_pSelected || !m_pLayout) return; + m_pLayout->DeleteItem(m_pSelected); + m_pSelected = nullptr; + m_dirty = TRUE; + Invalidate(); +} + +// ---- コピー / 複製 ------------------------------------------------------- + +// 選択アイテムをクリップボード(自前保持)へ複製して控える。 +void CCanvas::CopySelected() +{ + if (!m_pSelected) return; + delete m_pClipboard; + m_pClipboard = m_pSelected->Clone(); +} + +// クリップボードの内容を 1mm ずらして貼り付け、選択状態にする。 +void CCanvas::Paste() +{ + if (!m_pClipboard || !m_pLayout) return; + CPrintItem* p = m_pClipboard->Clone(); + CRect r = p->Rect(); + r.OffsetRect(100, 100); // 1mm ずらして重なりを避ける + p->SetRect(r); + AddAndSelect(p); +} + +// 選択アイテムをその場で複製し(1mm ずらし)、複製を選択状態にする。 +void CCanvas::DuplicateSelected() +{ + if (!m_pSelected || !m_pLayout) return; + CPrintItem* p = m_pSelected->Clone(); + CRect r = p->Rect(); + r.OffsetRect(100, 100); + p->SetRect(r); + AddAndSelect(p); +} + +// ---- z オーダー ---------------------------------------------------------- + +// 選択アイテムを最前面へ。 +void CCanvas::BringSelectedToFront() +{ + if (!m_pSelected || !m_pLayout) return; + m_pLayout->BringToFront(m_pSelected); + m_dirty = TRUE; + Invalidate(); +} + +// 選択アイテムを最背面へ。 +void CCanvas::SendSelectedToBack() +{ + if (!m_pSelected || !m_pLayout) return; + m_pLayout->SendToBack(m_pSelected); + m_dirty = TRUE; + Invalidate(); +} + +// 選択アイテムを1つ前面へ。 +void CCanvas::BringSelectedForward() +{ + if (!m_pSelected || !m_pLayout) return; + m_pLayout->BringForward(m_pSelected); + m_dirty = TRUE; + Invalidate(); +} + +// 選択アイテムを1つ背面へ。 +void CCanvas::SendSelectedBackward() +{ + if (!m_pSelected || !m_pLayout) return; + m_pLayout->SendBackward(m_pSelected); + m_dirty = TRUE; + Invalidate(); +} + +// ---- 整列(用紙/余白の内側矩形を基準) ------------------------------------ + +// 選択アイテムを基準矩形(余白があれば余白内側、無ければ用紙)に対して整列する。 +// how: 0=左 1=水平中央 2=右 3=上 4=垂直中央 5=下。サイズは変えず位置だけ移動。 +void CCanvas::AlignSelected(int how) +{ + if (!m_pSelected || !m_pLayout) return; + CRect b = m_pLayout->ContentRect(); + CRect r = m_pSelected->Rect(); + int w = r.Width(), h = r.Height(); + switch (how) + { + case 0: r.left = b.left; r.right = r.left + w; break; // 左 + case 1: { int cx = (b.left + b.right) / 2; r.left = cx - w / 2; r.right = r.left + w; } break; // 水平中央 + case 2: r.right = b.right; r.left = r.right - w; break; // 右 + case 3: r.top = b.top; r.bottom = r.top + h; break; // 上 + case 4: { int cy = (b.top + b.bottom) / 2; r.top = cy - h / 2; r.bottom = r.top + h; } break; // 垂直中央 + case 5: r.bottom = b.bottom; r.top = r.bottom - h; break; // 下 + } + m_pSelected->SetRect(r); + m_dirty = TRUE; + Invalidate(); +} + +// 「新規」「開く」でレイアウトが差し替わった後に呼ぶ。選択解除して再描画する。 +void CCanvas::OnLayoutReplaced() +{ + m_pSelected = nullptr; + m_dirty = FALSE; + Invalidate(); +} diff --git a/Canvas.h b/Canvas.h new file mode 100644 index 0000000..52db1f2 --- /dev/null +++ b/Canvas.h @@ -0,0 +1,84 @@ +// Canvas.h : ラベル編集キャンバス(ダイアログ上の子ウィンドウ) +// 旧 CLabelView の編集ロジックを CWnd ベースに移植(スクロール無し・ズーム有り)。 +#pragma once + +#include "CoordMap.h" + +class CLabelLayout; +class CPrintItem; + +class CCanvas : public CWnd +{ +public: + CCanvas(); + virtual ~CCanvas(); + + BOOL Create(CWnd* pParent, const CRect& rc, UINT id); + void SetLayout(CLabelLayout* p) { m_pLayout = p; m_pSelected = nullptr; } + + // ダイアログのボタンから呼ぶ操作 + void InsertText(); + void InsertRect(); + void InsertLine(); + void InsertImage(); + void EditSelectedProperties(); + void DeleteSelected(); + void ZoomIn() { SetZoom(m_zoom * 1.25); } + void ZoomOut() { SetZoom(m_zoom / 1.25); } + void FitZoom(); // 用紙がキャンバスに収まる倍率へ + + // コピー/複製/Zオーダー/整列(ダイアログ・キーから呼ぶ) + void CopySelected(); + void Paste(); + void DuplicateSelected(); + void BringSelectedToFront(); + void SendSelectedToBack(); + void BringSelectedForward(); + void SendSelectedBackward(); + void AlignSelected(int how); // 0=左 1=水平中央 2=右 3=上 4=垂直中央 5=下 + + // グリッド/スナップ + void SetSnap(BOOL b) { m_snap = b; Invalidate(); } + BOOL GetSnap() const { return m_snap; } + + BOOL HasSelection() const { return m_pSelected != nullptr; } + BOOL IsDirty() const { return m_dirty; } + void SetDirty(BOOL b) { m_dirty = b; } + void OnLayoutReplaced(); // New/Open 後の再描画+選択解除 + + double m_zoom; + +protected: + CLabelLayout* m_pLayout; + CPrintItem* m_pSelected; + CPrintItem* m_pClipboard; // コピー保持(自前所有) + BOOL m_dirty; + BOOL m_snap; // グリッドスナップ有効 + int m_gridHu; // グリッド間隔(1/100mm) + int SnapHu(int hu) const; // 最近接グリッドへ丸め + + enum class Drag { None, Move, Resize }; + Drag m_drag; + int m_dragHandle; + CPoint m_dragStartHu; + CRect m_origRect; + + static const int kMargin = 14; + CoordMap MakeMap() const; + CoordMap MakeMap(int dpiX, int dpiY) const; + CPoint HuFromClient(CPoint pt); + void SetZoom(double z); + void AddAndSelect(CPrintItem* p); + + afx_msg void OnPaint(); + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnLButtonUp(UINT nFlags, CPoint point); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point); + afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); + afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); + afx_msg UINT OnGetDlgCode(); // 矢印キー等をキャンバスへ届ける + + DECLARE_MESSAGE_MAP() +}; diff --git a/CoordMap.h b/CoordMap.h new file mode 100644 index 0000000..25e1e33 --- /dev/null +++ b/CoordMap.h @@ -0,0 +1,79 @@ +// CoordMap.h : 物理単位(1/100mm) ⇔ デバイス単位(ピクセル) 変換 +// +// 設計の背骨: +// - すべてのジオメトリ(用紙サイズ・各アイテムの矩形)は 1/100 mm の整数で保持する。 +// - 画面エディタも印刷(OnPrint)も「同じ」描画コード Draw(CDC&, CoordMap&) を呼ぶ。 +// 違いはこの CoordMap が保持する DPI とズーム倍率だけ。 +// - これにより、画面(≒96dpi)とサーマルラベルプリンタ(≒203dpi)で +// 同一レイアウトが物理的に正しいサイズで出力される。 +// +// 単位系メモ: 1 inch = 25.4 mm = 2540 (1/100mm) +#pragma once + +class CoordMap +{ +public: + // dpiX/dpiY : 出力デバイスの解像度(GetDeviceCaps(LOGPIXELSX/Y)) + // zoom : 画面表示倍率(印刷時は 1.0) + // originDevX/Y : デバイス座標の原点オフセット(スクロール量や印刷可能領域オフセット) + CoordMap(int dpiX = 96, int dpiY = 96, double zoom = 1.0, + int originDevX = 0, int originDevY = 0) + : m_dpiX(dpiX), m_dpiY(dpiY), m_zoom(zoom), + m_originX(originDevX), m_originY(originDevY) + { + } + + void SetZoom(double zoom) { m_zoom = zoom; } + double Zoom() const { return m_zoom; } + + void SetOrigin(int devX, int devY) { m_originX = devX; m_originY = devY; } + + // 1/100mm → デバイス(ピクセル)。原点オフセットとズームを適用。 + int MmToDevX(int hu) const { return m_originX + ScaleX(hu); } + int MmToDevY(int hu) const { return m_originY + ScaleY(hu); } + + // 寸法(長さ)変換: 原点オフセットを加えない + int LenToDevX(int hu) const { return ScaleX(hu); } + int LenToDevY(int hu) const { return ScaleY(hu); } + + CRect MmToDev(const CRect& huRect) const + { + return CRect(MmToDevX(huRect.left), MmToDevY(huRect.top), + MmToDevX(huRect.right), MmToDevY(huRect.bottom)); + } + + CPoint MmToDev(const CPoint& huPt) const + { + return CPoint(MmToDevX(huPt.x), MmToDevY(huPt.y)); + } + + // デバイス(ピクセル) → 1/100mm + int DevToMmX(int dev) const { return UnscaleX(dev - m_originX); } + int DevToMmY(int dev) const { return UnscaleY(dev - m_originY); } + + CPoint DevToMm(const CPoint& devPt) const + { + return CPoint(DevToMmX(devPt.x), DevToMmY(devPt.y)); + } + + // ポイント(1pt = 1/72inch) → デバイス上のフォント高さ(論理ピクセル, ズーム込み) + int PointToDevHeight(double pt) const + { + // pt → 1/100mm → デバイス + int hu = (int)(pt * 2540.0 / 72.0 + 0.5); + return LenToDevY(hu); + } + +private: + // hu(1/100mm) → デバイス: dev = hu / 2540 * dpi * zoom + int ScaleX(int hu) const { return (int)((double)hu * m_dpiX * m_zoom / 2540.0 + (hu >= 0 ? 0.5 : -0.5)); } + int ScaleY(int hu) const { return (int)((double)hu * m_dpiY * m_zoom / 2540.0 + (hu >= 0 ? 0.5 : -0.5)); } + int UnscaleX(int dev) const { return (int)((double)dev * 2540.0 / (m_dpiX * m_zoom) + (dev >= 0 ? 0.5 : -0.5)); } + int UnscaleY(int dev) const { return (int)((double)dev * 2540.0 / (m_dpiY * m_zoom) + (dev >= 0 ? 0.5 : -0.5)); } + + int m_dpiX; + int m_dpiY; + double m_zoom; + int m_originX; + int m_originY; +}; diff --git a/ImageItem.cpp b/ImageItem.cpp new file mode 100644 index 0000000..95ca5a6 --- /dev/null +++ b/ImageItem.cpp @@ -0,0 +1,199 @@ +// ImageItem.cpp +#include "pch.h" +#include "ImageItem.h" +#include +using std::min; // NOMINMAX 下で gdiplus.h が min/max を使うための保険 +using std::max; +#include +#pragma comment(lib, "gdiplus.lib") + +IMPLEMENT_SERIAL(CImageItem, CPrintItem, 1) + +// コンストラクタ。既定は縦横比保持フィット(m_fit=1)。 +CImageItem::CImageItem() + : m_fit(1) +{ +} + +// コピーコンストラクタ。画像バイト列はコピーするが、描画キャッシュ(m_cache)は +// 複製せず必要時に再生成する。Clone() から利用される。 +CImageItem::CImageItem(const CImageItem& other) + : CPrintItem(other) + , m_fit(other.m_fit) +{ + m_bytes.Copy(other.m_bytes); + // m_cache は複製しない(遅延再生成) +} + +// 画像ファイルを読み込み、その生バイト列を m_bytes に取り込む(.otz 埋め込み用)。 +// 読み込み妥当性の確認も兼ねて一度デコードする。失敗時は FALSE。 +BOOL CImageItem::LoadFromFile(LPCTSTR path) +{ + CFile file; + if (!file.Open(path, CFile::modeRead | CFile::shareDenyWrite)) + return FALSE; + + ULONGLONG len = file.GetLength(); + if (len == 0 || len > (ULONGLONG)INT_MAX) + return FALSE; + + m_bytes.SetSize((INT_PTR)len); + file.Read(m_bytes.GetData(), (UINT)len); + file.Close(); + + m_cache.reset(); + // 妥当性確認 + 既定サイズ算出のため一度ロード + CImage* img = EnsureImage(); + if (img == nullptr) + { + m_bytes.RemoveAll(); + return FALSE; + } + return TRUE; +} + +// m_bytes から CImage を遅延生成して返す(描画キャッシュ)。 +// 既に生成済みならそれを返す。バイト列が無効なら nullptr。 +CImage* CImageItem::EnsureImage() +{ + if (m_cache && !m_cache->IsNull()) + return m_cache.get(); + + if (m_bytes.GetSize() == 0) + return nullptr; + + HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)m_bytes.GetSize()); + if (!hMem) + return nullptr; + + void* p = ::GlobalLock(hMem); + memcpy(p, m_bytes.GetData(), (size_t)m_bytes.GetSize()); + ::GlobalUnlock(hMem); + + IStream* pStream = nullptr; + if (FAILED(::CreateStreamOnHGlobal(hMem, TRUE, &pStream))) + { + ::GlobalFree(hMem); + return nullptr; + } + + auto img = std::make_shared(); + HRESULT hr = img->Load(pStream); + pStream->Release(); // fDeleteOnRelease=TRUE のため hMem も解放される + if (FAILED(hr) || img->IsNull()) + return nullptr; + + m_cache = img; + return m_cache.get(); +} + +BOOL CImageItem::GetPixelSize(int& w, int& h) +{ + CImage* img = EnsureImage(); + if (!img) return FALSE; + w = img->GetWidth(); + h = img->GetHeight(); + return (w > 0 && h > 0); +} + +BOOL CImageItem::GetResolutionDpi(double& dx, double& dy) +{ + // EnsureImage() 成功中は atlimage が GDI+ を起動済みなので、 + // ここでは GdiplusStartup を呼ばず Gdiplus::Bitmap で解像度だけ読む。 + if (EnsureImage() == nullptr || m_bytes.GetSize() == 0) + return FALSE; + + HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)m_bytes.GetSize()); + if (!hMem) return FALSE; + void* p = ::GlobalLock(hMem); + memcpy(p, m_bytes.GetData(), (size_t)m_bytes.GetSize()); + ::GlobalUnlock(hMem); + + IStream* pStream = nullptr; + if (FAILED(::CreateStreamOnHGlobal(hMem, TRUE, &pStream))) + { + ::GlobalFree(hMem); + return FALSE; + } + + BOOL ok = FALSE; + { + Gdiplus::Bitmap bmp(pStream, FALSE); + if (bmp.GetLastStatus() == Gdiplus::Ok) + { + Gdiplus::REAL hx = bmp.GetHorizontalResolution(); + Gdiplus::REAL hy = bmp.GetVerticalResolution(); + if (hx > 0 && hy > 0) { dx = hx; dy = hy; ok = TRUE; } + } + } + pStream->Release(); // fDeleteOnRelease=TRUE のため hMem も解放 + return ok; +} + +// 画像を矩形内に描画する。m_fit=1 なら縦横比を保って収め、0 なら矩形いっぱいに +// 引き伸ばす。画像が読めない場合は ×印付きのプレースホルダ枠を描く。 +void CImageItem::Draw(CDC& dc, const CoordMap& map, bool selected) +{ + CRect dr = map.MmToDev(m_rect); + CImage* img = EnsureImage(); + + if (img) + { + CRect dest = dr; + if (m_fit == 1 && img->GetWidth() > 0 && img->GetHeight() > 0) + { + // 縦横比保持で矩形内にフィット + double sr = (double)img->GetWidth() / img->GetHeight(); + double dRatio = (double)dr.Width() / max(1, dr.Height()); + if (sr > dRatio) + { + int h = (int)(dr.Width() / sr + 0.5); + dest = CRect(dr.left, dr.top, dr.right, dr.top + h); + } + else + { + int w = (int)(dr.Height() * sr + 0.5); + dest = CRect(dr.left, dr.top, dr.left + w, dr.bottom); + } + } + int oldMode = dc.SetStretchBltMode(HALFTONE); + ::SetBrushOrgEx(dc.GetSafeHdc(), 0, 0, nullptr); + img->StretchBlt(dc.GetSafeHdc(), dest, CRect(0, 0, img->GetWidth(), img->GetHeight()), SRCCOPY); + dc.SetStretchBltMode(oldMode); + } + else + { + // 画像なし: プレースホルダ枠 + ×印 + CBrush* pOldBrush = (CBrush*)dc.SelectStockObject(NULL_BRUSH); + dc.Rectangle(dr); + dc.MoveTo(dr.left, dr.top); dc.LineTo(dr.right, dr.bottom); + dc.MoveTo(dr.right, dr.top); dc.LineTo(dr.left, dr.bottom); + dc.SelectObject(pOldBrush); + } + + if (selected) + DrawSelection(dc, dr); +} + +// 自身の複製を生成して返す(画像バイト列ごと複製)。 +CPrintItem* CImageItem::Clone() const +{ + return new CImageItem(*this); +} + +// 永続化。基底(矩形)に続けてフィット方式と画像バイト列を読み書きする。 +void CImageItem::Serialize(CArchive& ar) +{ + CPrintItem::Serialize(ar); + if (ar.IsStoring()) + { + ar << m_fit; + m_bytes.Serialize(ar); + } + else + { + ar >> m_fit; + m_bytes.Serialize(ar); + m_cache.reset(); + } +} diff --git a/ImageItem.h b/ImageItem.h new file mode 100644 index 0000000..d37da90 --- /dev/null +++ b/ImageItem.h @@ -0,0 +1,36 @@ +// ImageItem.h : 画像(ロゴ等)印刷アイテム +// 画像バイト列を .otz に埋め込み、ファイル単体で完結させる。 +#pragma once + +#include "PrintItem.h" +#include +#include + +class CImageItem : public CPrintItem +{ + DECLARE_SERIAL(CImageItem) +public: + CImageItem(); + CImageItem(const CImageItem& other); + + CByteArray m_bytes; // 元画像ファイルのバイト列(PNG/JPG/BMP) + int m_fit; // 0=矩形にフィット(縦横比無視), 1=縦横比保持 + + // ファイルから読み込んでバイト列を取り込む + BOOL LoadFromFile(LPCTSTR path); + + // 元画像の画素サイズ(px)。取得できれば TRUE。 + BOOL GetPixelSize(int& w, int& h); + // 元画像の埋め込み解像度(DPI)。情報が無ければ FALSE。 + BOOL GetResolutionDpi(double& dx, double& dy); + + ItemType Type() const override { return ItemType::Image; } + void Draw(CDC& dc, const CoordMap& map, bool selected) override; + CPrintItem* Clone() const override; + void Serialize(CArchive& ar) override; + +private: + // バイト列から CImage を遅延生成(描画キャッシュ。シリアライズ対象外) + CImage* EnsureImage(); + std::shared_ptr m_cache; +}; diff --git a/LabelLayout.cpp b/LabelLayout.cpp new file mode 100644 index 0000000..d296659 --- /dev/null +++ b/LabelLayout.cpp @@ -0,0 +1,278 @@ +// LabelLayout.cpp +#include "pch.h" +#include "LabelLayout.h" +#include "ImageItem.h" + +// コンストラクタ。既定の用紙(40×30mm, 203dpi, 余白0, 原紙なし)で初期化。 +CLabelLayout::CLabelLayout() + : m_paperWidthHu(4000) // 既定 40mm × 30mm (ロールラベル) + , m_paperHeightHu(3000) + , m_dpi(203) + , m_marginHu(0) + , m_pTemplate(nullptr) + , m_printTemplate(FALSE) +{ +} + +// デストラクタ。全アイテムと原紙を解放する。 +CLabelLayout::~CLabelLayout() +{ + RemoveAll(); + delete m_pTemplate; +} + +// 内容を空の既定レイアウトへ戻す(「新規」用)。 +void CLabelLayout::Reset() +{ + RemoveAll(); + ClearTemplate(); + m_paperWidthHu = 4000; + m_paperHeightHu = 3000; + m_dpi = 203; + m_marginHu = 0; + m_printTemplate = FALSE; +} + +// 原紙(背景下絵)を読み込む。用紙全体に敷くため fit=0(伸縮)で取り込む。 +// 失敗時は既存の原紙を保ったまま FALSE を返す。 +BOOL CLabelLayout::LoadTemplate(LPCTSTR path) +{ + CImageItem* t = new CImageItem(); + t->m_fit = 0; // 用紙矩形いっぱいに伸ばす(位置が一意に決まる) + if (!t->LoadFromFile(path)) + { + delete t; + return FALSE; + } + delete m_pTemplate; + m_pTemplate = t; + SyncTemplateRect(); + return TRUE; +} + +// 原紙を破棄する(「消去」用)。 +void CLabelLayout::ClearTemplate() +{ + delete m_pTemplate; + m_pTemplate = nullptr; +} + +// 原紙の矩形を現在の用紙サイズに合わせる(用紙サイズ変更時などに呼ぶ)。 +void CLabelLayout::SyncTemplateRect() +{ + if (m_pTemplate) + m_pTemplate->SetRect(CRect(0, 0, m_paperWidthHu, m_paperHeightHu)); +} + +// 原紙画像の画素サイズ(px)を取得。原紙が無ければ FALSE。 +BOOL CLabelLayout::GetTemplatePixelSize(int& w, int& h) const +{ + return m_pTemplate ? m_pTemplate->GetPixelSize(w, h) : FALSE; +} + +// 原紙画像の埋め込み解像度(DPI)を取得。情報が無ければ FALSE。 +BOOL CLabelLayout::GetTemplateDpi(double& dx, double& dy) const +{ + return m_pTemplate ? m_pTemplate->GetResolutionDpi(dx, dy) : FALSE; +} + +// 原紙の画素数から用紙サイズ(1/100mm)を決め直す。 +// useEmbeddedDpi=TRUE なら画像の埋め込みDPI、FALSE(または取得不可)なら +// m_dpi を基準に px→mm 換算する。 +void CLabelLayout::SetPaperSizeFromTemplate(BOOL useEmbeddedDpi) +{ + if (!m_pTemplate) return; + int w = 0, h = 0; + if (!m_pTemplate->GetPixelSize(w, h) || w <= 0 || h <= 0) return; + + double dx = m_dpi, dy = m_dpi; + if (useEmbeddedDpi) + { + double ex = 0, ey = 0; + if (m_pTemplate->GetResolutionDpi(ex, ey) && ex > 0 && ey > 0) { dx = ex; dy = ey; } + } + if (dx <= 0) dx = 203; + if (dy <= 0) dy = 203; + + int nw = (int)(w * 2540.0 / dx + 0.5); // px / dpi * 2540 = 1/100mm + int nh = (int)(h * 2540.0 / dy + 0.5); + if (nw > 0) m_paperWidthHu = nw; + if (nh > 0) m_paperHeightHu = nh; + SyncTemplateRect(); +} + +// 全アイテムを破棄して配列を空にする(原紙は対象外)。 +void CLabelLayout::RemoveAll() +{ + for (INT_PTR i = 0; i < m_items.GetCount(); ++i) + delete (CPrintItem*)m_items.GetAt(i); + m_items.RemoveAll(); +} + +// アイテムを最前面(配列末尾)に追加する。所有権はレイアウトが持つ。 +void CLabelLayout::AddItem(CPrintItem* p) +{ + m_items.Add(p); +} + +// 指定アイテムを配列から外して破棄する。 +void CLabelLayout::DeleteItem(CPrintItem* p) +{ + for (INT_PTR i = 0; i < m_items.GetCount(); ++i) + { + if ((CPrintItem*)m_items.GetAt(i) == p) + { + m_items.RemoveAt(i); + delete p; + return; + } + } +} + +// 指定座標(1/100mm)に当たるアイテムを最前面優先で返す。無ければ nullptr。 +CPrintItem* CLabelLayout::HitTest(const CPoint& huPt) const +{ + for (INT_PTR i = m_items.GetCount() - 1; i >= 0; --i) + { + CPrintItem* p = (CPrintItem*)m_items.GetAt(i); + if (p->HitTest(huPt)) + return p; + } + return nullptr; +} + +CRect CLabelLayout::ContentRect() const +{ + if (m_marginHu <= 0) + return CRect(0, 0, m_paperWidthHu, m_paperHeightHu); + int m = m_marginHu; + // 余白が大きすぎても潰れないようにクランプ + if (m * 2 >= m_paperWidthHu) m = (m_paperWidthHu - 1) / 2; + if (m * 2 >= m_paperHeightHu) m = (m_paperHeightHu - 1) / 2; + if (m < 0) m = 0; + return CRect(m, m, m_paperWidthHu - m, m_paperHeightHu - m); +} + +// 配列内でアイテム p の添字を返す(見つからなければ -1)。z オーダー操作の補助。 +static INT_PTR FindIndex(const CObArray& items, CPrintItem* p) +{ + for (INT_PTR i = 0; i < items.GetCount(); ++i) + if ((CPrintItem*)items.GetAt(i) == p) return i; + return -1; +} + +// 最前面へ(配列末尾へ移動)。 +void CLabelLayout::BringToFront(CPrintItem* p) +{ + INT_PTR i = FindIndex(m_items, p); + if (i < 0 || i == m_items.GetCount() - 1) return; + m_items.RemoveAt(i); + m_items.Add(p); +} + +// 最背面へ(配列先頭へ移動)。 +void CLabelLayout::SendToBack(CPrintItem* p) +{ + INT_PTR i = FindIndex(m_items, p); + if (i <= 0) return; + m_items.RemoveAt(i); + m_items.InsertAt(0, p); +} + +// 1つ前面へ(隣と入れ替え)。 +void CLabelLayout::BringForward(CPrintItem* p) +{ + INT_PTR i = FindIndex(m_items, p); + if (i < 0 || i == m_items.GetCount() - 1) return; + m_items.RemoveAt(i); + m_items.InsertAt(i + 1, p); +} + +// 1つ背面へ(隣と入れ替え)。 +void CLabelLayout::SendBackward(CPrintItem* p) +{ + INT_PTR i = FindIndex(m_items, p); + if (i <= 0) return; + m_items.RemoveAt(i); + m_items.InsertAt(i - 1, p); +} + +// .otz 形式マーカ。先頭 int がこれなら新形式(版数つき)、違えば旧形式 +// (先頭 int を m_paperWidthHu として読む)として後方互換を保つ。 +static const int kOtzMagic = 0x4F545A31; // 'OTZ1' +static const int kOtzVersion = 3; // 2:m_marginHu / 3:原紙(template) + +// レイアウト全体(用紙設定・余白・原紙・全アイテム)を .otz へ読み書きする。 +// 先頭にマジック+版数を持つ新形式。マジックが無いファイルは旧形式として読む。 +void CLabelLayout::Serialize(CArchive& ar) +{ + if (ar.IsStoring()) + { + ar << kOtzMagic << kOtzVersion; + ar << m_paperWidthHu << m_paperHeightHu << m_dpi << m_marginHu; + ar << (int)m_printTemplate; + ar << (CObject*)m_pTemplate; // nullptr も CArchive が扱う + } + else + { + // 既存の原紙は破棄してから読み直す + delete m_pTemplate; + m_pTemplate = nullptr; + m_printTemplate = FALSE; + + int first; + ar >> first; + if (first == kOtzMagic) + { + int ver; + ar >> ver; + ar >> m_paperWidthHu >> m_paperHeightHu >> m_dpi; + m_marginHu = 0; + if (ver >= 2) ar >> m_marginHu; + if (ver >= 3) + { + int pt; ar >> pt; m_printTemplate = pt; + CObject* pObj = nullptr; + ar >> pObj; + if (pObj && pObj->IsKindOf(RUNTIME_CLASS(CImageItem))) + m_pTemplate = (CImageItem*)pObj; + else + delete pObj; // 想定外の型(通常 nullptr) + } + } + else + { + // 旧形式: 先頭 int は m_paperWidthHu + m_paperWidthHu = first; + ar >> m_paperHeightHu >> m_dpi; + m_marginHu = 0; + } + SyncTemplateRect(); + } + // CObArray::Serialize が各 CPrintItem* を多態的に保存/復元する + m_items.Serialize(ar); +} + +// レイアウトを指定パスの .otz ファイルへ保存する。成功で TRUE。 +BOOL CLabelLayout::SaveToFile(LPCTSTR path) +{ + CFile file; + if (!file.Open(path, CFile::modeCreate | CFile::modeWrite)) + return FALSE; + CArchive ar(&file, CArchive::store); + try { Serialize(ar); ar.Close(); } + catch (CException* e) { e->Delete(); return FALSE; } + return TRUE; +} + +// 指定パスの .otz ファイルからレイアウトを読み込む。成功で TRUE。 +BOOL CLabelLayout::LoadFromFile(LPCTSTR path) +{ + CFile file; + if (!file.Open(path, CFile::modeRead | CFile::shareDenyWrite)) + return FALSE; + CArchive ar(&file, CArchive::load); + try { RemoveAll(); Serialize(ar); ar.Close(); } + catch (CException* e) { e->Delete(); return FALSE; } + return TRUE; +} diff --git a/LabelLayout.h b/LabelLayout.h new file mode 100644 index 0000000..f3f7240 --- /dev/null +++ b/LabelLayout.h @@ -0,0 +1,63 @@ +// LabelLayout.h : ラベルレイアウトのデータモデル(Doc/View に依存しない) +// 全印刷領域(用紙サイズ)と複数の印刷アイテムを保持し、.otz の保存/読込を行う。 +#pragma once + +#include "PrintItem.h" + +class CImageItem; + +class CLabelLayout +{ +public: + CLabelLayout(); + ~CLabelLayout(); + + // 全印刷領域(用紙サイズ, 1/100mm) + int m_paperWidthHu; + int m_paperHeightHu; + int m_dpi; // 想定プリンタ解像度(プリセット情報) + int m_marginHu; // 四辺一律の内側余白(1/100mm, 0=なし) + + // 原紙(位置合わせ用の背景画像)。用紙全体に敷く下絵。 + // 画面では常に薄く表示し、印刷は m_printTemplate のときだけ行う。 + CImageItem* m_pTemplate; // nullptr = 原紙なし + BOOL m_printTemplate;// 原紙自身を印刷するか(既定 FALSE) + + CObArray m_items; // CPrintItem* (描画順 = z オーダー) + + void AddItem(CPrintItem* p); + void DeleteItem(CPrintItem* p); + void RemoveAll(); + int GetItemCount() const { return (int)m_items.GetCount(); } + CPrintItem* GetItem(int i) const { return (CPrintItem*)m_items.GetAt(i); } + CPrintItem* HitTest(const CPoint& huPt) const; // 最前面優先。無ければ nullptr + + // 余白を考慮した内側矩形(整列の基準・ガイド描画用)。余白0なら用紙全体。 + CRect ContentRect() const; + + // 原紙(背景下絵)操作 + BOOL LoadTemplate(LPCTSTR path); // 読込(用紙全体に敷く)。失敗で FALSE + void ClearTemplate(); // 原紙を破棄 + CImageItem* Template() const { return m_pTemplate; } + BOOL HasTemplate() const { return m_pTemplate != nullptr; } + void SyncTemplateRect(); // 原紙の矩形を現在の用紙サイズに合わせる + + // 原紙の画素サイズ/埋め込みDPI(取得可なら TRUE) + BOOL GetTemplatePixelSize(int& w, int& h) const; + BOOL GetTemplateDpi(double& dx, double& dy) const; + // 原紙の画素数から用紙サイズを設定。useEmbeddedDpi=TRUE で画像の埋め込み + // DPI、FALSE(または埋め込み無し)で m_dpi を基準に px→1/100mm 換算する。 + void SetPaperSizeFromTemplate(BOOL useEmbeddedDpi); + + // z オーダー操作(m_items 内の位置を入替) + void BringToFront(CPrintItem* p); + void SendToBack(CPrintItem* p); + void BringForward(CPrintItem* p); + void SendBackward(CPrintItem* p); + + void Reset(); // 既定の空レイアウトに戻す + + void Serialize(CArchive& ar); + BOOL SaveToFile(LPCTSTR path); + BOOL LoadFromFile(LPCTSTR path); +}; diff --git a/MainDialog.cpp b/MainDialog.cpp new file mode 100644 index 0000000..a37feb3 --- /dev/null +++ b/MainDialog.cpp @@ -0,0 +1,422 @@ +// MainDialog.cpp +#include "pch.h" +#include "AveryDennisonUtil.h" +#include "MainDialog.h" +#include "RenderScene.h" +#include "PaperSizeDlg.h" + +// ===== バージョン情報ダイアログ ===== +class CAboutDlg : public CDialog +{ +public: + CAboutDlg() : CDialog(IDD_ABOUTBOX) {} + enum { IDD = IDD_ABOUTBOX }; +protected: + void DoDataExchange(CDataExchange* pDX) override { CDialog::DoDataExchange(pDX); } + DECLARE_MESSAGE_MAP() +}; +BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) +END_MESSAGE_MAP() + +// 子キャンバスのウィンドウ ID(リソース上の IDC_CANVAS とは別) +static const UINT kCanvasId = 1101; + +static LPCTSTR kFilter = + _T("Otozai ラベル (*.otz)|*.otz|すべてのファイル (*.*)|*.*||"); + +BEGIN_MESSAGE_MAP(CMainDialog, CDialog) + ON_COMMAND(ID_FILE_NEW, &CMainDialog::OnFileNew) + ON_COMMAND(ID_FILE_OPEN, &CMainDialog::OnFileOpen) + ON_COMMAND(ID_FILE_SAVE, &CMainDialog::OnFileSave) + ON_COMMAND(ID_FILE_SAVE_AS, &CMainDialog::OnFileSaveAs) + ON_COMMAND(ID_INSERT_TEXT, &CMainDialog::OnInsertText) + ON_COMMAND(ID_INSERT_IMAGE, &CMainDialog::OnInsertImage) + ON_COMMAND(ID_INSERT_RECT, &CMainDialog::OnInsertRect) + ON_COMMAND(ID_INSERT_LINE, &CMainDialog::OnInsertLine) + ON_COMMAND(ID_EDIT_PROPERTIES, &CMainDialog::OnEditProperties) + ON_COMMAND(ID_EDIT_DELETE_ITEM, &CMainDialog::OnEditDeleteItem) + ON_COMMAND(ID_PAPER_SIZE, &CMainDialog::OnPaperSize) + ON_COMMAND(ID_VIEW_ZOOMIN, &CMainDialog::OnViewZoomIn) + ON_COMMAND(ID_VIEW_ZOOMOUT, &CMainDialog::OnViewZoomOut) + ON_COMMAND(ID_FILE_PRINT, &CMainDialog::OnFilePrint) + ON_COMMAND(ID_APP_ABOUT, &CMainDialog::OnAppAbout) + ON_COMMAND(ID_ITEM_COPY, &CMainDialog::OnItemCopy) + ON_COMMAND(ID_ITEM_PASTE, &CMainDialog::OnItemPaste) + ON_COMMAND(ID_ITEM_DUPLICATE, &CMainDialog::OnItemDuplicate) + ON_COMMAND(ID_ITEM_FRONT, &CMainDialog::OnItemFront) + ON_COMMAND(ID_ITEM_BACK, &CMainDialog::OnItemBack) + ON_COMMAND(ID_ALIGN_MENU, &CMainDialog::OnAlignMenu) + ON_COMMAND_RANGE(ID_ALIGN_LEFT, ID_ALIGN_BOTTOM, &CMainDialog::OnAlignCmd) + ON_BN_CLICKED(IDC_CHECK_GRID, &CMainDialog::OnToggleGrid) + ON_COMMAND(ID_TEMPLATE_LOAD, &CMainDialog::OnTemplateLoad) + ON_COMMAND(ID_TEMPLATE_CLEAR, &CMainDialog::OnTemplateClear) + ON_BN_CLICKED(IDC_CHECK_PRINT_TEMPLATE, &CMainDialog::OnTogglePrintTemplate) +END_MESSAGE_MAP() + +// コンストラクタ。メインダイアログのリソース ID を指定して生成。 +CMainDialog::CMainDialog(CWnd* pParent) + : CDialog(IDD_MAIN_DIALOG, pParent) +{ +} + +// コントロールと変数の関連付け(本ダイアログは直接の DDX 変数は持たない)。 +void CMainDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); +} + +// ダイアログ初期化。プレースホルダ位置に編集キャンバスを生成し、 +// チェックボックスの初期状態をモデルへ合わせる。 +BOOL CMainDialog::OnInitDialog() +{ + CDialog::OnInitDialog(); + + SetIcon(AfxGetApp()->LoadStandardIcon(IDI_APPLICATION), TRUE); + SetIcon(AfxGetApp()->LoadStandardIcon(IDI_APPLICATION), FALSE); + + // プレースホルダ(IDC_CANVAS)の矩形にキャンバスを生成 + CWnd* pHolder = GetDlgItem(IDC_CANVAS); + CRect rc; + if (pHolder) + { + pHolder->GetWindowRect(rc); + ScreenToClient(rc); + pHolder->ShowWindow(SW_HIDE); + } + else + { + rc = CRect(80, 10, 460, 300); + } + + m_canvas.SetLayout(&m_layout); + m_canvas.Create(this, rc, kCanvasId); + m_canvas.FitZoom(); + + // グリッド/スナップのチェック状態をキャンバスの既定に合わせる + CButton* pGrid = (CButton*)GetDlgItem(IDC_CHECK_GRID); + if (pGrid) pGrid->SetCheck(m_canvas.GetSnap() ? BST_CHECKED : BST_UNCHECKED); + + UpdateTemplateCheck(); + UpdateCaption(); + return TRUE; +} + +// タイトルバーを「ファイル名 - AveryDennisonUtil」に更新する(未保存は「無題」)。 +void CMainDialog::UpdateCaption() +{ + CString name = m_path.IsEmpty() ? CString(_T("無題")) : m_path; + if (!m_path.IsEmpty()) + { + int slash = m_path.ReverseFind(_T('\\')); + if (slash >= 0) name = m_path.Mid(slash + 1); + } + CString title; + title.Format(_T("%s - AveryDennisonUtil"), (LPCTSTR)name); + SetWindowText(title); +} + +// 未保存の変更がある場合に保存確認する。続行可なら TRUE、キャンセルなら FALSE。 +// 新規/開く/終了の前に呼んで作業内容の消失を防ぐ。 +BOOL CMainDialog::ConfirmDiscardIfDirty() +{ + if (!m_canvas.IsDirty()) + return TRUE; + int r = AfxMessageBox(_T("変更を保存しますか?"), MB_YESNOCANCEL | MB_ICONQUESTION); + if (r == IDCANCEL) return FALSE; + if (r == IDYES) return DoSave(FALSE); + return TRUE; +} + +// Enter キーでダイアログが閉じないように無効化(編集中の誤終了防止)。 +void CMainDialog::OnOK() +{ + // Enter キーでダイアログを閉じない(編集を継続) +} + +// 終了(×ボタン/Esc/「終了」)。未保存なら確認してから閉じる。 +void CMainDialog::OnCancel() +{ + if (!ConfirmDiscardIfDirty()) + return; + CDialog::OnCancel(); +} + +// ===== ファイル ===== + +// 「新規」。未保存確認の後、レイアウトを初期化して表示を更新する。 +void CMainDialog::OnFileNew() +{ + if (!ConfirmDiscardIfDirty()) return; + m_layout.Reset(); + m_path.Empty(); + m_canvas.OnLayoutReplaced(); + m_canvas.FitZoom(); + UpdateTemplateCheck(); + UpdateCaption(); +} + +// 「開く」。.otz を選んで読み込み、表示・チェック状態・タイトルを更新する。 +void CMainDialog::OnFileOpen() +{ + if (!ConfirmDiscardIfDirty()) return; + CFileDialog dlg(TRUE, _T("otz"), nullptr, + OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, kFilter, this); + if (dlg.DoModal() != IDOK) return; + + if (!m_layout.LoadFromFile(dlg.GetPathName())) + { + AfxMessageBox(_T("ファイルを開けませんでした。")); + m_layout.Reset(); + } + m_path = dlg.GetPathName(); + m_canvas.OnLayoutReplaced(); + m_canvas.FitZoom(); + UpdateTemplateCheck(); + UpdateCaption(); +} + +void CMainDialog::OnFileSave() { DoSave(FALSE); } // 「保存」(既存パスへ上書き) +void CMainDialog::OnFileSaveAs(){ DoSave(TRUE); } // 「名前を付けて保存」 + +// 保存の実処理。bForceDialog=TRUE またはパス未設定のときは保存ダイアログを出す。 +// 成功で TRUE。保存後はダーティ状態を解除しタイトルを更新する。 +BOOL CMainDialog::DoSave(BOOL bForceDialog) +{ + CString path = m_path; + if (bForceDialog || path.IsEmpty()) + { + CFileDialog dlg(FALSE, _T("otz"), m_path.IsEmpty() ? _T("label.otz") : (LPCTSTR)m_path, + OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY, kFilter, this); + if (dlg.DoModal() != IDOK) return FALSE; + path = dlg.GetPathName(); + } + if (!m_layout.SaveToFile(path)) + { + AfxMessageBox(_T("保存に失敗しました。")); + return FALSE; + } + m_path = path; + m_canvas.SetDirty(FALSE); + UpdateCaption(); + return TRUE; +} + +// ===== 挿入・編集(キャンバスへ委譲) ===== + +void CMainDialog::OnInsertText() { m_canvas.InsertText(); } +void CMainDialog::OnInsertImage() { m_canvas.InsertImage(); } +void CMainDialog::OnInsertRect() { m_canvas.InsertRect(); } +void CMainDialog::OnInsertLine() { m_canvas.InsertLine(); } +void CMainDialog::OnEditProperties() { m_canvas.EditSelectedProperties(); } +void CMainDialog::OnEditDeleteItem() { m_canvas.DeleteSelected(); } +void CMainDialog::OnViewZoomIn() { m_canvas.ZoomIn(); } +void CMainDialog::OnViewZoomOut() { m_canvas.ZoomOut(); } + +// 「用紙サイズ」。現在値をダイアログに渡し、OK なら用紙寸法・DPI・余白を反映する。 +void CMainDialog::OnPaperSize() +{ + CPaperSizeDlg dlg(this); + dlg.m_widthMm = m_layout.m_paperWidthHu / 100.0; + dlg.m_heightMm = m_layout.m_paperHeightHu / 100.0; + dlg.m_dpi = m_layout.m_dpi; + dlg.m_marginMm = m_layout.m_marginHu / 100.0; + if (dlg.DoModal() == IDOK) + { + m_layout.m_paperWidthHu = (int)(dlg.m_widthMm * 100 + 0.5); + m_layout.m_paperHeightHu = (int)(dlg.m_heightMm * 100 + 0.5); + m_layout.m_dpi = dlg.m_dpi; + m_layout.m_marginHu = (int)(dlg.m_marginMm * 100 + 0.5); + m_layout.SyncTemplateRect(); + m_canvas.SetDirty(TRUE); + m_canvas.FitZoom(); + m_canvas.Invalidate(); + } +} + +// ===== 印刷(GDI 直接印刷) ===== + +// 「印刷」。プリンタ選択ダイアログ→プリンタ DC を取得し、画面と同じ +// Render::DrawLayout で 1 ページ印刷する(StartDoc/StartPage/EndPage/EndDoc)。 +void CMainDialog::OnFilePrint() +{ + CPrintDialog pd(FALSE, + PD_RETURNDC | PD_USEDEVMODECOPIES | PD_NOPAGENUMS | PD_NOSELECTION | PD_ALLPAGES, + this); + if (pd.DoModal() != IDOK) + return; + + HDC hdc = pd.GetPrinterDC(); + if (!hdc) + { + AfxMessageBox(_T("プリンタ DC を取得できませんでした。")); + return; + } + + CDC dc; + dc.Attach(hdc); + + DOCINFO di; + ZeroMemory(&di, sizeof(di)); + di.cbSize = sizeof(di); + di.lpszDocName = _T("AveryDennisonUtil"); + + if (dc.StartDoc(&di) > 0) + { + if (dc.StartPage() > 0) + { + int dpiX = dc.GetDeviceCaps(LOGPIXELSX); + int dpiY = dc.GetDeviceCaps(LOGPIXELSY); + // プリンタ DC の原点(0,0)は「印字可能領域」の左上。ラベル mm(0,0) を + // そこに合わせる(印字可能領域の考慮)。物理マージン分を引くと逆に + // 非印字領域へはみ出して切れるため、オフセットは加えない。 + CoordMap map(dpiX, dpiY, 1.0, 0, 0); // 実 DPI・原点(0,0)・等倍 + Render::DrawLayout(dc, m_layout, map, /*screen=*/false, nullptr); + dc.EndPage(); + } + dc.EndDoc(); + } + + dc.Detach(); + ::DeleteDC(hdc); +} + +// 「バージョン情報」ダイアログを表示する。 +void CMainDialog::OnAppAbout() +{ + CAboutDlg dlg; + dlg.DoModal(); +} + +// ===== 編集操作(キャンバスへ委譲) ===== + +void CMainDialog::OnItemCopy() { m_canvas.CopySelected(); } +void CMainDialog::OnItemPaste() { m_canvas.Paste(); } +void CMainDialog::OnItemDuplicate() { m_canvas.DuplicateSelected(); } +void CMainDialog::OnItemFront() { m_canvas.BringSelectedToFront(); } +void CMainDialog::OnItemBack() { m_canvas.SendSelectedToBack(); } + +// 「整列...」ボタン。左/中央/右・上/中/下のポップアップメニューを出す。 +// 選択された項目は ON_COMMAND_RANGE 経由で OnAlignCmd に届く。 +void CMainDialog::OnAlignMenu() +{ + if (!m_canvas.HasSelection()) + { + AfxMessageBox(_T("整列するアイテムを選択してください。")); + return; + } + CMenu menu; + menu.CreatePopupMenu(); + menu.AppendMenu(MF_STRING, ID_ALIGN_LEFT, _T("左揃え")); + menu.AppendMenu(MF_STRING, ID_ALIGN_HCENTER, _T("左右中央")); + menu.AppendMenu(MF_STRING, ID_ALIGN_RIGHT, _T("右揃え")); + menu.AppendMenu(MF_SEPARATOR, 0, (LPCTSTR)nullptr); + menu.AppendMenu(MF_STRING, ID_ALIGN_TOP, _T("上揃え")); + menu.AppendMenu(MF_STRING, ID_ALIGN_VCENTER, _T("上下中央")); + menu.AppendMenu(MF_STRING, ID_ALIGN_BOTTOM, _T("下揃え")); + + CRect rc; + GetDlgItem(ID_ALIGN_MENU)->GetWindowRect(&rc); + menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON, + rc.right, rc.top, this); +} + +// 整列メニュー項目のハンドラ。ID の連番を how(0..5)に変換して整列を実行。 +void CMainDialog::OnAlignCmd(UINT nID) +{ + m_canvas.AlignSelected((int)(nID - ID_ALIGN_LEFT)); +} + +// 「グリッド/スナップ」チェックの切替をキャンバスへ反映する。 +void CMainDialog::OnToggleGrid() +{ + CButton* p = (CButton*)GetDlgItem(IDC_CHECK_GRID); + if (p) m_canvas.SetSnap(p->GetCheck() == BST_CHECKED); +} + +// ===== 原紙(背景下絵) ===== + +// 「原紙を印刷」チェックの表示をモデル(m_printTemplate)に合わせる。 +void CMainDialog::UpdateTemplateCheck() +{ + CButton* p = (CButton*)GetDlgItem(IDC_CHECK_PRINT_TEMPLATE); + if (p) p->SetCheck(m_layout.m_printTemplate ? BST_CHECKED : BST_UNCHECKED); +} + +// 「原紙...」ボタン。画像を読み込み、続いて用紙サイズを原紙に合わせるか +// (埋め込みDPI / 用紙DPI / 変更しない)をダイアログで選ばせる。 +void CMainDialog::OnTemplateLoad() +{ + CFileDialog dlg(TRUE, nullptr, nullptr, + OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, + _T("画像ファイル (*.png;*.jpg;*.jpeg;*.bmp;*.gif)|*.png;*.jpg;*.jpeg;*.bmp;*.gif|すべて (*.*)|*.*||"), + this); + if (dlg.DoModal() != IDOK) return; + if (!m_layout.LoadTemplate(dlg.GetPathName())) + { + AfxMessageBox(_T("原紙を読み込めませんでした。")); + return; + } + + // 用紙サイズを原紙ファイルのサイズに合わせる(px→mm 換算の基準を選択) + int pw = 0, ph = 0; + if (m_layout.GetTemplatePixelSize(pw, ph) && pw > 0 && ph > 0) + { + int pdpi = m_layout.m_dpi; + double papW = pw * 25.4 / pdpi, papH = ph * 25.4 / pdpi; + + double edx = 0, edy = 0; + BOOL hasEmbed = m_layout.GetTemplateDpi(edx, edy); + + CString msg; + int r; + if (hasEmbed) + { + double embW = pw * 25.4 / edx, embH = ph * 25.4 / edy; + msg.Format( + _T("用紙サイズを原紙に合わせます。\n\n画素: %d × %d px\n\n") + _T("[はい] 画像の埋め込みDPI %.0f dpi → %.1f × %.1f mm\n") + _T("[いいえ] 用紙のDPI %d dpi → %.1f × %.1f mm\n") + _T("[キャンセル] 用紙サイズは変更しない"), + pw, ph, edx, embW, embH, pdpi, papW, papH); + r = AfxMessageBox(msg, MB_YESNOCANCEL | MB_ICONQUESTION); + if (r == IDYES) m_layout.SetPaperSizeFromTemplate(TRUE); + else if (r == IDNO) m_layout.SetPaperSizeFromTemplate(FALSE); + // IDCANCEL: 用紙サイズは変更しない(原紙は現用紙に伸縮表示) + } + else + { + msg.Format( + _T("用紙サイズを原紙に合わせます。\n(画像に埋め込みDPI情報がありません)\n\n") + _T("画素: %d × %d px\n\n") + _T("[はい] 用紙のDPI %d dpi → %.1f × %.1f mm で合わせる\n") + _T("[いいえ] 用紙サイズは変更しない"), + pw, ph, pdpi, papW, papH); + r = AfxMessageBox(msg, MB_YESNO | MB_ICONQUESTION); + if (r == IDYES) m_layout.SetPaperSizeFromTemplate(FALSE); + } + } + + m_canvas.SetDirty(TRUE); + m_canvas.FitZoom(); + m_canvas.Invalidate(); +} + +// 「消去」ボタン。読み込んだ原紙を破棄して再描画する。 +void CMainDialog::OnTemplateClear() +{ + if (!m_layout.HasTemplate()) return; + m_layout.ClearTemplate(); + m_canvas.SetDirty(TRUE); + m_canvas.Invalidate(); +} + +// 「原紙を印刷」チェックの切替をモデル(m_printTemplate)へ反映する。 +void CMainDialog::OnTogglePrintTemplate() +{ + CButton* p = (CButton*)GetDlgItem(IDC_CHECK_PRINT_TEMPLATE); + if (p) + { + m_layout.m_printTemplate = (p->GetCheck() == BST_CHECKED); + m_canvas.SetDirty(TRUE); + } +} diff --git a/MainDialog.h b/MainDialog.h new file mode 100644 index 0000000..a7e1ce3 --- /dev/null +++ b/MainDialog.h @@ -0,0 +1,57 @@ +// MainDialog.h : メイン ダイアログ(ダイアログベース アプリの主ウィンドウ) +#pragma once + +#include "resource.h" +#include "LabelLayout.h" +#include "Canvas.h" + +class CMainDialog : public CDialog +{ +public: + CMainDialog(CWnd* pParent = nullptr); + enum { IDD = IDD_MAIN_DIALOG }; + +protected: + CLabelLayout m_layout; // データモデル + CCanvas m_canvas; // 編集キャンバス(子ウィンドウ) + CString m_path; // 現在の .otz パス(空 = 無題) + + void DoDataExchange(CDataExchange* pDX) override; + BOOL OnInitDialog() override; + void OnOK() override; // Enter での誤終了を防ぐ(何もしない) + void OnCancel() override; // 終了(保存確認) + + void UpdateCaption(); + void UpdateTemplateCheck(); // 「原紙を印刷」チェックをモデルに合わせる + BOOL ConfirmDiscardIfDirty(); + BOOL DoSave(BOOL bForceDialog); + + afx_msg void OnFileNew(); + afx_msg void OnFileOpen(); + afx_msg void OnFileSave(); + afx_msg void OnFileSaveAs(); + afx_msg void OnInsertText(); + afx_msg void OnInsertImage(); + afx_msg void OnInsertRect(); + afx_msg void OnInsertLine(); + afx_msg void OnEditProperties(); + afx_msg void OnEditDeleteItem(); + afx_msg void OnPaperSize(); + afx_msg void OnViewZoomIn(); + afx_msg void OnViewZoomOut(); + afx_msg void OnFilePrint(); + afx_msg void OnAppAbout(); + afx_msg void OnItemCopy(); + afx_msg void OnItemPaste(); + afx_msg void OnItemDuplicate(); + afx_msg void OnItemFront(); + afx_msg void OnItemBack(); + afx_msg void OnAlignMenu(); + afx_msg void OnAlignCmd(UINT nID); + afx_msg void OnToggleGrid(); + afx_msg void OnTemplateLoad(); + afx_msg void OnTemplateClear(); + afx_msg void OnTogglePrintTemplate(); + + DECLARE_MESSAGE_MAP() +}; diff --git a/PaperSizeDlg.cpp b/PaperSizeDlg.cpp new file mode 100644 index 0000000..2540d10 --- /dev/null +++ b/PaperSizeDlg.cpp @@ -0,0 +1,76 @@ +// PaperSizeDlg.cpp +#include "pch.h" +#include "AveryDennisonUtil.h" +#include "PaperSizeDlg.h" + +IMPLEMENT_DYNAMIC(CPaperSizeDlg, CDialog) + +// ロールラベルのよく使う寸法プリセット(幅mm, 高さmm, dpi) +struct Preset { LPCTSTR name; double w; double h; int dpi; }; +static const Preset kPresets[] = { + { _T("ロールラベル 40×30mm (203dpi)"), 40, 30, 203 }, + { _T("ロールラベル 50×30mm (203dpi)"), 50, 30, 203 }, + { _T("ロールラベル 32×25mm (203dpi)"), 32, 25, 203 }, + { _T("ロールラベル 60×40mm (203dpi)"), 60, 40, 203 }, + { _T("レシート 58mm幅 (203dpi)"), 58, 40, 203 }, + { _T("カスタム"), 0, 0, 0 }, +}; +static const int kPresetCount = sizeof(kPresets) / sizeof(kPresets[0]); + +// コンストラクタ。各値を既定(40×30mm, 203dpi, 余白0)で初期化。 +CPaperSizeDlg::CPaperSizeDlg(CWnd* pParent) + : CDialog(IDD_PAPER_SIZE, pParent) + , m_widthMm(40.0) + , m_heightMm(30.0) + , m_dpi(203) + , m_marginMm(0.0) +{ +} + +// 入力欄と各メンバ変数の関連付け+範囲検証(幅/高さ/DPI/余白)。 +void CPaperSizeDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + DDX_Text(pDX, IDC_EDIT_WIDTH, m_widthMm); + DDV_MinMaxDouble(pDX, m_widthMm, 1.0, 2000.0); + DDX_Text(pDX, IDC_EDIT_HEIGHT, m_heightMm); + DDV_MinMaxDouble(pDX, m_heightMm, 1.0, 2000.0); + DDX_Text(pDX, IDC_EDIT_DPI, m_dpi); + DDV_MinMaxInt(pDX, m_dpi, 50, 1200); + DDX_Text(pDX, IDC_EDIT_MARGIN, m_marginMm); + DDV_MinMaxDouble(pDX, m_marginMm, 0.0, 100.0); +} + +// 初期化。プリセット コンボにロールラベル等の候補を流し込み、既定で「カスタム」を選ぶ。 +BOOL CPaperSizeDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + CComboBox* pc = (CComboBox*)GetDlgItem(IDC_COMBO_PRESET); + if (pc) + { + for (int i = 0; i < kPresetCount; ++i) + pc->AddString(kPresets[i].name); + pc->SetCurSel(kPresetCount - 1); // 既定はカスタム + } + return TRUE; +} + +// プリセット選択変更時。選んだ寸法/DPI を各入力欄へ反映する(カスタムは現状維持)。 +void CPaperSizeDlg::OnPresetChanged() +{ + CComboBox* pc = (CComboBox*)GetDlgItem(IDC_COMBO_PRESET); + if (!pc) return; + int sel = pc->GetCurSel(); + if (sel < 0 || sel >= kPresetCount) return; + const Preset& ps = kPresets[sel]; + if (ps.w <= 0) return; // カスタムは現状値を維持 + + m_widthMm = ps.w; + m_heightMm = ps.h; + m_dpi = ps.dpi; + UpdateData(FALSE); +} + +BEGIN_MESSAGE_MAP(CPaperSizeDlg, CDialog) + ON_CBN_SELCHANGE(IDC_COMBO_PRESET, &CPaperSizeDlg::OnPresetChanged) +END_MESSAGE_MAP() diff --git a/PaperSizeDlg.h b/PaperSizeDlg.h new file mode 100644 index 0000000..36fbbfc --- /dev/null +++ b/PaperSizeDlg.h @@ -0,0 +1,22 @@ +// PaperSizeDlg.h : 全印刷領域(用紙サイズ)設定ダイアログ +#pragma once +#include "resource.h" + +class CPaperSizeDlg : public CDialog +{ + DECLARE_DYNAMIC(CPaperSizeDlg) +public: + CPaperSizeDlg(CWnd* pParent = nullptr); + enum { IDD = IDD_PAPER_SIZE }; + + double m_widthMm; + double m_heightMm; + int m_dpi; + double m_marginMm; + +protected: + void DoDataExchange(CDataExchange* pDX) override; + BOOL OnInitDialog() override; + afx_msg void OnPresetChanged(); + DECLARE_MESSAGE_MAP() +}; diff --git a/PrintItem.cpp b/PrintItem.cpp new file mode 100644 index 0000000..1545d39 --- /dev/null +++ b/PrintItem.cpp @@ -0,0 +1,109 @@ +// PrintItem.cpp +#include "pch.h" +#include "PrintItem.h" + +IMPLEMENT_DYNAMIC(CPrintItem, CObject) + +// 選択ハンドルの一辺(デバイスピクセル) +static const int kHandleSize = 7; + +// コンストラクタ。矩形を空(0,0,0,0)で初期化。 +CPrintItem::CPrintItem() + : m_rect(0, 0, 0, 0) +{ +} + +CPrintItem::~CPrintItem() +{ +} + +// 当たり判定。指定座標(1/100mm)がこのアイテムの矩形内なら TRUE。 +// 既定は矩形の内側判定。直線など特殊な形状は派生側で上書きできる。 +BOOL CPrintItem::HitTest(const CPoint& huPt) const +{ + return m_rect.PtInRect(huPt); +} + +// 共通の永続化処理。矩形(left/top/right/bottom)だけを保存/復元する。 +// 派生クラスは自分の Serialize で本関数を呼んでから固有データを読み書きする。 +void CPrintItem::Serialize(CArchive& ar) +{ + CObject::Serialize(ar); + if (ar.IsStoring()) + { + ar << (int)m_rect.left << (int)m_rect.top + << (int)m_rect.right << (int)m_rect.bottom; + } + else + { + int l, t, r, b; + ar >> l >> t >> r >> b; + m_rect.SetRect(l, t, r, b); + } +} + +// 8 個のハンドル(四隅 + 各辺中央)の中心座標 +static CPoint HandleCenter(const CRect& dr, int i) +{ + int mx = (dr.left + dr.right) / 2; + int my = (dr.top + dr.bottom) / 2; + switch (i) + { + case 0: return CPoint(dr.left, dr.top); // 左上 + case 1: return CPoint(mx, dr.top); // 上中央 + case 2: return CPoint(dr.right, dr.top); // 右上 + case 3: return CPoint(dr.right, my); // 右中央 + case 4: return CPoint(dr.right, dr.bottom); // 右下 + case 5: return CPoint(mx, dr.bottom); // 下中央 + case 6: return CPoint(dr.left, dr.bottom); // 左下 + case 7: return CPoint(dr.left, my); // 左中央 + } + return CPoint(0, 0); +} + +// index 番目(0..7)のハンドルが占める小矩形(デバイス座標)を返す。 +CRect CPrintItem::HandleRectAt(const CRect& devRect, int index) +{ + CPoint c = HandleCenter(devRect, index); + int h = kHandleSize / 2; + return CRect(c.x - h, c.y - h, c.x + h + 1, c.y + h + 1); +} + +// 選択中アイテムの装飾(破線枠 + 8個のサイズ変更ハンドル)を描画する。 +// devRect はアイテム矩形をデバイス座標へ変換したもの。 +void CPrintItem::DrawSelection(CDC& dc, const CRect& devRect) +{ + // 破線の選択枠 + CPen pen(PS_DOT, 1, RGB(0, 0, 255)); + CPen* pOld = dc.SelectObject(&pen); + int oldBk = dc.SetBkMode(TRANSPARENT); + CBrush* pOldBrush = (CBrush*)dc.SelectStockObject(NULL_BRUSH); + dc.Rectangle(devRect); + dc.SelectObject(pOldBrush); + dc.SetBkMode(oldBk); + dc.SelectObject(pOld); + + // ハンドル(白塗り+黒枠) + CBrush white(RGB(255, 255, 255)); + CPen black(PS_SOLID, 1, RGB(0, 0, 0)); + CPen* pOld2 = dc.SelectObject(&black); + CBrush* pOld3 = dc.SelectObject(&white); + for (int i = 0; i < 8; ++i) + dc.Rectangle(HandleRectAt(devRect, i)); + dc.SelectObject(pOld3); + dc.SelectObject(pOld2); +} + +// devPt(デバイス座標)がどのハンドル上にあるかを返す。0..7、無ければ -1。 +// 判定矩形を少し広げて掴みやすくしている。 +int CPrintItem::HitHandle(const CRect& devRect, const CPoint& devPt) +{ + for (int i = 0; i < 8; ++i) + { + CRect hr = HandleRectAt(devRect, i); + hr.InflateRect(2, 2); // 掴みやすく + if (hr.PtInRect(devPt)) + return i; + } + return -1; +} diff --git a/PrintItem.h b/PrintItem.h new file mode 100644 index 0000000..0cdebe2 --- /dev/null +++ b/PrintItem.h @@ -0,0 +1,49 @@ +// PrintItem.h : 印刷アイテムの抽象基底クラス +// +// 各印刷領域(ゾーン)を表す。位置・サイズは 1/100mm の CRect で保持。 +// 描画は Draw() ただ1つに集約し、画面エディタと印刷で共有する。 +#pragma once + +#include "CoordMap.h" + +enum class ItemType { Text, Rect, Line, Image }; + +class CPrintItem : public CObject +{ + DECLARE_DYNAMIC(CPrintItem) // 抽象基底: SERIAL ではなく DYNAMIC(具象クラスのみ SERIAL) +public: + CPrintItem(); + virtual ~CPrintItem(); + + // CObject のコピーコンストラクタはアクセス不可のため、明示的に提供する + // (派生クラスの暗黙コピーコンストラクタ/Clone() が利用する) + CPrintItem(const CPrintItem& src) : CObject(), m_rect(src.m_rect) {} + CPrintItem& operator=(const CPrintItem& src) { m_rect = src.m_rect; return *this; } + + // --- 形状(1/100mm) --- + CRect m_rect; // 正規化済み(left<=right, top<=bottom)を前提 + void SetRect(const CRect& r) { m_rect = r; m_rect.NormalizeRect(); } + const CRect& Rect() const { return m_rect; } + + // --- 種別 --- + virtual ItemType Type() const = 0; + + // --- 描画(画面・印刷で共有) --- + // selected が true のとき、画面エディタでは選択枠/ハンドルを描く(印刷時は false)。 + virtual void Draw(CDC& dc, const CoordMap& map, bool selected) = 0; + + // --- 当たり判定(1/100mm 座標) --- + virtual BOOL HitTest(const CPoint& huPt) const; + + // --- 複製 --- + virtual CPrintItem* Clone() const = 0; + + // --- 永続化 --- + virtual void Serialize(CArchive& ar) override; + + // 選択ハンドルの描画(共通)。ハンドルは 8 個(四隅+各辺中央)。 + static void DrawSelection(CDC& dc, const CRect& devRect); + // ハンドル位置(デバイス座標)を取得。index 0..7。見つからなければ false。 + static int HitHandle(const CRect& devRect, const CPoint& devPt); + static CRect HandleRectAt(const CRect& devRect, int index); +}; diff --git a/README.md b/README.md new file mode 100644 index 0000000..f556d77 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# AveryDennisonUtil + +ラベル/レシートの**レイアウトを画面上で設計し、Windows プリンターへ印刷**する MFC (VC++) デスクトップアプリ。 +高価な中華製ラベルソフトの置き換えを目的とする。**ダイアログベース**アプリ(左側にボタン列、右側に編集キャンバス)。 + +## できること +- 全印刷領域(用紙/ラベルサイズ)の確定(`用紙サイズ` ボタン。ロールラベルのプリセット + 余白指定) +- 複数の印刷領域(ゾーン)の配置・サイズ変更(ドラッグ移動 / ハンドルでリサイズ) +- 各領域に印刷するデータの入力 + - テキスト(フォント・サイズ・揃え・太字) + - 矩形枠 / 直線(区切り) + - 画像(PNG/JPG/BMP、`.otz` に埋め込み) +- 編集操作 + - コピー / 貼付け / 複製(`Ctrl+C` / `Ctrl+V` / `Ctrl+D`) + - 重なり順(`前面` / `背面` ボタン、`Ctrl+]` / `Ctrl+[` で前後へ) + - 整列(`整列...` ボタン → 用紙/余白を基準に 左右中央/上下中央など) + - グリッド/スナップ(`グリッド/スナップ` チェック、1mm グリッド。矢印キーで微調整、`Shift` で大きく) +- 原紙(位置合わせ用の背景下絵) + - `原紙...` ボタンで画像を読み込み、用紙全体に敷く(画面では薄く表示し、上に載せるアイテムの位置合わせを容易にする) + - 読み込み時に **用紙サイズを原紙ファイルのサイズに合わせる**。px→mm 換算の基準を選択可能: + - 画像の埋め込みDPI(ファイル本来の実寸)/ 用紙のDPI設定 / 変更しない + - `原紙を印刷` チェックで、原紙自身を印刷する/しないを選択(既定 = 印刷しない=既存の印刷済みラベルに重ねる用途) + - `消去` で原紙を除去。原紙画像は `.otz` に埋め込み保存 +- Windows プリンターへ印刷(GDI、`CPrintDialog` 経由の直接印刷。ラベル mm(0,0) を印字可能領域の左上に合わせて出力) +- レイアウトの保存・再読込(`*.otz`。版数つきヘッダで前方/後方互換に配慮) + +## アーキテクチャ(要点) +- すべてのジオメトリは **1/100 mm の整数**で保持(ピクセルで持たない)。 +- 描画は各アイテムの `Draw(CDC&, const CoordMap&)` ただ 1 つに集約し、 + **画面エディタと印刷で同一コードを共有**。違いは [`CoordMap`](CoordMap.h) が持つ DPI とズームだけ。 +- これにより画面(≒96dpi)とサーマルラベル(≒203dpi)で同一レイアウトが + 物理的に正しいサイズで出力される。 + +| 役割 | クラス | +|------|--------| +| アプリ(ダイアログ起動のみ) | `CAveryDennisonUtilApp` ([AveryDennisonUtil.cpp](AveryDennisonUtil.cpp)) | +| メイン ダイアログ(ボタン・ファイル・印刷) | `CMainDialog : CDialog` ([MainDialog.cpp](MainDialog.cpp)) | +| 編集キャンバス(子ウィンドウ・マウス操作) | `CCanvas : CWnd` ([Canvas.cpp](Canvas.cpp)) | +| データモデル(用紙+アイテム列・.otz 保存) | `CLabelLayout` ([LabelLayout.cpp](LabelLayout.cpp)) | +| 共有描画(画面・印刷) | `Render::DrawLayout` ([RenderScene.cpp](RenderScene.cpp)) | +| 単位変換 | `CoordMap` ([CoordMap.h](CoordMap.h)) | +| アイテム | `CPrintItem` 派生:`CTextItem` / `CShapeItem` / `CImageItem` | + +## ビルド / デバッグ +- VS2022 (v143) / x64 / **MFC・CRT は静的リンク**。単一 exe で外部 DLL に依存しないため、 + **Visual Studio 2022 で `AveryDennisonUtil.sln` を開き、構成 Debug(または Release)/x64 を選んで + F5(デバッグ実行)するだけで動く**(Debug 用 DLL の配置は不要)。 +- コマンドラインからビルドする場合: + +``` +& "C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe" ` + AveryDennisonUtil.sln /p:Configuration=Debug /p:Platform=x64 +``` + +- 出力: `bin\Debug\AveryDennisonUtil.exe`(Release は `bin\Release\`)。 +- 補足: このマシンには vcpkg のグローバル統合が入っているが、本プロジェクトは未使用のため + `.vcxproj` で `VcpkgEnabled=false` にして無効化済み(不要な include 注入と pwsh デプロイ手順を回避)。 + +## 使い方 +1. `用紙サイズ` ボタンでラベル寸法を設定(ロールラベルのプリセットあり。`余白` を入れると破線ガイドが出て整列の基準になる)。 +2. `テキスト`/`画像`/`矩形枠`/`直線` ボタンでアイテムを追加。 +3. キャンバス上でドラッグ移動、ハンドルでリサイズ。ダブルクリック(または `プロパティ` ボタン)で編集。`Del` キー / `削除` ボタンで削除。 +4. コピー/複製(`コピー`+`貼付` または `複製` / `Ctrl+C,V,D`)、重なり順(`前面`/`背面`)、`整列...` で配置を整える。`グリッド/スナップ` を入れると 1mm グリッドへ吸着し、矢印キーで微調整できる(`Shift` で大きく移動)。 +5. 既存ラベル等に合わせたい場合は `原紙...` で下絵を読み込む。読込時に用紙を原紙サイズへ合わせるか(埋め込みDPI / 用紙DPI)を選択。薄く表示された原紙に合わせてアイテムを配置し、原紙そのものも印刷したいときは `原紙を印刷` をチェック。 +6. `印刷` ボタン → プリンタを選んで印刷。 +7. `保存` / `名前を付けて保存` で `.otz` 保存、`開く` で再読込。 + +## 検証のヒント +実機が無い場合、`Microsoft Print to PDF` へ印刷すると 203dpi 相当の解像度差を含めて +配置・サイズが mm 通りかを PDF 上で確認できる。 diff --git a/RenderScene.cpp b/RenderScene.cpp new file mode 100644 index 0000000..f285338 --- /dev/null +++ b/RenderScene.cpp @@ -0,0 +1,101 @@ +// RenderScene.cpp +#include "pch.h" +#include "RenderScene.h" +#include "LabelLayout.h" +#include "PrintItem.h" +#include "ImageItem.h" + +#pragma comment(lib, "msimg32.lib") // CDC::AlphaBlend(原紙の薄表示) + +// レイアウト1枚分を dc に描画する。画面エディタと印刷の共通描画パス。 +// 描画順は 用紙白地(画面のみ)→ 原紙 → グリッド/余白ガイド(画面のみ)→ 各アイテム。 +// screen=true : 画面用。用紙の白地・グリッド・余白ガイド・選択枠を描く。原紙は薄く表示。 +// screen=false: 印刷用。原紙は m_printTemplate のときだけ・等倍・薄表示なしで描く。 +// map がDPI/ズーム/原点を吸収するので、同じコードで解像度差を正しく出力できる。 +void Render::DrawLayout(CDC& dc, const CLabelLayout& layout, const CoordMap& map, + bool screen, const CPrintItem* selected, int gridHu) +{ + CRect paper(0, 0, layout.m_paperWidthHu, layout.m_paperHeightHu); + CRect paperDev = map.MmToDev(paper); + + if (screen) + { + CBrush white(RGB(255, 255, 255)); + dc.FillRect(paperDev, &white); + } + + // 原紙(背景下絵): 全アイテムの背面。画面では常に・印刷は許可時のみ。 + CImageItem* tpl = layout.Template(); + if (tpl && (screen || layout.m_printTemplate)) + { + tpl->SetRect(paper); // 常に用紙全体へ + tpl->Draw(dc, map, /*selected=*/false); + + if (screen && paperDev.Width() > 0 && paperDev.Height() > 0) + { + // 画面では原紙を薄く被せ、上に載るアイテムを見やすくする + CDC tmp; + tmp.CreateCompatibleDC(&dc); + CBitmap wb; + wb.CreateCompatibleBitmap(&dc, 1, 1); + CBitmap* pOldB = tmp.SelectObject(&wb); + tmp.SetPixelV(0, 0, RGB(255, 255, 255)); + BLENDFUNCTION bf = { AC_SRC_OVER, 0, 110, 0 }; // 白を約43%重ねる + dc.AlphaBlend(paperDev.left, paperDev.top, paperDev.Width(), paperDev.Height(), + &tmp, 0, 0, 1, 1, bf); + tmp.SelectObject(pOldB); + } + } + + if (screen) + { + + // グリッド(アイテムの下・用紙内)。デバイス間隔が細かすぎる時は描かない。 + if (gridHu > 0 && map.LenToDevX(gridHu) >= 5) + { + CPen grid(PS_SOLID, 1, RGB(225, 225, 225)); + CPen* pOldG = dc.SelectObject(&grid); + for (int x = gridHu; x < layout.m_paperWidthHu; x += gridHu) + { + int dx = map.MmToDevX(x); + dc.MoveTo(dx, paperDev.top); + dc.LineTo(dx, paperDev.bottom); + } + for (int y = gridHu; y < layout.m_paperHeightHu; y += gridHu) + { + int dy = map.MmToDevY(y); + dc.MoveTo(paperDev.left, dy); + dc.LineTo(paperDev.right, dy); + } + dc.SelectObject(pOldG); + } + + // 余白ガイド(破線) + if (layout.m_marginHu > 0) + { + CRect mdev = map.MmToDev(layout.ContentRect()); + int oldBk = dc.SetBkMode(TRANSPARENT); + CPen mpen(PS_DOT, 1, RGB(150, 150, 200)); + CPen* pOldM = dc.SelectObject(&mpen); + CBrush* pOldMB = (CBrush*)dc.SelectStockObject(NULL_BRUSH); + dc.Rectangle(mdev); + dc.SelectObject(pOldMB); + dc.SelectObject(pOldM); + dc.SetBkMode(oldBk); + } + + CPen edge(PS_SOLID, 1, RGB(120, 120, 120)); + CPen* pOld = dc.SelectObject(&edge); + CBrush* pOldBrush = (CBrush*)dc.SelectStockObject(NULL_BRUSH); + dc.Rectangle(paperDev); + dc.SelectObject(pOldBrush); + dc.SelectObject(pOld); + } + + for (int i = 0; i < layout.GetItemCount(); ++i) + { + CPrintItem* p = layout.GetItem(i); + bool sel = screen && (p == selected); + p->Draw(dc, map, sel); + } +} diff --git a/RenderScene.h b/RenderScene.h new file mode 100644 index 0000000..0e1d522 --- /dev/null +++ b/RenderScene.h @@ -0,0 +1,17 @@ +// RenderScene.h : レイアウト1枚分の描画(画面キャンバスと印刷で共有) +#pragma once + +#include "CoordMap.h" + +class CLabelLayout; +class CPrintItem; + +namespace Render +{ + // layout を map に従って dc へ描画する。 + // screen : true=画面(用紙の白地・枠・選択ハンドルを描く) / false=印刷 + // selected : 画面で選択枠を描くアイテム(印刷時は無視) + // gridHu : >0 かつ screen のとき、その間隔(1/100mm)でグリッドを描く(0=描かない) + void DrawLayout(CDC& dc, const CLabelLayout& layout, const CoordMap& map, + bool screen, const CPrintItem* selected, int gridHu = 0); +} diff --git a/ShapeItem.cpp b/ShapeItem.cpp new file mode 100644 index 0000000..062ab07 --- /dev/null +++ b/ShapeItem.cpp @@ -0,0 +1,60 @@ +// ShapeItem.cpp +#include "pch.h" +#include "ShapeItem.h" + +IMPLEMENT_SERIAL(CShapeItem, CPrintItem, 1) + +// コンストラクタ。既定は矩形枠・線幅0.3mm。 +CShapeItem::CShapeItem() + : m_shape(0) + , m_lineWidthHu(30) // 0.3mm +{ +} + +// 図形(矩形枠 or 直線)を描画する。線幅は 1/100mm 指定なので +// CoordMap で device ピクセルへ換算してから GDI ペンを作る。 +void CShapeItem::Draw(CDC& dc, const CoordMap& map, bool selected) +{ + CRect dr = map.MmToDev(m_rect); + + int penW = map.LenToDevX(m_lineWidthHu); + if (penW < 1) penW = 1; + + CPen pen(PS_SOLID, penW, RGB(0, 0, 0)); + CPen* pOld = dc.SelectObject(&pen); + + if (m_shape == 1) + { + // 直線: 矩形の左上→右下 + dc.MoveTo(dr.left, dr.top); + dc.LineTo(dr.right, dr.bottom); + } + else + { + // 矩形枠(塗りつぶしなし) + CBrush* pOldBrush = (CBrush*)dc.SelectStockObject(NULL_BRUSH); + dc.Rectangle(dr); + dc.SelectObject(pOldBrush); + } + + dc.SelectObject(pOld); + + if (selected) + DrawSelection(dc, dr); +} + +// 自身の複製を生成して返す。 +CPrintItem* CShapeItem::Clone() const +{ + return new CShapeItem(*this); +} + +// 永続化。基底(矩形)に続けて図形種別と線幅を読み書きする。 +void CShapeItem::Serialize(CArchive& ar) +{ + CPrintItem::Serialize(ar); + if (ar.IsStoring()) + ar << m_shape << m_lineWidthHu; + else + ar >> m_shape >> m_lineWidthHu; +} diff --git a/ShapeItem.h b/ShapeItem.h new file mode 100644 index 0000000..4543764 --- /dev/null +++ b/ShapeItem.h @@ -0,0 +1,20 @@ +// ShapeItem.h : 図形(矩形枠 / 直線)印刷アイテム +// 各印刷領域の区切り線・枠線に使用。 +#pragma once + +#include "PrintItem.h" + +class CShapeItem : public CPrintItem +{ + DECLARE_SERIAL(CShapeItem) +public: + CShapeItem(); + + int m_shape; // 0=矩形枠, 1=直線(左上→右下) + int m_lineWidthHu; // 線幅(1/100mm) + + ItemType Type() const override { return m_shape == 1 ? ItemType::Line : ItemType::Rect; } + void Draw(CDC& dc, const CoordMap& map, bool selected) override; + CPrintItem* Clone() const override; + void Serialize(CArchive& ar) override; +}; diff --git a/TextItem.cpp b/TextItem.cpp new file mode 100644 index 0000000..5798459 --- /dev/null +++ b/TextItem.cpp @@ -0,0 +1,76 @@ +// TextItem.cpp +#include "pch.h" +#include "TextItem.h" + +IMPLEMENT_SERIAL(CTextItem, CPrintItem, 1) + +// コンストラクタ。既定値(文字列・フォント・10pt・左揃え・非太字)で初期化。 +CTextItem::CTextItem() + : m_text(_T("テキスト")) + , m_fontName(_T("MS ゴシック")) + , m_pointSize(10.0) + , m_align(0) + , m_bold(FALSE) +{ +} + +// テキストを描画する。画面・印刷の双方から同じ本関数が呼ばれ、 +// CoordMap が DPI/ズームの違いを吸収するため、物理サイズ(pt)が +// どの解像度でも正しく出る。selected=true のとき選択枠も描く。 +void CTextItem::Draw(CDC& dc, const CoordMap& map, bool selected) +{ + CRect dr = map.MmToDev(m_rect); + + // フォント高さをデバイス単位で算出(ポイント→1/100mm→デバイス) + int fontHeight = map.PointToDevHeight(m_pointSize); + if (fontHeight < 1) fontHeight = 1; + + LOGFONT lf; + ZeroMemory(&lf, sizeof(lf)); + lf.lfHeight = -fontHeight; // 負値 = 文字セル高さ(=文字高さ指定) + lf.lfWeight = m_bold ? FW_BOLD : FW_NORMAL; + lf.lfCharSet = DEFAULT_CHARSET; + _tcsncpy_s(lf.lfFaceName, LF_FACESIZE, m_fontName, _TRUNCATE); + + CFont font; + font.CreateFontIndirect(&lf); + CFont* pOld = dc.SelectObject(&font); + int oldBk = dc.SetBkMode(TRANSPARENT); + COLORREF oldColor = dc.SetTextColor(RGB(0, 0, 0)); + + UINT fmt = DT_NOPREFIX | DT_WORDBREAK; + if (m_align == 1) fmt |= DT_CENTER; + else if (m_align == 2) fmt |= DT_RIGHT; + else fmt |= DT_LEFT; + + CRect textRect = dr; + dc.DrawText(m_text, textRect, fmt); + + dc.SetTextColor(oldColor); + dc.SetBkMode(oldBk); + dc.SelectObject(pOld); + + if (selected) + DrawSelection(dc, dr); +} + +// 自身の複製を生成して返す(コピー&貼付け・複製機能で使用)。 +CPrintItem* CTextItem::Clone() const +{ + CTextItem* p = new CTextItem(*this); + return p; +} + +// 永続化。基底(矩形)に続けてテキスト固有の属性を読み書きする。 +void CTextItem::Serialize(CArchive& ar) +{ + CPrintItem::Serialize(ar); + if (ar.IsStoring()) + { + ar << m_text << m_fontName << m_pointSize << m_align << m_bold; + } + else + { + ar >> m_text >> m_fontName >> m_pointSize >> m_align >> m_bold; + } +} diff --git a/TextItem.h b/TextItem.h new file mode 100644 index 0000000..7522aff --- /dev/null +++ b/TextItem.h @@ -0,0 +1,22 @@ +// TextItem.h : テキスト印刷アイテム +#pragma once + +#include "PrintItem.h" + +class CTextItem : public CPrintItem +{ + DECLARE_SERIAL(CTextItem) +public: + CTextItem(); + + CString m_text; // 印刷文字列(複数行可) + CString m_fontName; // フォント名 + double m_pointSize; // ポイントサイズ + int m_align; // 0=左, 1=中央, 2=右 + BOOL m_bold; + + ItemType Type() const override { return ItemType::Text; } + void Draw(CDC& dc, const CoordMap& map, bool selected) override; + CPrintItem* Clone() const override; + void Serialize(CArchive& ar) override; +}; diff --git a/TextPropsDlg.cpp b/TextPropsDlg.cpp new file mode 100644 index 0000000..a09bea1 --- /dev/null +++ b/TextPropsDlg.cpp @@ -0,0 +1,47 @@ +// TextPropsDlg.cpp +#include "pch.h" +#include "AveryDennisonUtil.h" +#include "TextPropsDlg.h" + +IMPLEMENT_DYNAMIC(CTextPropsDlg, CDialog) + +// コンストラクタ。各プロパティを既定値で初期化。 +CTextPropsDlg::CTextPropsDlg(CWnd* pParent) + : CDialog(IDD_TEXT_PROPS, pParent) + , m_text(_T("")) + , m_fontName(_T("MS ゴシック")) + , m_pointSize(10.0) + , m_align(0) + , m_bold(FALSE) +{ +} + +// 入力欄と各メンバ(文字列/フォント/サイズ/揃え/太字)の関連付け+サイズ範囲検証。 +void CTextPropsDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + DDX_Text(pDX, IDC_EDIT_TEXT, m_text); + DDX_Text(pDX, IDC_EDIT_FONT, m_fontName); + DDX_Text(pDX, IDC_EDIT_PTSIZE, m_pointSize); + DDV_MinMaxDouble(pDX, m_pointSize, 1.0, 500.0); + DDX_CBIndex(pDX, IDC_COMBO_ALIGN, m_align); + DDX_Check(pDX, IDC_CHECK_BOLD, m_bold); +} + +// 初期化。揃えコンボに「左/中央/右」を入れ、現在の選択を反映する。 +BOOL CTextPropsDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + CComboBox* pAlign = (CComboBox*)GetDlgItem(IDC_COMBO_ALIGN); + if (pAlign && pAlign->GetCount() == 0) + { + pAlign->AddString(_T("左揃え")); + pAlign->AddString(_T("中央揃え")); + pAlign->AddString(_T("右揃え")); + } + if (pAlign) pAlign->SetCurSel(m_align); + return TRUE; +} + +BEGIN_MESSAGE_MAP(CTextPropsDlg, CDialog) +END_MESSAGE_MAP() diff --git a/TextPropsDlg.h b/TextPropsDlg.h new file mode 100644 index 0000000..b3a523f --- /dev/null +++ b/TextPropsDlg.h @@ -0,0 +1,22 @@ +// TextPropsDlg.h : テキストアイテムのプロパティ編集ダイアログ +#pragma once +#include "resource.h" + +class CTextPropsDlg : public CDialog +{ + DECLARE_DYNAMIC(CTextPropsDlg) +public: + CTextPropsDlg(CWnd* pParent = nullptr); + enum { IDD = IDD_TEXT_PROPS }; + + CString m_text; + CString m_fontName; + double m_pointSize; + int m_align; + BOOL m_bold; + +protected: + void DoDataExchange(CDataExchange* pDX) override; + BOOL OnInitDialog() override; + DECLARE_MESSAGE_MAP() +}; diff --git a/pch.cpp b/pch.cpp new file mode 100644 index 0000000..c8bf920 --- /dev/null +++ b/pch.cpp @@ -0,0 +1,2 @@ +// pch.cpp : プリコンパイル済みヘッダを生成するためのソース +#include "pch.h" diff --git a/pch.h b/pch.h new file mode 100644 index 0000000..2a41705 --- /dev/null +++ b/pch.h @@ -0,0 +1,23 @@ +// pch.h : プリコンパイル済みヘッダ +// MFC ダイアログベース アプリ (AveryDennisonUtil) 共通インクルード +#pragma once + +#ifndef VC_EXTRALEAN +#define VC_EXTRALEAN // Windows ヘッダから使用頻度の低い部分を除外 +#endif + +#include "targetver.h" + +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // 一部の CString コンストラクタは明示的指定 + +// MFC の一部の警告を非表示 +#ifndef _AFX_ALL_WARNINGS +#define _AFX_ALL_WARNINGS +#endif + +#include // MFC のコアおよび標準コンポーネント +#include // MFC の拡張部分(CScrollView/CFormView/ツールバー等) +#include // MFC の Internet Explorer 4 コモン コントロール サポート +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC の Windows コモン コントロール サポート +#endif diff --git a/resource.h b/resource.h new file mode 100644 index 0000000..2272273 --- /dev/null +++ b/resource.h @@ -0,0 +1,60 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// AveryDennisonUtil.rc で使用 +// +#define IDR_MAINFRAME 128 +#define IDD_ABOUTBOX 100 +#define IDD_PAPER_SIZE 130 +#define IDD_TEXT_PROPS 131 +#define IDD_MAIN_DIALOG 132 + +// ----- ダイアログ コントロール ----- +#define IDC_CANVAS 1100 +#define IDC_EDIT_WIDTH 1000 +#define IDC_EDIT_HEIGHT 1001 +#define IDC_COMBO_PRESET 1002 +#define IDC_EDIT_DPI 1003 +#define IDC_EDIT_TEXT 1004 +#define IDC_EDIT_FONT 1005 +#define IDC_EDIT_PTSIZE 1006 +#define IDC_COMBO_ALIGN 1007 +#define IDC_CHECK_BOLD 1008 +#define IDC_CHECK_GRID 1009 +#define IDC_EDIT_MARGIN 1010 +#define IDC_CHECK_PRINT_TEMPLATE 1011 + +// ----- コマンド ----- +#define ID_INSERT_TEXT 32771 +#define ID_INSERT_RECT 32772 +#define ID_INSERT_LINE 32773 +#define ID_INSERT_IMAGE 32774 +#define ID_EDIT_PROPERTIES 32775 +#define ID_EDIT_DELETE_ITEM 32776 +#define ID_PAPER_SIZE 32777 +#define ID_VIEW_ZOOMIN 32778 +#define ID_VIEW_ZOOMOUT 32779 +#define ID_ITEM_COPY 32780 +#define ID_ITEM_PASTE 32781 +#define ID_ITEM_DUPLICATE 32782 +#define ID_ITEM_FRONT 32783 +#define ID_ITEM_BACK 32784 +#define ID_ALIGN_MENU 32785 +// 整列サブ項目(連番。AlignSelected の how=ID-ID_ALIGN_LEFT に対応) +#define ID_ALIGN_LEFT 32786 +#define ID_ALIGN_HCENTER 32787 +#define ID_ALIGN_RIGHT 32788 +#define ID_ALIGN_TOP 32789 +#define ID_ALIGN_VCENTER 32790 +#define ID_ALIGN_BOTTOM 32791 +#define ID_TEMPLATE_LOAD 32792 +#define ID_TEMPLATE_CLEAR 32793 + +// Next default values for new objects +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 132 +#define _APS_NEXT_COMMAND_VALUE 32794 +#define _APS_NEXT_CONTROL_VALUE 1012 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/targetver.h b/targetver.h new file mode 100644 index 0000000..53413f1 --- /dev/null +++ b/targetver.h @@ -0,0 +1,6 @@ +#pragma once + +// 最新の Windows プラットフォームを対象とする場合は SDKDDKVer.h をインクルードする。 +// 以前の Windows プラットフォーム向けにビルドする場合は WinSDKVer.h と +// _WIN32_WINNT マクロを先に定義してからインクルードする。 +#include