Skip to content

LowWire: HighWire for the rest of the ST family - #7

Open
neilrackett wants to merge 23 commits into
freemint:masterfrom
neilrackett:lowwire
Open

LowWire: HighWire for the rest of the ST family#7
neilrackett wants to merge 23 commits into
freemint:masterfrom
neilrackett:lowwire

Conversation

@neilrackett

Copy link
Copy Markdown
highwire-frogfind-0 highwire-frogfind-1 highwire-falcon-0
highwire-falcon-1 highwire-st-0 highwire-st-1
highwire-st-2 highwire-tt-0 highwire-tt-1

I'm working on a project that enables stock STs to get online using WiFi, and needed a fast native browser to test it with. HighWire seemed like the perfect choice, but it needed SpeedoGDOS (kept crashing), NVDI (needs key/serial) or fVDI (painfully slow), and had a UI that targeted high resolution TT/Falcon displays.

This PR, working title "LowWire", makes HighWire run, and run well, on the plain ROM VDI at every ST, TT and Falcon resolution (most of which I've screenshotted above), while leaving the GDOS path untouched.

What's new

  • romvdi.c compatibility layer — every extended VDI call HighWire makes is wrapped: straight pass-through with GDOS, GEM 1.0 fallbacks without (16-bit text narrowed for v_gtext, vqt_xfntinfo synthesised, vq_scrninfo answered ourselves — including the TT low pen map and Falcon truecolour).
  • ST medium looks good now — the four-colour palette becomes a grey ramp and new rasterizers dither images into all four levels; the palette (all modes, up to 256 pens) is restored on exit.
  • Correct aspect ratios — images are squashed along the long axis on non-square-pixel screens (ST medium, TT low), which also halves their decode time; see IMG_ASPECT config option.
  • Readable text without GDOS — bitmap fonts get proper line leading (LINE_SPACING), algorithmic bold/italic widths are measured correctly, and a vqt_width misuse that mashed wrapped lines together is fixed.
  • Fits on 320px screens — menu titles tighten to their text widths and History (whose pulldown is wider than the screen) menu is hidden; it's available from every window anyway.
  • Works out of the box — a CACHE folder is created beside the program when no CACHEDIR is configured (without it, remote images are silently never displayed), and runtime diagnostics no longer printf over the GEM screen.
  • Bugs fixed along the way — edit fields collapsing to a few pixels (a measurement of the wrong buffer that GDOS masked for 25 years), a crash on <input type="text" disabled>, and incomplete rasterizer fallbacks.
  • An HTML test suite under html/test/ (linked from sample.htm) covers all supported elements and CSS — it was what enabled me to catch two of the bugs above.

New options are documented in example.cfg (GREY_PALETTE, IMG_ASPECT, LINE_SPACING), all default-on with sensible values and all switchable off. With NVDI/SpeedoGDOS present, behaviour is unchanged by design.

Tested: Hatari (TOS 2.06 and EmuTOS) across ST low/medium/high, TT low and Medium, and Falcon 40/80 columns with/without interlacing, plus online with a real Mega STE via STinG.

There's more information in the commit messages and I'd be happy to split, squash, or adjust as needed to suit project conventions.

The info bar and close gadget asked for the VDI system font by point size
(11 or 12pt). The system font is not scalable - it is a fixed 8x8 bitmap in
the ST low/medium resolutions and 8x16 in ST high and above - so a point
size selects whichever face is nearest, landing on 8x16 in the ST
resolutions and making the interface far too large.

Ask by pixel height instead, using the value graf_handle() reported for the
AES system font, so our text matches the desktop in every resolution. The
three copies of the same heuristic are folded into one helper.
HighWire required a Speedo/FSM capable GDOS and called exit() when it found
none, so an ST or Mega ST(E) with only the ROM VDI could not run it at all.

The requirement was real rather than cosmetic: vqt_xfntinfo() is the only
path in font_base() that ever builds a FONTBASE, and on the ROM VDI it fails
for every id including the system font. font_byType()'s fallback chain ends
at font_base(1,0), so that NULL was then dereferenced and the machine
bombed. The gate was guarding a crash.

Add romvdi.c, wrapping every extended VDI call HighWire makes. With a GDOS
present each wrapper is a straight pass-through and behaviour is unchanged.
Without one:

  vqt_xfntinfo    synthesised, after checking vst_font() can select the id
                  at all - refusing unknown ids keeps the existing fallback
                  chain working instead of drawing with a stale font
  vst_arbpt       -> vst_point
  vst_setsize32   ignored (no condensed text)
  vst_map_mode    remembered, so text conversion knows what it is holding
  vqt_advance     -> vqt_width
  v_ftext         -> v_gtext
  v_ftext16/16n   narrowed to 8-bit via unicode_to_8bit(), then v_gtext
  vqt_f_extent16n narrowed likewise, then vqt_extent
  vst_scratch, vst_kern, vst_(un)load_fonts  ignored

The result is single-face, single-size text with no bold, italic or
scaling - the same output HighWire already produces when a GDOS is present
but has no fonts loaded, which is what an ST can actually manage.
Three problems visible when running in ST low/medium resolution.

vq_scrninfo() arrived with NVDI and the plain ROM VDI does not answer it,
so the reply array held unrelated data that decoded as out[0]=4 and 31 bits
per pixel. That raised "Unrecognized screen format!" on every ST. Sanity
check the reply and describe the screen ourselves when it is not usable: an
ST/STE screen is always interleaved bitplanes, one plane per bit of depth,
with the VDI's own colour index to pixel value mapping supplied alongside
since it is not the identity.

The safety net that should have caught this could never fire. It tested
(!raster_cmap), but the statics are initialised to invalid_raster rather
than NULL, so it was always false - which is why an unrecognised screen was
followed by "cnvpal_color() undefined function called" instead of falling
back to the standard bitmap format. Test against the stub instead.

The status line looked stretched and the URL field kept the 8x16 face
because vst_height() takes the CHARACTER height, not the cell height: the
8x8 face is only 6 pixels tall, so asking for 8 overshoots onto the next
face or has the VDI scale one. Walk the request down until the cell handed
back fits the AES's. The URL field hardcoded vst_point(12) and a 16 pixel
caret; both now follow the resolution.
… notice

Three follow-ups from testing in ST low and medium resolution.

ST medium gives four colours and the VDI's defaults are white, black, red
and green. remap_color() picks the nearest by RGB distance, so a page with a
dark background and dark text sends both to black and the text vanishes.
Recolour the two chromatic entries to greys, turning the palette into a
luminance ramp - white, light grey, dark grey, black - so page colours sort
by brightness and stay legible either way round. Indices 0 and 1 are left
alone so G_WHITE and G_BLACK still mean what the interface expects, and the
originals are put back at exit since the ST palette is global and leaving
the desktop grey is not ours to do. Two planes only: mono needs no help and
sixteen colours has the range to cope. Off via GREY_PALETTE = 0.

The info bar and close gadget now ask for the ROM's small 6x6 face in the ST
resolutions - the one the desktop labels icons with - since 8x8 leaves the
bar too cramped to show much of a URL. sys_font_setup() takes the target
cell height for this. The URL field stays on 8x8 deliberately: its caret
steps in whole 8 pixel cells.

Also drop the startup notice printed when no FSM GDOS is found. It painted
raw TOS text over the screen on every launch and said nothing the user could
act on.
ignore_colours was defined as (planes < 2), which is only ever true of a
monochrome screen. ST medium resolution has two planes, so it took the full
colour path - and that path paints page backgrounds and 3D edges with
G_LWHITE and G_LBLACK, VDI pens 8 and 9, which need sixteen colours to
exist. On a four pen screen they are out of range, and everything came out
black on black including HighWire's own chrome.

The light and dark shades are what the test is really about, so the
condition is the number of pens rather than whether the screen is mono:
(planes < 4). Monochrome is unaffected, ST medium now takes the same
black-on-white path, and sixteen colours and above are unchanged.

The grey palette from the previous commit is kept: with the interface on the
mono path it no longer affects the chrome, but it still gives images four
grey levels to dither into rather than white, black, red and green.
Without a CACHEDIR the disk cache never engages, and remote images are
downloaded then thrown away: the image decoders read from files, so every
image on an http page came out as a placeholder while local pages worked.

Default to CACHE in the program's folder, created on first run.  An
explicit CACHEDIR still wins, and if the default cannot be written (say a
write protected drive) cache_build() backs the cache out cleanly as
before.  The path building shares a new prog_dir() helper with
open_default() instead of adding a third copy of the Dgetdrv/Dgetpath
dance.
The URL edit field, the info bar and the status line all drew their text
top aligned inside boxes sized for other things (toolbar icons, AES
widgets), which left the small ST faces floating high with a gap below.
The font setup helpers now report the cell height they actually selected
and every draw site centres on it via one VCENTER macro; the edit field
caret follows its text automatically.

sys_font_setup()'s vst_height() walk-down also ran up to max_cell VDI
traps on every info bar, status and toolbar redraw for an answer that is
fixed for the session - the result is now remembered per cell size (only
two ever occur), and ui_font_setup() no longer selects the system font
twice.
The vq_scrninfo() compensation moves out of rasterizer() into romvdi.c
with the other ROM VDI fallbacks, as hw_vq_scrninfo().  Without a GDOS
the doomed call is no longer issued at all, and the synthesised reply now
covers more screens than the ST modes it was written for: the 256 colour
pen map (pen 1 is hardware pixel 255, not 1) so TT low renders images in
the right colours, and a packed pixel description for more than 8 planes
so Falcon style truecolour lands on the existing 16/24/32 bit rasterizers
instead of an unusable fallback.

In the narrowing layer, the map mode test is hoisted out of narrow()'s
per character loop, "until NUL" becomes an explicit num < 0 convention
instead of borrowing the buffer size, and hw_vqt_advance() converts its
character through a new narrow_char() rather than truncating inline.
has_fsm_gdos now starts FALSE so a missed romvdi_init() fails towards the
safe fallback.
The 2 plane rasterizers were black and white dithers: they write the same
word to both planes, so images could only produce hardware pixels 0 and 3
and the grey palette never reached them.  New G2 variants of the three
(colour mapped, greyscale, truecolour) do 4 level error diffusion and
write the two plane words separately - with the grey ramp installed the
hardware pixel values 0..3 run white, light, dark, black (VDI pens
0,2,3,1), so the chosen level is the pixel value.  They are selected when
color_GreyRamp is set, which color.c now exports; without the ramp the
plain black and white dither remains, which is the right output for a
white/black/red/green palette.

The standard format last resort gets finished too: it left raster_gray
and raster_true pointing at the fatal "undefined function" stubs and
raster_stnd() read the low bytes of the palette entry where cnvpal_4_8()
keeps the RGB value rather than the device pixel in the top byte.  New
gscale_stnd()/dither_stnd() writers cover grey and truecolour images
there, and setup() allocates the dither buffer for that path on deep
screens as well.
edit_init() fills the text buffer with cols non-breaking spaces and
measures them to size the field - but it measured word->item, which for
a fresh word still points at the shared empty word, not at the buffer
just filled.  With a Speedo GDOS vqt_f_extent16n() takes an explicit
count, so it walked cols cells of adjacent static data and returned
garbage that happened to look like a plausible width.  The ROM VDI path
narrows to a NUL terminated string, saw the empty word's terminator
immediately and measured nothing: every text input collapsed to its few
pixels of padding.

Measure current->text, the cells that were actually written.  This also
gives GDOS systems a real measurement, and makes CursorH (taken from the
same extent call) meaningful on the ROM path.
On ST low the menu titles overflow 320 pixels: History wrapped around
onto the left of the bar, and opening its pulldown - which at 42 chars
by 23 rows is wider than the whole screen - overflowed the AES's menu
save area and took the machine down with two bombs.

menu_fit() reworks the tree in memory after rsrc_load(), decided purely
by measuring against the screen width.  A first pass tightens every
title to its exact text width and realigns the pulldowns (clamped to
the right screen edge); if the bar still does not fit, a second pass
hides the History title and its pulldown via OF_HIDETREE - every window
already offers the history from its own popup.  On 640 pixel screens
and up the bar fits as it is and the tree is left untouched.
Twelve pages exercising what the parser and renderer actually support,
grouped by category: text and inline styles, FONT/BASEFONT, lists,
tables, forms (inputs at several sizes - the collapsed edit field bug
was found this way), image scaling and floats, links and anchors,
colours, CSS (selectors, font properties, borders, with the recognised
but unimplemented properties labelled as such), character entities and
a frameset.  All filenames are 8.3 safe and the folder is self
contained, so it works from any drive.

html/sample/sample.htm gets a section linking the suite alongside the
existing test pages.
ST medium's pixels are twice as tall as wide, so every image rendered
stretched to double height; TT low has the same problem sideways.  Use
the pixel size the VDI reports for the workstation to squash the long
axis of every image by the real ratio - the height on ST medium, the
width on TT low - so pictures keep their real world proportions.  Near
square modes are untouched, and since the correction always shrinks, it
also roughly halves the decode and dither work per image.

Behind a new IMG_ASPECT config option, default on; set 0 for the old
1:1 rendering.
color_mapsetup() rewrites over 200 pens for the fixed colour cube, but
only the two pens recoloured by the ST medium grey ramp were saved and
put back at exit - quitting HighWire on a 256 colour screen with
FIXED_CMAP left the desktop repainted.  save_colors() now snapshots the
whole palette before anything recolours it and color_restore() puts
every pen back, replacing the grey pen special case with the general
mechanism.
- menu_fit() finds the History title by its object index (M_HISTORY, as
  AEI.c already does) instead of matching the display string, and reads
  ob_spec through the free_string member instead of a cast.
- The exit palette snapshot becomes a static 256 entry array: res_colors
  is bounded, so the malloc, its failure path and the exit-time free were
  machinery for nothing.  The grey ramp macros went with it - the general
  restore removed their second use site.
- img_scale() folds the mirrored aspect arithmetic into one squash()
  helper and only recomputes the rasterizer scale steps when a rasterizing
  caller (info != NULL) needs them - the layout paths were paying a long
  division per image per reflow for a discarded value.
- raster_stnd() reads the device pixel with a shift instead of a byte
  alias; gscale_stnd()/dither_stnd() hoist the pixel_val[] lookups out of
  their per pixel loops, where the stores stop gcc doing it.
- The toolbar caret steps by vdi_dev.wchar instead of a literal 8, the
  last hardcoded font width.
- GREY_PALETTE is documented in example.cfg next to IMG_ASPECT.
new_image() set the placeholder box straight from the width/height
attributes, so on ST medium the box appeared at twice the height the
image would eventually render at (and the reverse on TT low), with the
layout jumping when the data arrived.  The aspect correction moves into
a shared aspect_adjust() applied to the placeholder as well; since the
display size doubles as img_scale()'s scaling target, img_scale now
re-derives it from the absolute attributes first, which also stops
repeated calls on the reload paths squashing an already corrected
height a second time.

The screen shape test and its division collapse into a factor decided
once per session, so the per-image cost drops to a multiply and shift.
Runtime errors were printf'd/puts'd to the console, which on plain TOS
is the screen: a stale cache.idx entry drew "location_rdIdx(): host
entry not found" straight over the menu bar on real hardware.

errprintf() now stays quiet unless LOGGING is enabled (matching
logprintf), and every unguarded console diagnostic on a runtime path -
some sixty sites across the loader, parser, cache index, containers,
image decoders, encodings, fonts, network and window modules - goes
through it.  #ifdef DEBUG output is untouched.

Conditions the user must actually see get proper alerts instead of
silence: the Save As failure paths use hwUi_warn like the download
manager already did, and a missing STiK/STinG layer is reported once,
on the first attempt to go online.
hw_vqt_advance() took vqt_width()'s return value as the advance, but
that return is a status - the character code, or -1 - while the cell
width arrives in the first output argument, which the wrapper then
zeroed.  Every ROM VDI font therefore reported SpaceWidth as 33 pixels
(32 being the code for space, plus one), and since the leading space of
each wrapped line is skipped at draw time and compensated by exactly
SpaceWidth, every wrapped line's first word landed some 27 pixels into
its neighbour.  That was the text mashing visible on every GDOS-less
medium and high res screenshot; it also inflated list hanging indents
(SpaceWidth * 5 = 165 pixels) and justification gaps.
input_disable() dereferenced input->Next before checking it, and then
dereferenced the result of a ternary that had just decided it might be
NULL.  A plain <input type="text" disabled> always has a NULL Next at
the moment its attribute is parsed, so any page using one took the
machine down with two bombs.  Found by the html/test suite's forms
page, which is apparently the first document ever to try it.
With a Speedo GDOS, bold and italic are real fonts and extents match the
drawing.  On the ROM VDI they are vst_effects(): thickening widens every
glyph and skewing leans it sideways, and neither shows up in vqt_extent,
so every emphasised word measured narrower than it painted and later
words on the line were placed into its ink.

vqt_fontinfo() reports exactly the missing amounts in its effects array,
which the font cache already called but discarded; keep them per font
and add chars * EffWidth + EffSkew when words are measured.  Zero with a
GDOS present, so scalable-font rendering is unchanged.
The system font's bitmap cells use every row - ascenders touch the top,
descenders the bottom - so consecutive lines of body text sat pixel to
pixel on the ROM VDI.  Scalable fonts never showed this because their
cell metrics carry leading of their own.

Each bitmap font now gets a Leading amount, applied where lines are
stacked so the font metrics themselves stay honest (folding it into
Descend shrank checkboxes and radios, which reconstruct the ascender by
subtraction).  A new LINE_SPACING option controls it: -1 (the default)
picks an eighth of the cell height plus one pixel - 2 for the 8 pixel
faces, 3 for the 16 pixel one - 0 restores the packed look, a positive
number is used as pixels.
@neilrackett
neilrackett deleted the lowwire branch August 11, 2026 10:53
@neilrackett
neilrackett restored the lowwire branch August 11, 2026 10:54
@neilrackett neilrackett reopened this Aug 11, 2026
@mikrosk

mikrosk commented Aug 14, 2026

Copy link
Copy Markdown
Member

Hi, even though this is not an active project, thanks for your PR.

I'm wondering, is this still "backward compatible" to the original HighWire? I.e. can it do everything what HighWire could? If so, I'm happy to merge it (or even transfer ownership to you) but I don't find renaming to "LowWire" necessary then.

If you cut some features out, then your rename, separate fork and maintenance makes more sense and I'm happy just to edit README.md to point to your project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants