15th of Oct. 1997 by Michael

Desktop Projects

Japanese Full-Screen editor for MS DOS

This one dates back to 1997, shortly before I moved to Japan. It is probably my first published little program: I submitted this successfully for inclusion in the Simtel software archive, so it will probably have found its way to quite a lot of CD-ROMs and mirror websites. The lettering though.

 
 

The package has a .exe file that still runs fine in dosbox-x on macOS. I can’t even remember how I built this, it must have been Borland C++, which you had to install from I think a dozen of floppy-disks, I kid you not.

The heart of the project is probably this low-level C file with routines that talk to the VGA graphics card and blit the actual character bitmaps, and do all the visual stuff “by hand”.

For example, scrolling a line means that you have to copy up all the pixels that are below the line onto the deleted line, and then overwrite the last line on the screen with the characters of the line that has now become visible at the bottom.

/* EOUE640.C   EOUE Project, 1997 by Michael Mangelsdorf */
/* THIS FILE IS PUBLIC DOMAIN.
   LEAVE THIS MESSAGE IN PLACE AS COURTESY TO THE AUTHOR */

#include <dos.h>
#include <stdio.h>

extern unsigned char joyokanji_bitmap16x16[], kana_bitmap16x16[];

unsigned char TINV=0; /* text inverted */

unsigned char *eoue_font8x16ptr, *eoue_font8x8ptr,
          *eoue_vidmem=MK_FP(0xA000,0);
unsigned eoue_visualpage;

unsigned char eoue_pal_colors [17][3]; /* Index in RGB order, hold color
                     components pointed to by palette
                     registers in palette_buf[],
                     used by eoue_pal() */

unsigned char eoue_palette_buf [17]; /* Holds copy of pre-process palette,
                    used by eoue_pal(). */


void eoue_pal (unsigned char op);
void eoue_clrstat (void);
void eoue_cpythru (void);
void eoue_clrscr (void);  /*Forward prototypes*/
void eoue_pageflip (void);

/* Returns true if VGA detected.
   Called by eoue_setgr() when argument 1.
*/
unsigned char eoue_hasvga (void) {
union REGS r;
  r.x.ax=0x1A00; int86(0x10,&r,&r);
  if (r.h.al==0x1A) return 1;
   else return 0;
}


/* Get and store pointer to VGA 8x8 ASCII font.
   Called during eoue_setgr() when argument 1.
*/
void eoue_get8x8ptr (void) {
 static unsigned font8x8seg, font8x8off;
  asm {
   push bp
   push es
   mov ax,0x1130
   mov bh,3
   int 0x10
   mov [font8x8seg],es
   mov [font8x8off],bp
   pop es
   pop bp
  }
  eoue_font8x8ptr=MK_FP(font8x8seg,font8x8off);
}

/* Get and store pointer to VGA 8x16 ASCII font.
   Called during eoue_setgr() when argument 1.
*/
void eoue_get8x16ptr (void) {
 static unsigned font8x16seg, font8x16off;
  asm {
   push bp
   mov ax,0x1130
   mov bh,6
   int 0x10
   mov [font8x16seg],es
   mov [font8x16off],bp
   pop bp
  }
  eoue_font8x16ptr=MK_FP(font8x16seg,font8x16off);
}

/* Wait until (stale) retrace finished,
   then wait until display end.
*/
void eoue_flyback (void) {
  while (inportb(0x3DA)&8)    /*3DA: VGA input status register 1*/
  ; /*NOP*/                   /*Bit three indicates vertical retrace phase*/
  while (!(inportb(0x3DA)&8))
  ; /*NOP*/
}

/* Switch VGA display mode, BIOS interrupt.
   Argument: 0 sets textmode, 1 sets 640x480x16 mode.
   We modify a CRT setting; display end is going to
   be early, at raster line 400. This is to be able
   to have to swappable display pages.
   Failing function returns 1.
*/
unsigned char eoue_setgr (unsigned char grmode) {
 union REGS r;
 if (!eoue_hasvga()) return 1;

 if (!grmode) {
    /* blacken display for smooth palette restoration*/
    r.h.ah=0x12; r.h.bl=0x36; r.h.al=1; int86(0x10,&r,&r);
    eoue_pal(0);
 }
 /*set video mode*/
 r.h.ah=0; r.h.al=grmode?0x12:0x03;
 int86(0x10,&r,&r);
 if (!grmode) return 0;
 /* blacken display after setting mode*/
 r.h.ah=0x12; r.h.bl=0x36; r.h.al=1; int86(0x10,&r,&r);

   eoue_get8x16ptr();
   eoue_get8x8ptr();
   eoue_visualpage=0x0000;
   eoue_pal(1);
   outportb (0x3D4,0x12); /* select  vertical display end register */
   outportb (0x3D5,152);  /* bits 0-7, bits 8,9 need not be changed. */
   outportb (0x3D4,0x11); /* center the image better */
   outportb (0x3D5,169);  /* End Vertical retrace reg.*/
   outportb (0x3D4,0x10);
   outportb (0x3D5,0xC7); /* Start vertical retrace reg. -32 rasters */

   eoue_clrscr();
   eoue_clrstat();
   eoue_pageflip();
   eoue_clrstat();

 /*re-enable display*/
 r.h.ah=0x12; r.h.bl=0x36; r.h.al=0; int86(0x10,&r,&r);
 return 0;
}

/* Change palette entry for one of the 4 colours (colcode 0-3).
*/
void eoue_modify_color (char colcode, char red, char g, char b) {
 union REGS r;
    r.h.ah=0x10; r.h.al=0x10;
    r.x.bx=eoue_palette_buf[colcode]; r.h.dh=red; r.h.ch=g; r.h.cl=b;
    int86(0x10,&r,&r);
}

/*
*/
void eoue_pal (unsigned char op) {
 unsigned char i;
 union REGS r; struct SREGS s;
 /* Store indexes of affected colors first, if not already done.
    Those are found in the palette registers. */
 if (op) {
   r.h.ah=0x10; r.h.al=0x09; r.x.dx=FP_OFF(eoue_palette_buf);
   s.es=FP_SEG(eoue_palette_buf);
   int86x(0x10,&r,&r,&s);
 } else eoue_clrscr();
 /* Store or retrieve corresponding color values.
    Subfunction 10h writes RGB to VGA, 15h returns RGB values. */
 for (i=0;i<17;i++) {
    r.h.ah=0x10; r.h.al=op?0x15:0x10; r.x.bx=eoue_palette_buf[i];
    if (!op) {
      r.h.dh=eoue_pal_colors[i][0];
      r.h.ch=eoue_pal_colors[i][1];
      r.h.cl=eoue_pal_colors[i][2];
      int86(0x10,&r,&r);
    }
    else {
      int86(0x10,&r,&r);
      eoue_pal_colors[i][0]=r.h.dh;
      eoue_pal_colors[i][1]=r.h.ch;
      eoue_pal_colors[i][2]=r.h.cl;
    }
 }
 if (op) {
     /* Now modify palette. */
     r.h.ah=0x10; r.h.al=0x10;
     r.x.bx=eoue_palette_buf[0]; r.h.dh=0; r.h.ch=0; r.h.cl=24;
     int86(0x10,&r,&r);
     r.x.bx=eoue_palette_buf[1]; r.h.dh=0; r.h.ch=0; r.h.cl=0;
     int86(0x10,&r,&r);
     r.x.bx=eoue_palette_buf[2]; r.h.dh=63; r.h.ch=63; r.h.cl=63;
     int86(0x10,&r,&r);
     r.x.bx=eoue_palette_buf[3]; r.h.dh=12; r.h.ch=12; r.h.cl=24;
     int86(0x10,&r,&r);
     r.x.bx=eoue_palette_buf[4]; r.h.dh=62; r.h.ch=48; r.h.cl=28;
     int86(0x10,&r,&r);
     r.x.bx=eoue_palette_buf[8]; r.h.dh=64; r.h.ch=12; r.h.cl=24;
     int86(0x10,&r,&r);

   /*load overscan register, too*/
   r.h.ah=0x10; r.h.al=1; r.h.bh=1; int86 (0x10,&r,&r);
 }
}

/* Set the entire screen region to zero.
*/
void eoue_clrscr (void) {
 unsigned sweep;
  outportb (0x3C4,2);  /*select map mask register*/
  outportb (0x3C5,0xF); /*plane 0, set to 1*/
  sweep=0;
  do *(eoue_vidmem+sweep)=0; while (2*0x7FD0!=sweep++);
}

/* Rubout a character. CHECK.
*/
void eoue_clr8 (char x, char y) {
  unsigned char i;
  unsigned voffset=80*16*y+eoue_visualpage+x;
  for(i=0;i<16;i++) *(eoue_vidmem+80*i+voffset)=0;
}

/* Rubout a character. CHECK.
*/
void eoue_clr16 (char x, char y) {
  unsigned char i;
  unsigned voffset=80*16*y+eoue_visualpage+x;
  for(i=0;i<16;i++) {
    *(eoue_vidmem+80*i+voffset)=0;
    *(eoue_vidmem+80*i+voffset+1)=0;
  }
}

/* Rubout a character from status line. CHECK.
*/
void eoue_clrstat () {
  unsigned char i,j;
  unsigned voffset=80*16*25+eoue_visualpage;
    /*Phase 'set'*/
  outportb(0x3C4,2); /*select map mask register*/
  outportb(0x3C5,3);
  for (j=0;j<80;j++) *(eoue_vidmem+voffset+j)=255;
  voffset+=80;
  for (j=0;j<80;j++)
   for(i=0;i<8;i++) *(eoue_vidmem+80*i+voffset+j)=255;
  /*Phase 'reset'*/
  outportb(0x3C4,2); /*select map mask register*/
  outportb(0x3C5,12);
  voffset=80*16*25+eoue_visualpage+80;
  for (j=0;j<80;j++)
   for(i=0;i<8;i++) *(eoue_vidmem+80*i+voffset+j)=0;
  outportb(0x3C4,2);   /*select map mask register*/
  outportb(0x3C5,0xF); /*default*/
}

/* Write VGA ROM font character to status line. CHECK.
*/
void eoue_prstat (char *ch, char x, char color)
{
  unsigned char i,j,l, *cbitmap;
  unsigned voffset=80*16*25+80+eoue_visualpage+x;
  /*phase 'set'*/
  outportb(0x3C4,2); /*select map mask register*/
  outportb(0x3C5,color);
  l=strlen(ch);
  for (j=0;j<l;j++) {
  cbitmap=eoue_font8x8ptr+8*ch[j];
   for(i=0;i<8;i++)
    *(eoue_vidmem+80*i+voffset+j) = TINV?~*(cbitmap+i):*(cbitmap+i);
  }
  /*phase 'reset'*/
  voffset=80*16*25+80+eoue_visualpage+x;
  outportb(0x3C4,2); /*select map mask register*/
  outportb(0x3C5,3);
  l=strlen(ch);
  for (j=0;j<l;j++) {
  cbitmap=eoue_font8x8ptr+8*ch[j];
   for(i=0;i<8;i++)
    *(eoue_vidmem+80*i+voffset+j) = TINV?*(cbitmap+i):~*(cbitmap+i);
  }
  outportb(0x3C5,0x0F); /*back to default*/
}




/* Write VGA ROM font character. CHECK.
*/
void eoue_put8 (unsigned char ch, char x, char y, char color)
{
  unsigned char i, *cbitmap=eoue_font8x16ptr+16*ch;
  unsigned voffset=80*16*y+eoue_visualpage+x;
  outportb(0x3C4,2); /*select map mask register*/
  outportb(0x3C5,color);
  for(i=0;i<16;i++)
   *(eoue_vidmem+80*i+voffset) = TINV?~*(cbitmap+i):*(cbitmap+i);
  outportb(0x3C5,0x0F); /*back to default*/
}

/* Write 16 bit wide JIS/KANA character.  CHECK.
*/
void eoue_put16 (unsigned ch, char x, char y, char color)
{
  unsigned char i, *cbitmap;
  unsigned voffset=80*16*y+eoue_visualpage+x;
  if (ch<2000) cbitmap=joyokanji_bitmap16x16+32*ch;
  else cbitmap=kana_bitmap16x16+32*(ch-2000);
  outportb(0x3C4,2); /*select map mask register*/
  outportb(0x3C5,color);
  for(i=0;i<16;i++) {
    *(eoue_vidmem+80*i+voffset)=TINV?*(cbitmap+i*2):~*(cbitmap+i*2);
    *(eoue_vidmem+80*i+voffset+1)=TINV?*(cbitmap+i*2+1):~*(cbitmap+i*2+1);
  }
  outportb(0x3C5,0x0F); /*back to default*/
}

/* Make working page visual page, and vice-versa. CHECK.
   If argument 25, copy whole screen through, otherwise
   only specified line 0-24.
*/
void eoue_pageflip (void) {
  eoue_flyback();
  outportb (0x3D4,0x0C); /* start address msb 409*80 */
  outportb (0x3D5,eoue_visualpage?0x7F:0);
  outportb (0x3D4,0x0D); /* lsb */
  outportb (0x3D5,eoue_visualpage?0xD0:0);
  eoue_visualpage=0x7FD0-eoue_visualpage; /*toggle*/
  asm {   /* Copy visual page through into working page */
   mov dx, 0x3CE; mov ax, 0x0105; out dx,ax
   mov cx,0x7FD0;
   mov di, eoue_visualpage; mov si,cx; sub si, di;
   push ds; mov ax,0xA000; mov ds, ax; mov es,ax
   cld
   rep movsb
   pop ds
   mov ax,0x0005; out dx,ax
  }
}

/* Scrolls visual page, result in working page.
*/
void eoue_scrollthru (unsigned char direction, unsigned char line) {
  unsigned sweep=direction?0x7FD0-25*80:25*80;
  unsigned src=eoue_visualpage, dst=0x7FD0-eoue_visualpage;
  outportb (0x3CE,5); /*select mode register, graphics controller*/
  outportb (0x3CF,1); /*write mode 1 read mode 0*/
  if (!direction)
    do *(eoue_vidmem+src+sweep-16*80)=*(eoue_vidmem+dst+sweep);
    while (line*16*80!=sweep++);
  else  do *(eoue_vidmem+src+sweep+16*80)=*(eoue_vidmem+dst+sweep);
    while (line*16*80!=sweep--);
  outportb (0x3CE,5); /*select mode register, graphics controller*/
  outportb (0x3CF,0); /*default r/w mode*/
}

/* Clear one line in working page. CHECK.
*/
void eoue_linedel (unsigned char line, unsigned char fromc) {
  static unsigned i,j,start;
  start=16*80*line+eoue_visualpage;
  for (i=0;i<16;i++)
    for (j=fromc;j<80;j++) *(eoue_vidmem+start+i*80+j)=0;
}

/* Scoll workpage line left from lpos, result in workpage line. CHECK.
*/
void eoue_SLL (char lin, char lpos, char displ) {
  unsigned pagebase=lin*80*16+eoue_visualpage,i,j;
  outportb (0x3CE,5); /*select mode register, graphics controller*/
  outportb (0x3CF,1); /*write mode 1 read mode 0*/
  for (i=0;i<16;i++) for (j=lpos;j<80-displ;j++)
   *(eoue_vidmem+pagebase+i*80+j) = *(eoue_vidmem+pagebase+i*80+j+displ);
  outportb (0x3CE,5); /*select mode register, graphics controller*/
  outportb (0x3CF,0); /*default r/w mode*/
}

/* Scoll workpage line right from lpos, result in workpage line. CHECK.
*/
void eoue_SLR (char lin, char lpos, char displ) {
  unsigned pagebase=lin*80*16+eoue_visualpage,i,j;
  outportb (0x3CE,5); /*select mode register, graphics controller*/
  outportb (0x3CF,1); /*write mode 1 read mode 0*/
  for (i=0;i<16;i++) for (j=79;j>lpos+displ;j--)
   *(eoue_vidmem+pagebase+i*80+j) = *(eoue_vidmem+pagebase+i*80+j-displ);
  outportb (0x3CE,5); /*select mode register, graphics controller*/
  outportb (0x3CF,0); /*default r/w mode*/
}


/* Draw string of ASCII characters
*/
void eoue_fixprint (char *s, char x, char y, char color) {
 unsigned char i,j;
  j=strlen(s);
  for (i=0;i<j;i++)
    eoue_put8 (s[i],x+i,y,color);
}

/* Draw string of two-byte Japanese characters.
*/
void eoue_jprint (unsigned *codestr, char x, char y, char color) {
  unsigned char i;
  if (codestr==NULL) return;
  for (i=1;i<codestr[0];i++) eoue_put16 (codestr[i],x+2*i,y,color);
}

/* Same as jprint, but vertically
*/
void eoue_tategaki (unsigned *codestr, char x, char y, char color) {
  unsigned char i;
  if (codestr==NULL) return;
  for (i=1;i<codestr[0];i++) eoue_put16 (codestr[i],x,y+i,color);
}

void showbitmap (char* map, char x0, char y0, char color, char w, char h) {
  char i,j;
  unsigned k=0;
  outportb(0x3C4,2); /*select map mask register*/
  outportb(0x3C5,color);
  for (i=y0;i<y0+h;i++)
    for (j=x0;j<x0+w;j++)
       *(eoue_vidmem+eoue_visualpage+i*80+j)=*(map+k++);
  outportb(0x3C5,15);
}
 
 

Downloads

The “editor” executable is LQNT.exe. You can run it in an MS DOS emulator such as dosbox-x. The way the editor works is actually a (tiny) bit original. You type text (for example “GAIRAIGO”) then a space, followed by an inbuilt command (such as “kana”), and then the editor does a contextual replacement or other action (transforms the roman letters into katakana).

Words in ALLCAPS get replaced by their katakana, words in “lowercase” become hiragana.

Check LQNT.c to see how inserting kanji works. It’s the istrcmp conditional that checks for an uppercase K, followed by a numeric code. I think you were supposed to enter K(JIS-page)-(CHARCODE). I must have ran out of time or something, because it would have been relatively easy and really cool to have it show a little kanji picker based on a kana word.

JIS (Japanese Industrial Standards) was the then-equivalent of Unicode, but for Japanese characters only. DOS or early Windows had absolutely no awareness yet of UTF-8 or anything like that. There were multiple competing encoding standards for kanji, it was a complete mess. You couldn’t just “write Japanese” on your PC in those days.

Package download