/*---------------------------- 
   PoePoem1.rc resource script
   rc /r /v PoePoem1.rc
   WindRes -i PoePoem1.rc -o PoePoem1_res.obj -v
  ----------------------------*/
#include "PoePoem1.h"
PoePoem1    ICON  PoePoem1.ico
AnnabelLee  TEXT  PoePoem1.txt
STRINGTABLE
{
 IDS_APPNAME, "PoePoem1"
 IDS_CAPTION, """Annabel Lee"" by Edgar Allen Poe"
 IDS_POEMRES, "AnnabelLee"
}
/*-----------------------
   PoePoem1.h header file
  -----------------------*/
#define IDS_APPNAME 0
#define IDS_CAPTION 1
#define IDS_POEMRES 2
// Main.h
#ifndef Main_h
#define Main_h
#define dim(x)                          (sizeof(x) / sizeof(x[0]))
#define TYPEFACE_NAME                   L"Times New Roman"
#define FONT_SIZE                       12
struct                                  WndEventArgs
{
 HWND                                   hWnd;
 WPARAM                                 wParam;
 LPARAM                                 lParam;
 HINSTANCE                              hIns;
};
LRESULT CALLBACK fnWndProc_OnCreate     (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnSize       (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnVScroll    (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnHScroll    (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnMouseWheel (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnPaint      (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnDestroy    (WndEventArgs& Wea);  
struct                                  EVENTHANDLER
{
 unsigned int                           iMsg; 
 LRESULT                                (CALLBACK* fnPtr)(WndEventArgs&);
};
const EVENTHANDLER                      EventHandler[]=
{
 {WM_CREATE,                            fnWndProc_OnCreate},
 {WM_SIZE,                              fnWndProc_OnSize},
 {WM_VSCROLL,                           fnWndProc_OnVScroll},
 {WM_HSCROLL,                           fnWndProc_OnHScroll},
 {WM_MOUSEWHEEL,                        fnWndProc_OnMouseWheel},
 {WM_PAINT,                             fnWndProc_OnPaint},
 {WM_DESTROY,                           fnWndProc_OnDestroy}
};
struct ScrollData
{
 wchar_t**                              pPtrs;
 int                                    iNumLines;
 int                                    cxChar;
 int                                    cxCaps;
 int                                    cyChar;
 int                                    cxClient;
 int                                    cyClient;
 int                                    iMaxWidth;
};
#endif
//#define TCLib      // PoePoem1.cpp
#ifndef UNICODE      // cl  PoePoem1.cpp /O1 /Os /MT  user32.lib gdi32.lib PoePoem1.res                 // 99,328 Bytes;
   #define UNICODE   // cl  PoePoem1.cpp /O1 /Os /GR- /GS- TCLib.lib user32.lib gdi32.lib PoePoem1.res  // 10,752 Bytes
#endif               // g++ PoePoem1.cpp PoePoem1_res.obj -o PoePoem1_gcc.exe -mwindows -m64 -s -Os     // 25,600 Bytes
#ifndef _UNICODE 
   #define _UNICODE
#endif
#include <Windows.h>
#ifdef TCLib
   #include "string.h"
   #include "stdio.h"
   #include "memory.h"
#else
   #include <string.h>
   #include <stdio.h>
   #include <wchar.h>
#endif
#include "Main.h"
#include "PoePoem1.h"
LRESULT CALLBACK fnWndProc_OnCreate(WndEventArgs& Wea)
{
 ScrollData* pScrDta   = NULL;
 char*       pText     = NULL;
 wchar_t*    pWText    = NULL;
 wchar_t*    p         = NULL;
 HGLOBAL     hResource = NULL;
 HANDLE      hHeap     = NULL;
 HRSRC       hFoundRes = NULL;
 HDC         hdc       = NULL;
 HFONT       hFont     = NULL;
 size_t      iStrLen   = 0;
 char        szPoemRes[16];
 wchar_t     szBuffer[128];
 TEXTMETRIC  tm;
 Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;              // Get HINSTANCE From CREATESTRUCT Object
 LoadStringA(Wea.hIns,IDS_POEMRES,szPoemRes,sizeof(szPoemRes)); // Get Name Of Custom TEXT Resource "AnnabelLee" (It's Asci)
 hFoundRes = FindResourceA(Wea.hIns,szPoemRes,"TEXT");          // Find Custom Resource In Binary Executable
 hResource = LoadResource(Wea.hIns,hFoundRes);                  // Get HANDLE To Resource, So We Can Get Pointer To It.
 pText=(char*)LockResource(hResource);                          // Get Pointer To First Byte Of Custom Resource
 
 hHeap=GetProcessHeap();                                        // Will Need To Allocate Memory For A ScrollData Object
 pScrDta=(ScrollData*)HeapAlloc                                 // (See Main.h) So As To Eliminate Charles Petzold's
 (                                                              // Static And Global C Variables.  We'll Store The
  hHeap,HEAP_ZERO_MEMORY,sizeof(ScrollData)                     // Allocated Object As Part Of The App's WNDCLASSEX
 );                                                             // cbWndExtra Bytes, Which Is Kind Of A C Ism For A
 if(!pScrDta)                                                   // C++ Class Member Variable.  If -1 Is Returned From
    return -1;                                                  // WM_CREATE Handler Code, The CreateWindowEx() Call
 SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pScrDta);                // In WinMain() Will Fail, And App Will Terminate.
 hdc = GetDC(Wea.hWnd);                                         // To Get Scroll Bars And Scrolling Working, One Needs
 hFont=CreateFont                                               // To Know, For Example, The Size (Height) Of A Char-
 (                                                              // acter In Relation To The Size Of The Client Area Of
   -1*(FONT_SIZE*GetDeviceCaps(hdc,LOGPIXELSY))/72,             // The Window Where Text Is To Be Displayed And Scrolled.
   0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,    // So We Need To Create A Font That The App Will Use, 
   0,TYPEFACE_NAME                                              // And Get It's Metrics Using GetTextMetrics().  We'll
 );                                                             // Store This Info In The ScrollData Persisted Object,
 if(!hFont)                                                     // And Let It Up To The WM_SIZE Handler Code To Deal
    return -1;                                                  // With The Screen Size Stuff.  We Gotta Be Careful To
 HFONT hTmp=(HFONT)SelectObject(hdc,hFont);                     // Delete The Dynamically Allocated Resource.
 GetTextMetrics(hdc, &tm);
 pScrDta->cxChar = tm.tmAveCharWidth;
 pScrDta->cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * pScrDta->cxChar / 2;
 pScrDta->cyChar = tm.tmHeight + tm.tmExternalLeading;
 DeleteObject(SelectObject(hdc,hTmp));
 ReleaseDC(Wea.hWnd, hdc);
 
 iStrLen=MultiByteToWideChar(CP_ACP,0,pText,-1,NULL,0);         // We Need To Convert Asci "Annabel Lee" Poem Read From
 pWText=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iStrLen*2);  // Executable Resource To Wide Character For GUI Output
 MultiByteToWideChar(CP_ACP,0,pText,-1,pWText,iStrLen);         // In WM_PAINT Handler Code.  We'll Use MultiByteToWideChar().
 
 p=pWText;      // Run Through Annabel Lee Poem Counting Line Feed Characters Because, For The Scroll Logic This App 
 while(*p)      // Employs Using TextOut() In WM_PAINT Handler Code, We Need To Break The Poem Up Into An Array Of Lines.  
 {              // We'll Persist The Count Obtained Here In Our ScrollData Object, Which Becomes Part Of The 
   if(*p==13)   // WNDCLASSEX::cbWndExtra Bytes.  This Eliminates Charles Petzold's statics and globals.
      pScrDta->iNumLines++;
   p++;
 }
 pScrDta->pPtrs=(wchar_t**)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,pScrDta->iNumLines*sizeof(wchar_t**));
 if(pScrDta->pPtrs)
 {                                              // This Code Will Break The CR-LF Delimited Poem Lines From The Resources 
    size_t j;                                   // In The Executable Into An Array Of Character Pointers -- All Contained
    p=pWText;                                   // Within The ScrollData Object.  Wouldn't Surprise Me If There Was 
    for(size_t i=0; i<pScrDta->iNumLines; i++)  // Something Or Other In The C++ Specific Part Of The Standard Library To
    {                                           // Do This Easier, But Since I'm A Hard Core C Coder Code Like This Doesn't
        wmemset(szBuffer,'\0',128);             // Bother Me. Especially Since I Can Build This x64 Using My Own Custom C 
        j=0;                                    // Runtime In 10 K, As Opposed To A Megabyte Sized Executable If Linked
        while(true)                             // Against MS's LibCmt.  So What The Code Does Is Run Through The Poem Byte
        {                                       // By Byte Detecting When A NewLine Sequence (CR-LF) Has Delimited A Line. 
           if(*p==10)                           // Up Until That Point, Ordinary Printable Characters Are Accumulated In
              p++;                              // szBuffer[], But When A Newline Is Encountered The CR Is Replaced By A        
           if(*p==13)                           // NULL Byte Terminating The String In szBuffer.  Then We Make A Memory Alloc-
           {                                    // ation For The Delimited Line And Store It In ScrollData.
              szBuffer[j]=L'\0';
              iStrLen=wcslen(szBuffer);
              if(iStrLen>pScrDta->iMaxWidth)
                 pScrDta->iMaxWidth=iStrLen;
              pScrDta->pPtrs[i]=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iStrLen*2+2);
              wcscpy(pScrDta->pPtrs[i],szBuffer);
              p++;
              break;
           }
           szBuffer[j]=*p;
           p++,j++;
           
        }
    }
    pScrDta->iMaxWidth=pScrDta->iMaxWidth*pScrDta->cxChar;
 }
 
 return 0; 
}
LRESULT CALLBACK fnWndProc_OnSize(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    pScrDta->cxClient = LOWORD(Wea.lParam);
    pScrDta->cyClient = HIWORD(Wea.lParam);
    si.cbSize = sizeof(si) ;
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iNumLines - 1;
    si.nPage  = pScrDta->cyClient / pScrDta->cyChar;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    si.cbSize = sizeof(si);
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iMaxWidth / pScrDta->cxChar;
    si.nPage  = pScrDta->cxClient / pScrDta->cxChar;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
 }
 
 return 0; 
}
LRESULT CALLBACK fnWndProc_OnVScroll(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    si.cbSize = sizeof(si) ;// Get all the vertial scroll bar information
    si.fMask  = SIF_ALL ;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    int iVertPos = si.nPos; // Save the position for comparison later on
    switch (LOWORD(Wea.wParam))
    {
      case SB_TOP:
           si.nPos = si.nMin ;
           break ;
      case SB_BOTTOM:
           si.nPos = si.nMax ;
           break ;
      case SB_LINEUP:
           si.nPos -= 1 ;
           break ;
      case SB_LINEDOWN:
           si.nPos += 1 ;
           break ;
      case SB_PAGEUP:
           si.nPos -= si.nPage ;
           break ;
      case SB_PAGEDOWN:
           si.nPos += si.nPage ;
           break ;
      case SB_THUMBTRACK:
           si.nPos = si.nTrackPos ;
           break ;
      default:
           break ;
    }
    si.fMask = SIF_POS ;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    if(si.nPos != iVertPos)
    {
       ScrollWindow(Wea.hWnd, 0, pScrDta->cyChar*(iVertPos-si.nPos), NULL, NULL);
       UpdateWindow(Wea.hWnd);
    }
 }
 
 return 0; 
}
LRESULT CALLBACK fnWndProc_OnHScroll(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    si.cbSize = sizeof (si);// Get all the horizontal scroll bar information
    si.fMask  = SIF_ALL;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si) ;// Save the position for comparison later on
    int iHorzPos = si.nPos;
    switch (LOWORD(Wea.wParam))
    {
      case SB_LINELEFT:
           si.nPos -= 1 ;
           break ;
      case SB_LINERIGHT:
           si.nPos += 1 ;
           break ;
      case SB_PAGELEFT:
           si.nPos -= si.nPage ;
           break ;
      case SB_PAGERIGHT:
           si.nPos += si.nPage ;
           break ;
      case SB_THUMBTRACK:              // case SB_THUMBPOSITION:
           si.nPos = si.nTrackPos ;
           break ;
      default :
           break ;
    }
    si.fMask = SIF_POS;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si);
    if(si.nPos != iHorzPos)
       ScrollWindow(Wea.hWnd, pScrDta->cxChar*(iHorzPos-si.nPos), 0, NULL, NULL);
 }
 
 return 0; 
}
LRESULT CALLBACK fnWndProc_OnMouseWheel(WndEventArgs& Wea)
{
 int zdelta=GET_WHEEL_DELTA_WPARAM(Wea.wParam);
 if(zdelta>0)
 {
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEUP,0),0);
 }
 else
 {
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);
 }
 
 return 0; 
}
LRESULT CALLBACK fnWndProc_OnPaint(WndEventArgs& Wea)
{
 int x,y,iPaintBeg,iPaintEnd,iVertPos,iHorzPos;
 ScrollData* pScrDta=NULL;
 HFONT hFont=NULL;
 PAINTSTRUCT ps;
 SCROLLINFO si;
 HDC hdc;
 hdc = BeginPaint(Wea.hWnd, &ps);
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    
    hFont=CreateFont
    (
     -1*(FONT_SIZE*GetDeviceCaps(hdc,LOGPIXELSY))/72,0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,0,TYPEFACE_NAME
    );
    HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
    si.cbSize = sizeof (si) ;// Get vertical scroll bar position
    si.fMask  = SIF_POS ;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si), iVertPos = si.nPos;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si), iHorzPos = si.nPos;
    if(iVertPos+ps.rcPaint.top/pScrDta->cyChar>0)
       iPaintBeg=iVertPos + ps.rcPaint.top / pScrDta->cyChar;
    else
       iPaintBeg=0;
    if(iVertPos + ps.rcPaint.bottom / pScrDta->cyChar < pScrDta->iNumLines - 1)
       iPaintEnd=iVertPos + ps.rcPaint.bottom / pScrDta->cyChar;
    else
       iPaintEnd=pScrDta->iNumLines-1;
    for(int i = iPaintBeg; i<= iPaintEnd; i++)
    {
        x = pScrDta->cxChar * (1 - iHorzPos);
        y = pScrDta->cyChar * (i - iVertPos);
        TextOut(hdc, x, y, pScrDta->pPtrs[i], (int)wcslen(pScrDta->pPtrs[i]));
    }
    DeleteObject(SelectObject(hdc,hTmp));
 }
 EndPaint(Wea.hWnd, &ps);
 
 return 0; 
}
LRESULT CALLBACK fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 ScrollData* pScrDta = NULL;
 HANDLE hHeap        = NULL;
 
 hHeap=GetProcessHeap();
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta->pPtrs)
 {
    for(size_t i=0; i<pScrDta->iNumLines; i++)
        HeapFree(hHeap,0,pScrDta->pPtrs[i]);
    HeapFree(hHeap,0,pScrDta->pPtrs);
 }
 HeapFree(hHeap,0,pScrDta);
 PostQuitMessage(0);
 
 return 0; 
}
LRESULT CALLBACK fnWndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;
 for(size_t i=0; i<dim(EventHandler); i++)
 {
     if(EventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hWnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(Wea);
     }
 }
 return (DefWindowProc(hWnd, msg, wParam, lParam));
}
  
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
 char       szAppName[64];
 char       szCaption[64];
 wchar_t    szWAppName[16];
 wchar_t    szWCaption[40];
 HWND       hWnd = NULL;
 WNDCLASSEX wc   = {0};
 MSG        msg;
  
 LoadStringA(hInstance, IDS_APPNAME, szAppName, sizeof(szAppName));
 LoadStringA(hInstance, IDS_CAPTION, szCaption, sizeof(szCaption));
 MultiByteToWideChar(CP_UTF8,0,szAppName,-1,szWAppName,16);
 MultiByteToWideChar(CP_UTF8,0,szCaption,-1,szWCaption,40);
 wc.cbSize        = sizeof(wc);
 wc.style         = CS_HREDRAW | CS_VREDRAW;
 wc.lpfnWndProc   = fnWndProc;
 wc.cbClsExtra    = 0;
 wc.cbWndExtra    = sizeof(void*);
 wc.hInstance     = hInstance;
 wc.hIcon         = LoadIcon(hInstance, szWAppName);
 wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
 wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
 wc.lpszMenuName  = NULL;
 wc.lpszClassName = szWAppName;
 wc.hIconSm       = LoadIcon(hInstance, szWAppName);
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szWAppName,szWCaption,WS_OVERLAPPEDWINDOW|WS_VISIBLE,300,150,500,800,NULL,NULL,hInstance,NULL);
 UpdateWindow(hWnd);
 while(GetMessage(&msg, NULL, 0, 0))
 {
   TranslateMessage(&msg) ;
   DispatchMessage(&msg) ;
 }
 
 return (int)msg.wParam;
}
     I’ve played with this on and off for a couple weeks, and have quite a few versions on my laptops.  To use my code to build the apps you may want to change filenames to suit yourself.  And you’ll need Charles’ PoePoem.ico and PoePoem.txt or PoePoem.asc files.  You’ll need to change file names in a lot of places to get it to all work out, come up with the right icon, etc.  Easiest thing to do would be to use my file names, and just rename his PoePoem.ico and PoePoem.txt or PoePoem.asc files.