Skip to content

6502: Generate USTY chunk with parsed style information - #114

Open
sehugg wants to merge 6 commits into
Dialog-IF:mainfrom
sehugg:gen-usty
Open

sehugg wants to merge 6 commits into
Dialog-IF:mainfrom
sehugg:gen-usty

Conversation

@sehugg

@sehugg sehugg commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Been sitting on this awhile, so time to PR! Apologies for the huge commit.

This patch parses the LANG chunk and replaces it with a binary USTY chunk which is now mandatory for apple2, c64, aambox.

The parsing is more complex than frontend.c, it supports RGB colors and gives warnings on lots of things.
You can also override styles with -iftf-sys--* syntax.
For example: "-iftf-sys-c64-color: red"

It also adds a foreground text color feature to C64, activated by the above syntax or just "color: red".

There is also a generic warning subsystem sort of based on the Dialog compiler, which might be overkill (--help-all shows all warning options)

Aamshow will decode the currently-versioned USTY chunk for debugging purposes.

The USTY format is documented a few times in the comments, but I don't know if a small internal spec might be better (since it'll change with new features)

- parse LANG and issue warnings on improper styles
- rewrite LANG -> USTY for apple2, c64, aambox
- -iftf-sys-* platform overrides
- foreground text color (c64)
- warning options (see usage for flags)
@sehugg

sehugg commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

But first I have to figure out why the gosling test fails with blank lines but not on my Mac :^P

@dstelzer

Copy link
Copy Markdown
Contributor

This generally looks good to me! Just a few things:

  • I think it would be best to have USTY documented in a centralized place. The obvious place is the main Å-machine spec in docs/, even if it's not something compilers are expected to emit, but we could also make an "8-bit addendum" in docs/; Antora makes that fairly easy.
  • Since we're making a significant change to the spec, I think it's worth bumping the minor version here. "1.0.* 8-bit interpreters expect a LOOK chunk and 1.1.* 8-bit interpreters expect a USTY chunk" is easy to remember, and people who want to get the old CSS-processing code can look at the last 1.0.* release for it.
  • Conveniently, I'm about to bump the minor version anyway, to change the behavior of MUL_NUM. Might as well do this at the same time!
  • It looks like currently, a defined color property overrides the other bits on Commodore 64? That makes sense to me, but I want to make sure I'm documenting it correctly in the Dialog manual.
  • Does the C64 implementation distinguish between "inherit" and "initial" colors? That is, if I put an "inherit" span inside a "red" span, it should be red, but if I put an "initial" span inside a "red" span, it should be black. (Or blue if the whole thing was inside an italic div, etc.)
  • If we're allowing direct color specification on C64, it makes sense to allow background color specification too, via the SET_BODY opcode. Not essential, but would be nice.
  • What does the aambox option in aambundle output, exactly? Just a modified .aastory file to run through aambox?

@dstelzer

Copy link
Copy Markdown
Contributor

Oh, and one more thing: I think it's a bad idea to try to match hex color codes to the closest available fixed color. Better to make users explicitly acknowledge which color they want by name, which ensures they know about the limited palette available.

@sehugg

sehugg commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

I agree, RGB parsing is out for now. I added a spec and implemented the color: initial behavior.

I made room for SET_BODY background colors, coming in a later PR :)

Should I bump the minor version too?

@sehugg

sehugg commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

Not sure what's going on with the tests; the files are byte-identical on my Mac and Linux.

@dstelzer

Copy link
Copy Markdown
Contributor

Hmm. That test failure is what I would expect if something was going wrong with div margins…in particular, that would match the old behavior where SPC was incorrectly set to PAR instead of LINE when leaving a div.

@dstelzer

Copy link
Copy Markdown
Contributor

Otherwise, this all looks good! And yeah, I do think it's good to have this be version 1.1.0.

@dstelzer

Copy link
Copy Markdown
Contributor

The tests also pass on my local machine. What's going on?

@dstelzer

Copy link
Copy Markdown
Contributor

Oho, nope, I was able to reproduce the test failure, and I see the cause. The USTY chunk is not capturing top and bottom margins.

Here's the LOOK chunk of gosling.aastory:

=== LOOK ======================================================================
0000: margin-bottom: 2em
0001: font-weight: bold
      color: red!important
0002: font-family: monospace
0003: width: 100%
      text-align: center
      font-style: italic
0004: height: 1em
      text-align: left
0005: text-align: left
      border: 1px solid rgb(128, 128, 100)
      background-color: rgba(128, 128, 100, 0.33)
      padding: 0.125em
      padding-left: 0.67em
      border-radius: 12px
      margin-top: 1em
0006: height: 2em
      text-align: left
0007: font-style: italic
      font-style: normal!important
      font-size: 0.9em
      font-family: Helvetica, sans-serif
      margin-top: 1em
      margin-bottom: 1em
      background-color: rgba(0, 0, 255, 0.1)
      border: 1px solid blue
      margin-left: 1em
      margin-right: 1em
      padding: 0.5em
0008: margin-bottom: 1em
      font-style: italic
0009: float: right
      width: 40%
      text-align: right
000a: font-weight: bold
000b: margin-top: 0.25em
      margin-bottom: 0.25em
      padding-left: 2em
      text-indent:-2em
000c: font-size: smaller
      text-align: center
000d: font-style: italic
      font-style: normal!important
      margin-top: 1em
      margin-bottom: 1em
      background-color: rgba(255, 195, 0, 0.1)
      border: 1px solid rgb(255, 195, 0)
      margin-left: 1em
      margin-right: 1em
      padding: 0.5em
000e: font-style: italic
      font-style: normal!important
      margin-top: 1em
      margin-bottom: 1em
      background-color: rgba(128, 0, 32, 0.1)
      border: 1px solid rgb(128, 0, 32)
      margin-left: 1em
      margin-right: 1em
      padding: 0.5em
      --lightgray: #FF7652
      --black: #771900
000f: margin-top:.3em
      font-size: 1.2em
0010: font-style: italic
      margin-top: 1em
      margin-bottom: 1em
      background-color: rgba(0, 128, 0, 0.1)
      border: 1px solid rgb(0, 128, 0)
      margin-left: 1em
      margin-right: 1em
      padding: 0.5em
0011: font-weight: bold
      font-size: 1.4em
      margin-top: 1em
0012: padding-left: 2.5em
      text-indent:-2em

And here's the USTY chunk of gosling.ustory:

=== USTY ======================================================================
Tag: 00 (aambox, format version 0)
nclass: 20  nxsty: 0
Offsets: rec 8  xsty 168  (81 words resident)

Class records (8 bytes each):
  0000: all defaults
  0001: all defaults
  0002: all defaults
  0003: width=100%
  0004: all defaults
  0005: all defaults
  0006: all defaults
  0007: all defaults
  0008: all defaults
  0009: width=40% float=right
  000a: all defaults
  000b: all defaults
  000c: all defaults
  000d: all defaults
  000e: all defaults
  000f: all defaults
  0010: all defaults
  0011: all defaults
  0012: all defaults
  0013: all defaults

@dstelzer

Copy link
Copy Markdown
Contributor

If style warnings are not silenced:

Warning: style class class 0: Ignoring margin-bottom: unsupported value "2em".
(Use --no-warn-style to disable style warnings.)
Warning: style class class 1: color is not supported on aambox and was ignored.
Warning: style class class 4: Ignoring height: unsupported value "1em".
Warning: style class class 5: Ignoring margin-top: unsupported value "1em".
Warning: style class class 6: Ignoring height: unsupported value "2em".
Warning: style class class 7: Ignoring margin-top: unsupported value "1em".
Warning: style class class 7: Ignoring margin-bottom: unsupported value "1em".
Warning: style class class 8: Ignoring margin-bottom: unsupported value "1em".
Warning: style class class 11: Ignoring margin-top: unsupported value "0.25em".
Warning: style class class 11: Ignoring margin-bottom: unsupported value "0.25em".
Warning: style class class 13: Ignoring margin-top: unsupported value "1em".
Warning: style class class 13: Ignoring margin-bottom: unsupported value "1em".
Warning: style class class 14: Ignoring margin-top: unsupported value "1em".
Warning: style class class 14: Ignoring margin-bottom: unsupported value "1em".
Warning: style class class 15: Ignoring margin-top: unsupported value ".3em".
Warning: style class class 16: Ignoring margin-top: unsupported value "1em".
Warning: style class class 16: Ignoring margin-bottom: unsupported value "1em".
Warning: style class class 17: Ignoring margin-top: unsupported value "1em".

@dstelzer

Copy link
Copy Markdown
Contributor

And while I poke through gen_usty.c for this:

