Ben Crowder

Blog: #press

6 posts :: tag feed :: about the blog :: archive

Some quick thoughts about the project space I see myself working in (meaning personal coding projects that aren’t the productivity tools I mentioned before), both now and for the foreseeable future. To be honest, it’s mostly a roadmap for myself, posted here as part of working in public.

Bookmaking tools

One of the areas in the project space is bookmaking tools: tools that help with making either print books or ebooks. What I’ve worked on in that area (and some of these are still in progress or in the future):

  • Press — low-level typesetting (PDF compiler)
  • Ink — higher-level typesetting
  • Curves — programmatic type design
  • Typlate — type design templates
  • md2epub/Caxton — ebook compiler
  • epubdiff — ebook differ
  • Fledge — text processing shell
  • Storybook — writing tool (covered under the productivity tools, yes, but I feel it fits in here)

Creativity tools

The next area, somewhat related, is creativity tools: tools for making art, music, etc. I do realize that there’s a bit of overlap between the two areas — art can be used in books, for example. This is not a rigorous taxonomy.

What I’ve worked on:

  • Trill — music composition REPL
  • Grain — command-line tool for texturing art

While I haven’t done much in this area so far, the intersection of software and art has been calling to me more lately. I expect creativity tools to become much more of a focus for me, probably even more so than the bookmaking tools.

Human-Computer Interaction

Last but not least, HCI. My master’s thesis is in this area, and much of my other work also touches on it in limited ways. (What I mean by that, I think, is that with projects like Trill, Curves, and Press, the parts that have most interested me are the interfaces. Also, those interfaces have been textual in these particular cases, but I’m also interested in other kinds of UIs.) So I plan to start building more proofs of concept and interface experiments — like the spatial interface ideas I mentioned several weeks ago.


Reply via email or via office hours

Blogging is low on the priority list at the moment, thanks to school. The preliminary classes for the master’s degree are going well. I’m writing assembly for my computer systems class, and I have to say, I really like assembly. (No sarcasm.) It’s beautiful and simple in a way I didn’t expect. I don’t see myself using it much, but it’s a good tool for the belt.

Oh, with Press, I realized a few days ago that it’s a good candidate for the first implementation of Low Ink (a JSON-based page description language that compiles to PDF). I’ll be re-architecting that part of Press so that it uses Low Ink. Also hoping to finish up the text part of Press (HarfBuzz, etc.) soon so that it’s usable for more than just basic drawing. (I’m dealing with font subsetting and encoding stuff at the moment.)


Reply via email or via office hours

After a break of several months, I’m getting back to working on Press. Status is pretty much the same as last time I posted about it. (It’s actually even a little more behind than that, since I had HarfBuzz Python bindings working then, but now — after upgrading to macOS Sierra — I’m running into issues with PyGObject’s introspection module. I may end up having to write my own HarfBuzz bindings with CFFI. We’ll see.)

The high-level roadmap right now: get font embedding to work correctly, add support for embedding images (which should be fairly easy, I think), integrate ICU for language analysis and HarfBuzz for shaping, and add color space support.

As of now, I plan to use Press for making language charts (which I’ve been using PlotDevice for) and picture books. Once it’s to the point where I can do that, then I’ll start on Ink (low-level typesetting engine, intended for typesetting books, and higher-level rule-based engine for making it easier to work with).


Reply via email or via office hours

Progress on Press has been a bit slower lately. I’ve fixed most of the errors I discovered by running the exported PDFs through the 3-Heights PDF validator. I also refactored the code and reorganized the package per Kenneth Reitz’s advice.

I’ve implemented initial support for embedding subsetted fonts (doing the subsetting via fontTools.subset), and while the fonts (including uninstalled fonts) display fine on my macOS box, the PDFs don’t validate properly and the fonts don’t show at all on iOS, which means the embedding isn’t actually working right. Current suspects include the /Differences array (which I’m not generating properly yet) and the CMap (which I haven’t implemented at all yet). I still have to implement ToUnicode as well, so that copying and pasting does what it should, but I’m fairly certain that isn’t what’s causing the fonts to not embed properly.

I’m also trying to figure out color spaces. In general I believe I want the output to be either DeviceRGB or DeviceCMYK, with some way of specifying an output intent, and also an option for the user to embed an ICC profile if they want. I’m part of the way there.

Anyway, the font stuff is far more complicated than I expected going in, but I’m still making progress, and I’m learning a lot.


Reply via email or via office hours

Press can now generate PDFs. For example:

from press import Press

with Press('press-demo.pdf', size=Press.LETTER, margin=1*Press.INCH) as p:
    # Top and bottom borders
    p.pen(0.25)
    p.line(p.page_min_x, p.page_min_y, p.page_max_x, p.page_min_y)
    p.line(p.page_min_x, p.page_max_y, p.page_max_x, p.page_max_y)

    # Rotated colored rectangle
    p.push()
    p.pen(15)
    p.stroke(hsl=(0, 0.5, 0.5))
    p.fill('#fd0')
    p.translate(p.page_min_x + 4*Press.INCH, p.page_max_y - 4*Press.INCH)
    p.rotate(45)
    p.rect(0, 0, 2*Press.INCH, 2*Press.INCH)
    p.pop()

    # Lines of varying thickness
    p.translate(p.page_min_x + 1*Press.INCH, p.page_min_y + 1*Press.CM)
    for i in range(1, 20):
        p.pen(i / 2)
        p.line(0, 0, 30, 0)
        p.translate(0, 20)

That code generates the following PDF (linked):

press-demo.png

I’m working on text/font support now, which is by far the most complicated thing about this project.

Since there isn’t a clean, cross-platform way to select a font via code, I’ve decided to use font maps (inspired by @font-face in CSS):

fontmap = {
    'paths': [ './fonts', '/Library/Fonts', ],
    'Minion Pro': [
        { 'weight': 300, 'italics': False, 'filename': 'MinionPro-Regular.otf', },
        { 'weight': 300, 'italics': True,  'filename': 'MinionPro-It.otf', },
        { 'weight': 600, 'italics': False, 'filename': 'MinionPro-Bold.otf', },
        { 'weight': 600, 'italics': True,  'filename': 'MinionPro-BoldIt.otf', },
    ],
}

with Press('output.pdf', size=Press.LETTER, fontmap=fontmap) as p:
    p.font('Minion Pro', size=24, weight=300, italics=True, dlig=True, smcp=True, tracking=50)
    p.align(Press.LEFT)
    p.text("This is a test.", 50, 50)

Font maps are admittedly extra work, but they do have some advantages as well: you can use fonts you haven’t installed, for example, and you can specify exactly which font files you want to use. And I can’t see any good way around the lack of a cross-platform font selection mechanism (meaning, a way to pass in ‘Minion Pro’ with specific weight and styles, and get a font filename in return).

Anyway, I’m in the middle of reading the PDF spec on CIDFonts and CMaps. It’s … complicated. It makes my head hurt. But it’ll be awesome when it’s done.


Reply via email or via office hours

I’ve renamed inkpdf to Press (as in printing press).

I reached the point where creating the PDF manually is no longer feasible, so I’ve been working on getting Press to a point where I can implement the PDF generation. The basic structure is in place, sans the PDF part. (That’s next.)

Here’s what a Press script looks like right now:

from press import Press

p = Press('output.pdf', width=6*Press.INCH, height=11*Press.INCH,
          margin=1*Press.INCH)

# Horizontal borders at top and bottom of page
p.stroke('#000')
p.pen(1.0)
p.line(p.page_min_x, p.page_min_y, p.page_max_x, p.page_min_y)
p.line(p.page_max_x, p.page_min_y, p.page_max_x, p.page_max_y)

# Page 2
p.page(2)
p.layer('base')
p.stroke(rgb=(1, 0, 0))
p.line(150, 150, 300, 300)
p.layer('fg')
p.stroke(hsl=(0, 0.5, 0.8))
p.line(300, 300, 450, 150)

# Go back and add another line to page 1
p.page(1)
p.stroke('#025')
p.line(p.page_min_x, p.page_min_y, p.page_min_x, p.page_max_y)

p.save() # this doesn't work yet

You can also do something like this:

with Press('output2.pdf', size=Press.LETTER,
           margin=(0.5*Press.INCH, 1.0*Press.INCH),
           inner_margin=0.5*Press.INCH,
           outer_margin=1.25*Press.INCH,
           bleed=.125*Press.INCH) as p:
    p.line(50, 50, 250, 50)
    # And so on

(Context manager, inner/outer margin, bleed, built-in paper sizes.)

Up next: adding more primitives, designing the font selection mechanism, getting it to generate an actual PDF, embedding fonts, using arbitrary Unicode code points, integrating HarfBuzz, etc.


Reply via email or via office hours