zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

rc.zig (13194B) - Raw


      1 const std = @import("std");
      2 const utils = @import("utils.zig");
      3 const res = @import("res.zig");
      4 const SourceBytes = @import("literals.zig").SourceBytes;
      5 
      6 // https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files
      7 
      8 pub 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
    150 pub 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 
    185 pub 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 
    245 pub 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 
    279 const 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 
    294 pub 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 
    332 pub 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 
    345 pub 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 
    368 pub 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.
    383 pub 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 
    400 pub 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 
    427 pub 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 };