support drawing commands in quadwriter
#7
Open
opened 5 years ago by mbutterick
·
21 comments
Loading…
Reference in New Issue
There is no content yet.
Delete Branch '%!s(<nil>)'
Deleting a branch is permanent. It CANNOT be undone. Continue?
https://github.com/mbutterick/quad/issues/35#issuecomment-501707731
https://github.com/mbutterick/quad/issues/35#issuecomment-501733559
@otherjoel
I’m starting to work on this quad drawing idea. Though rather than a dialect separate from
#lang quadwriter
, I’d like to see if it can bejammedintegrated into the existingquadwriter
semantics. Basically my idea is to allow you to drop little drawing quads anywhere into the markup, say:"Hello world" (q ((draw "line") (x1 "10") (y1 "10") (x2 "100") (y2 "200"))) "Goodbye"
Quads with a
draw
attribute won’t have any effect on the visual layout of the surrounding quads, and they won’t be drawn inline. Instead, they’ll be treated as drawing commands that are rendered on whatever page they end up on, relative to the origin of that page. (In essence, it’s similar to whatposition = "absolute"
does in HTML).Of course, nothing would stop you from making a
quadwriter
document consisting only ofdraw
quads and page breaks, so the result would be a markup-style drawing (it’s not literally SVG, but we can think of it as SVG-ish, and I would adopt as much SVG-style notation as possible).That’s fine for shapes and paths. Text gets more complicated, however, because though it’s easy enough to draw text one slug at a time:
(q ((draw "text")(font-italic "true")(x "20")(y "20")(text "Hello from 20,20"))
... nobody really wants to work with text this way, because
It doesn’t have a notion of baseline alignment
it doesn’t have a notion of advance width (that is, being able to set two pieces of text with different formatting in sequence without manually calculating widths)
it doesn’t have a notion of line wrapping (e.g., what is known in layout programs as a “text box”)
My question to you is — what would be the most natural notation for expressing these ideas (for instance, as you would want to express them in
#lang bookcover
)? It feels like we’d want to be able to take normally marked-upquadwriter
text and wrap it in a specialdraw
quad with a few extra attributes that controls this extra behavior.The most natural thing would be able to describe a box, with sizing info in addition to placement. The box would also, ideally, either inherit text formatting from the page (font sizing, line heights, justification) or specify its own. The text would then wrap to fit the box. (Maybe a miniature page is a good way to think of it?)
As to overflows (by which I suppose we just mean vertical overflows) it would be nice to have the option to either clip off anything that would have been drawn below the bottom of the box, or to let the box expand vertically to fit its contents, or to preserve the box’s size but allow vertical overflow. (There wouldn’t be any difference between the last two except when, e.g., setting a box’s background or border, in which case the difference is visually obvious.)
Perhaps eventually you’d be able to arbitrarily chain particular boxes together so that text overflowing past the bottom of one box would flow into the top of the next.
If quadwriter is going to have SVG-like primitives for vector drawing operations, this kind of brings me back around to my earlier request for having a
pict->qdraw
type of functionality.I’m not wedded to
pict
s per se, but it would be killer to have a functional way of first building up smaller vector drawing operations into a complex graphic; and then, once you have it ready to go, plonking it down on the page where you want it. This is the model I’m used to withpict
+racket/draw
. I’m kind of a noob when it comes to graphics in general, doubly so with Racket implementations of graphical operations. But I imagine it would be nice for users to leverage Racket‘s existing libraries and mental models, and nice for you not to have to reinvent all of the functional scaffolding those libraries provide.I note that you can render a
pict
onto apostscript-dc%
orsvg-dc%
, maybe it would make sense to provide something like aqdraw-dc%
that can accept the same operations and produces a(q ((draw …))
expression? Or perhaps convert apostscript-dc%
orsvg-dc%
directly into a q-expression? (Though I will say, converting PostScript to q-expressions seems like undesirable round-tripping when it’ll just end up as PDF/PostScript again anyway)I was thinking of this again when I saw @spdegabrielle post this metapict example the other day. Aside from my own uses for quadwriter/draw (
bookcover
etc), I have in mind that some brave soul other than ourselves ought someday to be able to come along and implement an equation typesetting DSL that produces Q-expressions. This becomes much more likely if they can do the drawing in-memory using existing libraries and have the results somehow “translated” to quad at the end. Also note, something other than absolute positioning would be needed for something like this.I agree.
That sounds doable except for “expand vertically”, which requires more thought (this would send
quad
more in the direction of the HTML/CSS box model, which I’m not sure is wise)That is, in essence, what happens now with the main text flow, though I know what you mean.
I’m wary of making the drawing operations too Racket-centric, inasmuch as the whole idea of Q-expressions is to have an API that is addressable by any external tool (not merely Racket). (See also.) It seems wiser to just allow SVGs to be embedded in PDF.
After an unforeseen delay I’m returning to this issue. I’m still thinking that supporting freeform drawing from within
quadwriter
is the best plan. For instance, I’ve been considering how to implement headers and footers. It seems like they could just be handled as drawing commands (plus some extra magic to get them to repeat on certain pages, but I’ve sorted that out already).This makes sense, you could give the headers/footers an absolute position that would always be outside the margins of the main content.
For header/footer elements, it would be very useful to be able specify the x-coordinate of
draw
quads relative to the “inside” or “outside” edge (to account for layout on alternating pages). Also there would often be a need to auto-center the quad on a particular x-coordinate given relative to the inside or outside edge.So on a letter-size page with, say, a 1" outside margin and 0.5" inner margin, you could center a footer under the text block something like this:
I see what you mean, though I’m learning to be leery of splitting up an attribute like
x
into peripheral attributes likecenter-x
,x-offset
,x-relative-to
that essentially computex
. (Attributes propagate downwards, so one has to be alert to the possibility that, say, a subquad has anx
attribute that contradicts these other values.)How far could we get by allowing small computations in the value of the attribute? So instead of:
We might have:
I’m just realizing, a problem with both of these proposals is that they don’t fully describe a solution to the alternating-page layout problem, which is what I’m trying to address. For example,
page-margin-inner - 288
is actually going to come out to the same value on every page, right? Or might the-
operator do different things on verso/recto pages? (seems dubious)Yes I see. I agree your notation is an improvement in this regard, and it’s easier on the eyes as well.
Also, though: you could just require that most of these calculations be handled by whatever layer is generating the q-expression. All I care about at the moment is that the draw-quad have enough intelligence to position itself correctly when it matters if it lands on a recto or verso page, which is something you can’t know in advance.
Note that my example was attempting to describe how you’d automatically center a draw-quad whose width depends on its contents. But I can see how it would be simpler and better to require the width attribute, and just specify that it be wide enough to handle the biggest thing you might put there.
Perhaps the
x
attribute could accept afrom spine
directive, something likeOn a recto page, the resulting x coord would simply be 288. On a verso page, 288 (and I guess, the quad’s
width
value, if we’re adamant thatx
always refer to the northwest corner) would be subtracted from the value ofpage-width
.My idea so far is to handle alternating pages by specifying a
page-repeat
attribute (which seems like a more generic expression of the idea) rather than packing verso/recto semantics inside of certain measurement values (which seems more specific, though I am open to persuasion).So we might handle the problem like so:
The surrounding quad defines the common attributes (italic, width, y position, centered, etc.) And then the two drawing quads inherit these attributes, but change the
x
position depending on whether it’s a left page or right page.(Of course I’m ignoring how the text string knows it’s page
123 of 400
, but that’s a separate problem.)Yes, that seems much more straightforward.
The specific example you gave, though, makes me squint a bit, for other reasons. The surrounding quad has text content and an absolute y-position, but does not have the “draw” attribute? Is a quad then considered to be kind of a ghostly thing that does not take up space on the page unless it has certain attributes/contents? Maybe this is just another thing I haven’t grokked about quads before now.
Also, a value containing
-margin
to me seems like a length, not a position. A page’s inner margin might be 72pts, but a quad with an x-coord of 72 is not going to end up on the margin, correct? Mightn’tedge
be a good descriptor here?Yes, by analogy to the
g
element in SVG or thediv
in HTML, which can be nested however deeply. But the use ofq
for everything arguably muddies the waters. To make it clearer, we could ultimately adopt notation like so:So far, I’ve avoided attaching any semantics to the tag (so
q
is as good as anything). But arguably, with the introduction of drawing elements, the attributes-only approach hits its limit, since it doesn’t make sense to propagatedraw = "text"
to subquads.I suppose it can be both, depending on whether you’re starting at 0.
In general I agree with your view that it should be easy to align drawing objects to obvious anchors within the page. Though I’d like to avoid the LaTeX approach of creating a raft of global variables.
Moreover, as you say, the layer generating the Q-expression could do all these calculations just as easily. So there is a balance to be struck about who does certain housekeeping. Something that only Quad can do would be, say, centering a line of text, because Quad has access to text measurement, and the other tool does not.
Since so many formatting operations depend on the resolved locations of certain elements, I’m starting to wonder whether we ought to have some kind of query language to find certain quads in the finished layout. For instance if you wanted the first line of the current page, you could say
page:line[2]
Or the 42nd line in the document as a whole:
doc:line[42]
This could also be adapted for the
repeat
semantics to express multiple elements. For instance, “the first two pages of every section” might be done like so:doc:section[*]:page[1:2]
We could also extend it to be able to query certain geometric properties, for instance “the y coordinate of the northwest corner of the last line in the first column on this page”:
page:column[1]:line[last]#nw.y
This seems like a strange thing to want. But the point is that with a query system, there is a coherent way of discovering interesting geometric features on the page.
(Of course, not a new idea — XML has XPath, HTML has CSS selectors, etc.)
This may be an uninformed comment—I just noticed a few days ago how much progress you'd made with
quad
since I'd last looked at it—but here goes.Allowing "small" expressions in the values of attributes strikes me as sign that you've created an embedded DSL. Just this example, I see a language for attribute values that includes integers, strings, booleans, enumerations (e.g.
center
), variable references, and operators. Yet this DSL is being written inside of strings. That seems like a curious choice, given that you literally wrote the book on the approach I'd otherwise recommend :)It didn't occur to me until I thought about the addition of expressions, but my next thought was that many of those things are already present: in particular, the rich variety of datatypes, rather than just strings.
I guess at root I'm wondering why you chose to define q-expressions as a subset of x-expressions. I work with a fair amount of XML, and the fact that the only datatypes are strings and elements is one of my least favorite parts.
To reduce the biggest barrier to adoption. Q-expressions are a stand-in for any data serialization format, all of which can be made to behave in a roughly XML-ish manner. Quad doesn’t foreclose the possibility of using Racket as a richer environment for building Quad layouts. But if that’s the price of admission, no one will use Quad. Whereas if 80% of Quad is available through the API of Q-expressions — which can be generated by any external tool you like, with Quad then serving as a renderer — then Quad will be a lot more useful to a lot more people.
That said, I’d like to create as few embedded DSLs as possible. For instance, other things being equal, I’d prefer to avoid supporting computations, because they open the door to mischief and annoyance. By comparison, query strings are relatively contained & benign.
I’ve just pushed an update that adds drawing quads (
line
andtext
to start, though more to come) andparent
andrepeat
attributes that can be used to repeat & position them (using newanchor-from-parent
andanchor-to
attributes). Finally I have added the query-string system I gestured at above.Right now one shortcoming is that if you query for a
page
and then position on it, your quad will be positioned on the inner rectangle of the page (that contains the text) not the outer edge of the paper (which is intuitively what a “page” refers to). Still, that inner area is a useful thing to refer to, so I think there needs to be a name for that (interior
?) Suggestions welcome. Ultimately it seems like it may be useful to divide the page into multiple vertical sections that can have different formatting. So a simple page would only have one such section (which would correspond to this notion of a page interior) but a complex page might have several.Also, though I’m not claiming Quad’s anchor-attachment model solves all layout problems, it’s already there at the low level, so at the high level, I think it makes more sense to lean into it than to mint some different abstraction.
For instance, let’s suppose you wanted to center a text string at the bottom of the page as a footer. Rather than measuring widths and margins and computing, you could just do something like this:
string
is the text that is drawn.repeat
is a query string that says “put this on every page”.parent
means “make this quad’s parent the current page” (the repeat directive is processed first)anchor-from-parent
means to attach from the south edge of the parent (which in this case, is the current page)anchor-to
means to attach to the north edge of the drawing quad.IOW, the natural consequence of the south-to-north joinery is that the drawing quad will be centered horizontally beneath the lower edge of the page. Similarly, to align the drawing quad under the lower left corner, you could change
anchor-from-parent
tosw
andanchor-to
tonw
.