Skip to content
This repository has been archived by the owner on Jul 23, 2022. It is now read-only.

support drawing commands in quadwriter #7

Open
mbutterick opened this issue Aug 5, 2019 · 21 comments
Open

support drawing commands in quadwriter #7

mbutterick opened this issue Aug 5, 2019 · 21 comments

Comments

@mbutterick
Copy link
Owner

No description provided.

@mbutterick mbutterick changed the title quad/draw dialect add quad/draw dialect Aug 5, 2019
@mbutterick mbutterick changed the title add quad/draw dialect add quaddraw app Aug 5, 2019
@mbutterick
Copy link
Owner Author

https://github.com/mbutterick/quad/issues/35#issuecomment-501707731

But I think that canvas service is a subset of quadwriter. How would it work? You just want to put boxes at certain positions, with (possibly) line-wrapped type inside, or images? And everything happens on one logical “page”, so there’s no column or page breaks to compute?

@mbutterick
Copy link
Owner Author

https://github.com/mbutterick/quad/issues/35#issuecomment-501733559

I'm envisioning how I would use quad/quadwriter in place of racket/draw under the hood of #lang bookcover, so I'd be using it to, for example, implement the kinds of things talked about in the bookcover overview. So: arbitrary page sizes; graphical elements or line-wrapped type placed at precise coordinates, possibly overlapping; barcodes; generative art; etc. With the current approach I create a bunch of picts and then draw them on a pdf-dc%.

The benefits over racket/draw would be things like better typesetting (bookcover cannot currently wrap text at all), much better portability across OSs, less fighting with the system font stack over things like font naming, etc.

@mbutterick
Copy link
Owner Author

@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 be jammed integrated into the existing quadwriter 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 what position = "absolute" does in HTML).

Of course, nothing would stop you from making a quadwriter document consisting only of draw 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

  1. It doesn’t have a notion of baseline alignment

  2. 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)

  3. 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-up quadwriter text and wrap it in a special draw quad with a few extra attributes that controls this extra behavior.

@mbutterick mbutterick changed the title add quaddraw app support drawing commands in quadwriter Aug 19, 2019
@otherjoel
Copy link

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)?

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.

@otherjoel
Copy link

Though rather than a dialect separate from #lang quadwriter, I’d like to see if it can be jammed integrated into the existing quadwriter 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"

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 picts 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 with pict + 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 a postscript-dc% or svg-dc%, maybe it would make sense to provide something like a qdraw-dc% that can accept the same operations and produces a (q ((draw …)) expression? Or perhaps convert a postscript-dc% or svg-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)

@otherjoel
Copy link

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.

@mbutterick
Copy link
Owner Author

(Maybe a miniature page is a good way to think of it?)

I agree.

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.

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)

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.

That is, in essence, what happens now with the main text flow, though I know what you mean.

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’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.

@mbutterick
Copy link
Owner Author

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).

@otherjoel
Copy link

For instance, I’ve been considering how to implement headers and footers. It seems like they could just be handled as drawing commands

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:

(q ((draw "text")
    (name "footer")
    (font-italic "true")
    (center-x "true")
    (x-offset "-288")
    (x-relative-to "spine")
    (y "720")
    (text "Centered Hello at bottom center"))

@mbutterick
Copy link
Owner Author

I see what you mean, though I’m learning to be leery of splitting up an attribute like x into peripheral attributes like center-x, x-offset, x-relative-to that essentially compute x. (Attributes propagate downwards, so one has to be alert to the possibility that, say, a subquad has an x attribute that contradicts these other values.)

How far could we get by allowing small computations in the value of the attribute? So instead of:

(q ((draw "text")
    (name "footer")
    (font-italic "true")
    (center-x "true")
    (x-offset "-288")
    (x-relative-to "spine")
    (y "720")
    (text "Centered Hello at bottom center"))

We might have:

(q ((draw "text")
    (name "footer")
    (font-italic "true")
    (x "page-margin-inner - 288")
    (width "page-width - page-margin-inner - page-margin-outer")
    (y "720")
    (line-align "center")
    (text "Centered Hello at bottom center"))

@otherjoel
Copy link

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)

Attributes propagate downwards, so one has to be alert to the possibility that, say, a subquad has an x attribute that contradicts these other values

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.

@otherjoel
Copy link

Perhaps the x attribute could accept a from spine directive, something like

(q ((draw "text")
    (font-italic "true")
    (x "288 from spine")
    (width "72")
    (y "720")
    (line-align "center")
    (text "Page 123 of 400"))

On 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 that x always refer to the northwest corner) would be subtracted from the value of page-width.

@mbutterick
Copy link
Owner Author

they don’t fully describe a solution to the alternating-page layout problem

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:

'(q ((font-italic "true")
     (width "72")
     (y "720")
     (line-align "center")
     (text "Page 123 of 400"))
    (q ((draw "text")
        (page-repeat "left")
        (x "page-margin-outer")))
    (q ((draw "text")
        (page-repeat "right")
        (x "page-margin-inner")))

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.)

@otherjoel
Copy link

My idea so far is to handle alternating pages by specifying a page-repeat attribute

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’t edge be a good descriptor here?

@mbutterick
Copy link
Owner Author

Is a quad then considered to be kind of a ghostly thing

Yes, by analogy to the g element in SVG or the div in HTML, which can be nested however deeply. But the use of q for everything arguably muddies the waters. To make it clearer, we could ultimately adopt notation like so:

'(group ((font-italic "true")
     (width "72")
     (y "720")
     (line-align "center")
     (text "Page 123 of 400"))
    (text
        (page-repeat "left")
        (x "page-margin-outer")))
    (text
        (page-repeat "right")
        (x "page-margin-inner")))

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 propagate draw = "text" to subquads.

Also, a value containing -margin to me seems like a length, not a position

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.

@mbutterick
Copy link
Owner Author

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.

@mbutterick
Copy link
Owner Author

(Of course, not a new idea — XML has XPath, HTML has CSS selectors, etc.)

@LiberalArtist
Copy link

How far could we get by allowing small computations in the value of the attribute? … We might have:

(q ((draw "text")
    (name "footer")
    (font-italic "true")
    (x "page-margin-inner - 288")
    (width "page-width - page-margin-inner - page-margin-outer")
    (y "720")
    (line-align "center")
    (text "Centered Hello at bottom center"))

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.

@mbutterick
Copy link
Owner Author

wondering why you chose to define q-expressions as a subset of x-expressions

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.

@mbutterick
Copy link
Owner Author

I’ve just pushed an update that adds drawing quads (line and text to start, though more to come) andparent and repeat attributes that can be used to repeat & position them (using new anchor-from-parent and anchor-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.

@mbutterick
Copy link
Owner Author

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:

(text ((string "my footer text")
       (repeat "page[*]")
       (parent "page[this]")
       (anchor-from-parent "south")
       (anchor-to "north")))
  • 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 to sw and anchor-to to nw.

@mbutterick mbutterick transferred this issue from mbutterick/quad Jan 8, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants