master
  1const std = @import("std");
  2const utils = @import("utils.zig");
  3const res = @import("res.zig");
  4const SourceBytes = @import("literals.zig").SourceBytes;
  5
  6// https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files
  7
  8pub const ResourceType = enum {
  9    accelerators,
 10    bitmap,
 11    cursor,
 12    dialog,
 13    dialogex,
 14    /// As far as I can tell, this is undocumented; the most I could find was this:
 15    /// https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/91697
 16    dlginclude,
 17    /// Undocumented, basically works exactly like RCDATA
 18    dlginit,
 19    font,
 20    html,
 21    icon,
 22    menu,
 23    menuex,
 24    messagetable,
 25    plugplay, // Obsolete
 26    rcdata,
 27    stringtable,
 28    /// Undocumented
 29    toolbar,
 30    user_defined,
 31    versioninfo,
 32    vxd, // Obsolete
 33
 34    // Types that are treated as a user-defined type when encountered, but have
 35    // special meaning without the Visual Studio GUI. We match the Win32 RC compiler
 36    // behavior by acting as if these keyword don't exist when compiling the .rc
 37    // (thereby treating them as user-defined).
 38    //textinclude, // A special resource that is interpreted by Visual C++.
 39    //typelib, // A special resource that is used with the /TLBID and /TLBOUT linker options
 40
 41    // Types that can only be specified by numbers, they don't have keywords
 42    cursor_num,
 43    icon_num,
 44    string_num,
 45    anicursor_num,
 46    aniicon_num,
 47    fontdir_num,
 48    manifest_num,
 49
 50    const map = std.StaticStringMapWithEql(
 51        ResourceType,
 52        std.static_string_map.eqlAsciiIgnoreCase,
 53    ).initComptime(.{
 54        .{ "ACCELERATORS", .accelerators },
 55        .{ "BITMAP", .bitmap },
 56        .{ "CURSOR", .cursor },
 57        .{ "DIALOG", .dialog },
 58        .{ "DIALOGEX", .dialogex },
 59        .{ "DLGINCLUDE", .dlginclude },
 60        .{ "DLGINIT", .dlginit },
 61        .{ "FONT", .font },
 62        .{ "HTML", .html },
 63        .{ "ICON", .icon },
 64        .{ "MENU", .menu },
 65        .{ "MENUEX", .menuex },
 66        .{ "MESSAGETABLE", .messagetable },
 67        .{ "PLUGPLAY", .plugplay },
 68        .{ "RCDATA", .rcdata },
 69        .{ "STRINGTABLE", .stringtable },
 70        .{ "TOOLBAR", .toolbar },
 71        .{ "VERSIONINFO", .versioninfo },
 72        .{ "VXD", .vxd },
 73    });
 74
 75    pub fn fromString(bytes: SourceBytes) ResourceType {
 76        const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(bytes);
 77        if (maybe_ordinal) |ordinal| {
 78            if (ordinal.ordinal >= 256) return .user_defined;
 79            return fromRT(@enumFromInt(ordinal.ordinal));
 80        }
 81        return map.get(bytes.slice) orelse .user_defined;
 82    }
 83
 84    // TODO: Some comptime validation that RT <-> ResourceType conversion is synced?
 85    pub fn fromRT(rt: res.RT) ResourceType {
 86        return switch (rt) {
 87            .ACCELERATOR => .accelerators,
 88            .ANICURSOR => .anicursor_num,
 89            .ANIICON => .aniicon_num,
 90            .BITMAP => .bitmap,
 91            .CURSOR => .cursor_num,
 92            .DIALOG => .dialog,
 93            .DLGINCLUDE => .dlginclude,
 94            .DLGINIT => .dlginit,
 95            .FONT => .font,
 96            .FONTDIR => .fontdir_num,
 97            .GROUP_CURSOR => .cursor,
 98            .GROUP_ICON => .icon,
 99            .HTML => .html,
100            .ICON => .icon_num,
101            .MANIFEST => .manifest_num,
102            .MENU => .menu,
103            .MESSAGETABLE => .messagetable,
104            .PLUGPLAY => .plugplay,
105            .RCDATA => .rcdata,
106            .STRING => .string_num,
107            .TOOLBAR => .toolbar,
108            .VERSION => .versioninfo,
109            .VXD => .vxd,
110            _ => .user_defined,
111        };
112    }
113
114    pub fn canUseRawData(resource: ResourceType) bool {
115        return switch (resource) {
116            .user_defined,
117            .html,
118            .plugplay, // Obsolete
119            .rcdata,
120            .vxd, // Obsolete
121            .manifest_num,
122            .dlginit,
123            => true,
124            else => false,
125        };
126    }
127
128    pub fn nameForErrorDisplay(resource: ResourceType) []const u8 {
129        return switch (resource) {
130            // zig fmt: off
131            .accelerators, .bitmap, .cursor, .dialog, .dialogex, .dlginclude, .dlginit, .font,
132            .html, .icon, .menu, .menuex, .messagetable, .plugplay, .rcdata, .stringtable,
133            .toolbar, .versioninfo, .vxd => @tagName(resource),
134            // zig fmt: on
135            .user_defined => "user-defined",
136            .cursor_num => std.fmt.comptimePrint("{d} (cursor)", .{@intFromEnum(res.RT.CURSOR)}),
137            .icon_num => std.fmt.comptimePrint("{d} (icon)", .{@intFromEnum(res.RT.ICON)}),
138            .string_num => std.fmt.comptimePrint("{d} (string)", .{@intFromEnum(res.RT.STRING)}),
139            .anicursor_num => std.fmt.comptimePrint("{d} (anicursor)", .{@intFromEnum(res.RT.ANICURSOR)}),
140            .aniicon_num => std.fmt.comptimePrint("{d} (aniicon)", .{@intFromEnum(res.RT.ANIICON)}),
141            .fontdir_num => std.fmt.comptimePrint("{d} (fontdir)", .{@intFromEnum(res.RT.FONTDIR)}),
142            .manifest_num => std.fmt.comptimePrint("{d} (manifest)", .{@intFromEnum(res.RT.MANIFEST)}),
143        };
144    }
145};
146
147/// https://learn.microsoft.com/en-us/windows/win32/menurc/stringtable-resource#parameters
148/// https://learn.microsoft.com/en-us/windows/win32/menurc/dialog-resource#parameters
149/// https://learn.microsoft.com/en-us/windows/win32/menurc/dialogex-resource#parameters
150pub const OptionalStatements = enum {
151    characteristics,
152    language,
153    version,
154
155    // DIALOG
156    caption,
157    class,
158    exstyle,
159    font,
160    menu,
161    style,
162
163    pub const map = std.StaticStringMapWithEql(
164        OptionalStatements,
165        std.static_string_map.eqlAsciiIgnoreCase,
166    ).initComptime(.{
167        .{ "CHARACTERISTICS", .characteristics },
168        .{ "LANGUAGE", .language },
169        .{ "VERSION", .version },
170    });
171
172    pub const dialog_map = std.StaticStringMapWithEql(
173        OptionalStatements,
174        std.static_string_map.eqlAsciiIgnoreCase,
175    ).initComptime(.{
176        .{ "CAPTION", .caption },
177        .{ "CLASS", .class },
178        .{ "EXSTYLE", .exstyle },
179        .{ "FONT", .font },
180        .{ "MENU", .menu },
181        .{ "STYLE", .style },
182    });
183};
184
185pub const Control = enum {
186    auto3state,
187    autocheckbox,
188    autoradiobutton,
189    checkbox,
190    combobox,
191    control,
192    ctext,
193    defpushbutton,
194    edittext,
195    hedit,
196    iedit,
197    groupbox,
198    icon,
199    listbox,
200    ltext,
201    pushbox,
202    pushbutton,
203    radiobutton,
204    rtext,
205    scrollbar,
206    state3,
207    userbutton,
208
209    pub const map = std.StaticStringMapWithEql(
210        Control,
211        std.static_string_map.eqlAsciiIgnoreCase,
212    ).initComptime(.{
213        .{ "AUTO3STATE", .auto3state },
214        .{ "AUTOCHECKBOX", .autocheckbox },
215        .{ "AUTORADIOBUTTON", .autoradiobutton },
216        .{ "CHECKBOX", .checkbox },
217        .{ "COMBOBOX", .combobox },
218        .{ "CONTROL", .control },
219        .{ "CTEXT", .ctext },
220        .{ "DEFPUSHBUTTON", .defpushbutton },
221        .{ "EDITTEXT", .edittext },
222        .{ "HEDIT", .hedit },
223        .{ "IEDIT", .iedit },
224        .{ "GROUPBOX", .groupbox },
225        .{ "ICON", .icon },
226        .{ "LISTBOX", .listbox },
227        .{ "LTEXT", .ltext },
228        .{ "PUSHBOX", .pushbox },
229        .{ "PUSHBUTTON", .pushbutton },
230        .{ "RADIOBUTTON", .radiobutton },
231        .{ "RTEXT", .rtext },
232        .{ "SCROLLBAR", .scrollbar },
233        .{ "STATE3", .state3 },
234        .{ "USERBUTTON", .userbutton },
235    });
236
237    pub fn hasTextParam(control: Control) bool {
238        switch (control) {
239            .scrollbar, .listbox, .iedit, .hedit, .edittext, .combobox => return false,
240            else => return true,
241        }
242    }
243};
244
245pub const ControlClass = struct {
246    pub const map = std.StaticStringMapWithEql(
247        res.ControlClass,
248        std.static_string_map.eqlAsciiIgnoreCase,
249    ).initComptime(.{
250        .{ "BUTTON", .button },
251        .{ "EDIT", .edit },
252        .{ "STATIC", .static },
253        .{ "LISTBOX", .listbox },
254        .{ "SCROLLBAR", .scrollbar },
255        .{ "COMBOBOX", .combobox },
256    });
257
258    /// Like `map.get` but works on WTF16 strings, for use with parsed
259    /// string literals ("BUTTON", or even "\x42UTTON")
260    pub fn fromWideString(str: []const u16) ?res.ControlClass {
261        const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral;
262        return if (ascii.eqlIgnoreCaseW(str, utf16Literal("BUTTON")))
263            .button
264        else if (ascii.eqlIgnoreCaseW(str, utf16Literal("EDIT")))
265            .edit
266        else if (ascii.eqlIgnoreCaseW(str, utf16Literal("STATIC")))
267            .static
268        else if (ascii.eqlIgnoreCaseW(str, utf16Literal("LISTBOX")))
269            .listbox
270        else if (ascii.eqlIgnoreCaseW(str, utf16Literal("SCROLLBAR")))
271            .scrollbar
272        else if (ascii.eqlIgnoreCaseW(str, utf16Literal("COMBOBOX")))
273            .combobox
274        else
275            null;
276    }
277};
278
279const ascii = struct {
280    /// Compares ASCII values case-insensitively, non-ASCII values are compared directly
281    pub fn eqlIgnoreCaseW(a: []const u16, b: []const u16) bool {
282        if (a.len != b.len) return false;
283        for (a, b) |a_c, b_c| {
284            if (a_c < 128) {
285                if (std.ascii.toLower(@intCast(a_c)) != std.ascii.toLower(@intCast(b_c))) return false;
286            } else {
287                if (a_c != b_c) return false;
288            }
289        }
290        return true;
291    }
292};
293
294pub const MenuItem = enum {
295    menuitem,
296    popup,
297
298    pub const map = std.StaticStringMapWithEql(
299        MenuItem,
300        std.static_string_map.eqlAsciiIgnoreCase,
301    ).initComptime(.{
302        .{ "MENUITEM", .menuitem },
303        .{ "POPUP", .popup },
304    });
305
306    pub fn isSeparator(bytes: []const u8) bool {
307        return std.ascii.eqlIgnoreCase(bytes, "SEPARATOR");
308    }
309
310    pub const Option = enum {
311        checked,
312        grayed,
313        help,
314        inactive,
315        menubarbreak,
316        menubreak,
317
318        pub const map = std.StaticStringMapWithEql(
319            Option,
320            std.static_string_map.eqlAsciiIgnoreCase,
321        ).initComptime(.{
322            .{ "CHECKED", .checked },
323            .{ "GRAYED", .grayed },
324            .{ "HELP", .help },
325            .{ "INACTIVE", .inactive },
326            .{ "MENUBARBREAK", .menubarbreak },
327            .{ "MENUBREAK", .menubreak },
328        });
329    };
330};
331
332pub const ToolbarButton = enum {
333    button,
334    separator,
335
336    pub const map = std.StaticStringMapWithEql(
337        ToolbarButton,
338        std.static_string_map.eqlAsciiIgnoreCase,
339    ).initComptime(.{
340        .{ "BUTTON", .button },
341        .{ "SEPARATOR", .separator },
342    });
343};
344
345pub const VersionInfo = enum {
346    file_version,
347    product_version,
348    file_flags_mask,
349    file_flags,
350    file_os,
351    file_type,
352    file_subtype,
353
354    pub const map = std.StaticStringMapWithEql(
355        VersionInfo,
356        std.static_string_map.eqlAsciiIgnoreCase,
357    ).initComptime(.{
358        .{ "FILEVERSION", .file_version },
359        .{ "PRODUCTVERSION", .product_version },
360        .{ "FILEFLAGSMASK", .file_flags_mask },
361        .{ "FILEFLAGS", .file_flags },
362        .{ "FILEOS", .file_os },
363        .{ "FILETYPE", .file_type },
364        .{ "FILESUBTYPE", .file_subtype },
365    });
366};
367
368pub const VersionBlock = enum {
369    block,
370    value,
371
372    pub const map = std.StaticStringMapWithEql(
373        VersionBlock,
374        std.static_string_map.eqlAsciiIgnoreCase,
375    ).initComptime(.{
376        .{ "BLOCK", .block },
377        .{ "VALUE", .value },
378    });
379};
380
381/// Keywords that are be the first token in a statement and (if so) dictate how the rest
382/// of the statement is parsed.
383pub const TopLevelKeywords = enum {
384    language,
385    version,
386    characteristics,
387    stringtable,
388
389    pub const map = std.StaticStringMapWithEql(
390        TopLevelKeywords,
391        std.static_string_map.eqlAsciiIgnoreCase,
392    ).initComptime(.{
393        .{ "LANGUAGE", .language },
394        .{ "VERSION", .version },
395        .{ "CHARACTERISTICS", .characteristics },
396        .{ "STRINGTABLE", .stringtable },
397    });
398};
399
400pub const CommonResourceAttributes = enum {
401    preload,
402    loadoncall,
403    fixed,
404    moveable,
405    discardable,
406    pure,
407    impure,
408    shared,
409    nonshared,
410
411    pub const map = std.StaticStringMapWithEql(
412        CommonResourceAttributes,
413        std.static_string_map.eqlAsciiIgnoreCase,
414    ).initComptime(.{
415        .{ "PRELOAD", .preload },
416        .{ "LOADONCALL", .loadoncall },
417        .{ "FIXED", .fixed },
418        .{ "MOVEABLE", .moveable },
419        .{ "DISCARDABLE", .discardable },
420        .{ "PURE", .pure },
421        .{ "IMPURE", .impure },
422        .{ "SHARED", .shared },
423        .{ "NONSHARED", .nonshared },
424    });
425};
426
427pub const AcceleratorTypeAndOptions = enum {
428    virtkey,
429    ascii,
430    noinvert,
431    alt,
432    shift,
433    control,
434
435    pub const map = std.StaticStringMapWithEql(
436        AcceleratorTypeAndOptions,
437        std.static_string_map.eqlAsciiIgnoreCase,
438    ).initComptime(.{
439        .{ "VIRTKEY", .virtkey },
440        .{ "ASCII", .ascii },
441        .{ "NOINVERT", .noinvert },
442        .{ "ALT", .alt },
443        .{ "SHIFT", .shift },
444        .{ "CONTROL", .control },
445    });
446};