if(!strcmp(key, "-iftf-text-decoration")) {

I believe it should be -iftf-reverse-video. Though I'm not sure why this CSS property is checked separately from all the others?

@sehugg

sehugg commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

That was probably left over from before the -iftf-reverse-video decision was made. It was its own branch because it used to interfere with the platform-specific overrides, but I'll move it.

So I'll pull out text-decoration parsing completely (since Dialog already warns) but we probably still want -iftf-sys-apple2-reverse-video.

The margin stuff is weird and I suspect an esoteric sscanf() library version thing, but I'll have to test.

@dstelzer

Copy link
Copy Markdown
Contributor

Running this code:

	success = scan_length("1em", &val, unit);
	printf("%s: %d %s (%d)\n", "1em", val, unit, success);
	
	success = scan_length("5ch", &val, unit);
	printf("%s: %d %s (%d)\n", "5cm", val, unit, success);
	
	success = scan_length("0.1em", &val, unit);
	printf("%s: %d %s (%d)\n", "0.1em", val, unit, success);
	
	success = scan_length("0", &val, unit);
	printf("%s: %d %s (%d)\n", "0", val, unit, success);
	
	success = scan_length("3 en", &val, unit);
	printf("%s: %d %s (%d)\n", "3 en", val, unit, success);

Produces this:

1em: 1 m (2)
5cm: 5 ch (2)
0.1em: 0 m (2)
0: 0  (1)
3 en: 3 en (2)

It looks like the %f specifier in sscanf accepts 1e as meaning 1—that is, it's reading the e as an exponential-notation marker, even with no other number after it.

@dstelzer

Copy link
Copy Markdown
Contributor

This behavior varies between C libraries. It looks like if the current code works, it's actually a bug in sscanf according to the spec (from https://en.cppreference.com/c/io/fscanf):

When parsing an incomplete floating-point value that ends in the exponent with no digits, such as parsing "100er" with the conversion specifier %f, the sequence "100e" (the longest prefix of a possibly valid floating-point number) is consumed, resulting in a matching error (the consumed sequence cannot be converted to a floating-point number), with "r" remaining. Some existing implementations do not follow this rule and roll back to consume only "100", leaving "er", e.g., glibc bug 1765.

Unfortunately it looks like the only real way to fix it is to do our own number parsing. Maybe try to match both %d.%d %s and %d %s and take the first one that matches.

@dstelzer

Copy link
Copy Markdown
Contributor

The following code seems to work:

static int scan_length(const char *value, int *val, char *unit) {
	int frac, n;

	while(*value == ' ' || *value == '\t') value++;
	if((*value < '0' || *value > '9') && *value != '.') return 0;
	unit[0] = 0;
	n = sscanf(value, "%d.%d %15s", val, &frac, unit);
	if(n > 1) return n-1; // Found both an integer part and a decimal part (with or without a unit); return 2 if we found a unit and 1 if we didn't
	n = sscanf(value, "%d %15s", val, unit); // Otherwise, try as just an int
	if(n < 1) return 0; // Didn't work
	return n;
}

With this code, values like .33ch will not match at all, but since the default value is 0, that's not currently a problem. (If it is, we can add another check for it.)

@sehugg

sehugg commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Yep, that's it. Who knew a 45-year old function could still be clunky? ;)

@dstelzer

Copy link
Copy Markdown
Contributor

This is looking good overall now! All that's left are a couple minor quibbles. Some of these could also go in another PR.

  • Is there a reason to restrict to 16 targets and 16 format versions when we could spend another byte to get 256 targets and 256 format versions? I don't think we'll ever hit that limit, but since this is the one part of the spec that's very hard to change later, it seems worth spending one extra byte for extensibility.
  • Am I understanding right that a LOOK chunk with no style classes will simply not be emitted as a USTY at all?
  • The current Å-machine spec allows for up to $3FFF style classes per file. Is restricting that to 255 for 8-bit platforms a deliberate change? If so, does aambundle give a sensible error message when that bound is exceeded?
  • Some chicanery with softlinks needs to happen to get the new .adoc page into Antora, but I can handle that in another PR once this one is merged.
  • We may want to use SHORT instead of WORD to refer to 16-bit values in the 8-bit spec, just because the frequent reference to the 8-bit machines may confuse people on how big a WORD is. This isn't very important, though, and I can also handle it in another PR.
  • Will the C64 currently obey color: lines, or only -iftf-sys-c64-color:? (This only matters because I want to document it properly in the Dialog manual, so I want to make sure I'm up to date with the current version.)

None of these are too important, though; no need to change them if you disagree.

@sehugg

sehugg commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Maybe we just lose the format nibble in the version byte? Nothing reads it except aamshow, and since the .ustory files are neither portable nor backward-compatible nothing really needs to check it. It can always come back in a future header revision if needed, i.e. if aamshow can't parse without it.

There is always at least one style, thus USTY is always present, the sty_emitted variable is just a check to make sure the LOOK chunk was found.

More than 255 styles in USTY is a hard error in aambundle. The 6502 is much happier this way.

Agreed SHORT is better.

C64 will obey color: if there is no -iftf attribute, but warn and ignore if it's RGB.

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