clearance

main
Matthew Butterick 7 years ago
parent cc60dbbb31
commit 9279750b2e

@ -1,5 +1,8 @@
#lang info
(define collection 'multi)
(define deps '(("base" #:version "6.0") "sugar" "csp" "rackunit-lib" "hyphenate" "at-exp-lib" "data-lib" "draw-lib" "gui-lib" "math-lib" "plot-gui-lib" "plot-lib" "profile-lib" "typed-racket-lib"))
(define build-deps '("racket-doc" "scribble-lib" "draw-doc" "scribble-doc"))
(define deps '("beautiful-racket-lib"
"txexpr"
("base" #:version "6.0") "sugar" "csp" "rackunit-lib" "hyphenate" "at-exp-lib" "data-lib" "draw-lib" "gui-lib" "math-lib" "plot-gui-lib" "plot-lib" "profile-lib" "typed-racket-lib"))
(define build-deps '("debug"
"racket-doc" "scribble-lib" "draw-doc" "scribble-doc"))
(define update-implies '("sugar"))

@ -1,15 +1,14 @@
#lang debug racket/base
(require racket/contract racket/list txexpr sugar/debug sugar/list racket/promise racket/function
(require racket/contract racket/list racket/match txexpr sugar/debug sugar/define sugar/list racket/promise racket/function (only-in racket/control call/prompt)
"param.rkt" "qexpr.rkt" "atomize.rkt" "quad.rkt")
(define/contract (insert-breaks xs
(define+provide/contract (insert-breaks xs
[target-size (current-line-width)]
[debug #f]
#:break-val [break-val 'break]
;; todo: generalize these procs so they're not particular to quads
#:mandatory-break-proc [mandatory-break? (const #f)]
#:optional-break-proc [optional-break? (const #f)]
#:finish-segment-proc [finish-segment (λ (pieces) (dropf pieces optional-break?))]
#:finish-segment-proc [finish-segment-proc values]
#:size-proc [size-proc (const 1)])
((any/c) (integer? any/c
#:break-val any/c
@ -18,41 +17,44 @@
#:size-proc procedure?
#:finish-segment-proc procedure?) . ->* . (listof any/c))
(define start-signal (gensym))
(define (finish-segment pieces) (finish-segment-proc (reverse (dropf pieces optional-break?))))
(define last-optional-break-k #f)
(define (capture-optional-break-k!) (let/cc k (set! last-optional-break-k k) #f))
(for/fold ([vals null]
[pieces null]
[size-so-far start-signal]
#:result (reverse (append (finish-segment pieces) vals)))
([x (in-list xs)])
(define-values (size-start size-mid size-end) (size-proc x))
(define at-start? (eq? size-so-far start-signal))
(define underflow? (and (not at-start?) (<= (+ size-so-far size-end) target-size)))
(define (add-to-segment) (values vals (cons x pieces) (if at-start?
size-start
(+ size-so-far size-mid))))
(define (insert-break)
;; when break is found, q is omitted from accumulation
;; and any preceding optional breaks are dropped (that would be trailing before the break)
(values (cons break-val (append (finish-segment pieces) vals)) null start-signal))
(cond
[(mandatory-break? x) (when debug (report x 'got-mandatory-break))
(insert-break)]
[(optional-break? x)
(cond
[at-start? (when debug (report x 'skipping-opt-break-at-beginning)) (values vals null size-so-far)]
[(and underflow? (capture-optional-break-k!)) (when debug (report x 'resuming-breakpoint))
(set! last-optional-break-k #f) ;; prevents continuation loop
(insert-break)]
[else (when debug (report x 'add-optional-break))
(add-to-segment)])]
[(or at-start? underflow?) (when debug (report x 'add-ordinary-char))
(add-to-segment)]
[last-optional-break-k (when debug (report x 'invoking-last-breakpoint))
(last-optional-break-k #t)]
[else (when debug (report x 'falling-back))
(define-values (vals pcs size-so-far) (insert-break))
(values vals (list x) size-start)]))) ;; fallback if no last-breakpoint-k exists
(call/prompt ;; continuation boundary for last-optional-break-k
(thunk
(define (capture-optional-break-k!) (let/cc k (set! last-optional-break-k k) #f))
(for/fold ([segments null]
[pieces null]
[size-so-far start-signal]
#:result (append* (reverse (cons (finish-segment pieces) segments))))
([x (in-list xs)])
(define-values (size-start size-mid size-end) (size-proc x))
(define at-start? (eq? size-so-far start-signal))
(define underflow? (and (not at-start?) (<= (+ size-so-far size-end) target-size)))
(define (add-to-segment) (values segments (cons x pieces) (if at-start?
size-start
(+ size-so-far size-mid))))
(define (insert-break)
;; when break is found, q is omitted from accumulation
;; and any preceding optional breaks are dropped (that would be trailing before the break)
(values (list* (list break-val) (finish-segment pieces) segments) null start-signal))
(cond
[(mandatory-break? x) (when debug (report x 'got-mandatory-break))
(insert-break)]
[(optional-break? x)
(cond
[at-start? (when debug (report x 'skipping-opt-break-at-beginning)) (values segments null size-so-far)]
[(and underflow? (capture-optional-break-k!)) (when debug (report x 'resuming-breakpoint))
(set! last-optional-break-k #f) ;; prevents continuation loop
(insert-break)]
[else (when debug (report x 'add-optional-break))
(add-to-segment)])]
[(or at-start? underflow?) (when debug (report x 'add-ordinary-char))
(add-to-segment)]
[last-optional-break-k (when debug (report x 'invoking-last-breakpoint))
(last-optional-break-k #t)]
[else (when debug (report x 'falling-back))
(match-define-values (vals _ _) (insert-break))
(values vals (list x) size-start)]))))) ;; fallback if no last-breakpoint-k exists
(define x (q (hasheq 'size (delay (values 1 1 1))) #\x))
@ -211,7 +213,7 @@
(delay (values 0 1 0))
(delay (values 1 1 1)))))])
(if (promise? val) (force val) (val))))
#:finish-segment-proc (λ (pcs) (list ($slug #f (reverse (dropf pcs optional-break?)))))))
#:finish-segment-proc (λ (pcs) (list ($slug #f pcs)))))
(module+ test
(test-case

Binary file not shown.

@ -1,13 +0,0 @@
#lang racket/base
(require (for-syntax racket/base) racket/list sugar/debug "quads.rkt" "error.rkt")
(provide (except-out (all-from-out racket/base) #%module-begin)
(all-from-out racket/list sugar/debug "quads.rkt" "error.rkt")
(rename-out [~module-begin #%module-begin])
(for-syntax (all-from-out racket/base)))
(define-syntax-rule (~module-begin . args)
(#%module-begin
. args))
(module reader syntax/module-reader
#:language 'quad/dev)

@ -1,9 +0,0 @@
#lang racket/base
(provide (all-defined-out))
(struct exn:quad-overflow exn:fail ())
(define (raise-overflow-error)
(raise
(exn:quad-overflow
"overflow error: No breakpoint available. Increase line width"
(current-continuation-marks))))

@ -1,244 +0,0 @@
#lang racket/base
(require ffi/unsafe
ffi/unsafe/define
racket/draw/private/libs)
(define-syntax-rule (define+provide id val)
(begin
(define id val)
(provide id)))
(define-runtime-lib freetype-lib
[(unix) (ffi-lib "libfontconfig" '("1" ""))]
[(macosx) (ffi-lib "libfreetype.6.dylib")]
[(windows) (ffi-lib "libfreetype-6.dll")])
(define-ffi-definer define-freetype freetype-lib #:provide provide)
;; types
(define _void-pointer (_cpointer 'void-pointer))
(define _char _byte)
(define _char-pointer (_cpointer 'char-pointer))
(define _uchar _ubyte)
(define _FT_Byte _ubyte)
(define _FT_Bytes _bytes)
(define _FT_Char _char)
(define _FT_Int _int)
(define _FT_UInt _uint)
(define _FT_Int16 _short)
(define _FT_UInt16 _ushort)
(define _FT_Int32 _int32)
(define _FT_UInt32 _uint32)
(define _FT_Short _short)
(define _FT_UShort _ushort)
(define _FT_Long _long)
(define _FT_ULong _ulong)
(define _FT_Bool _byte)
(define _FT_Offset _size) ;; equivalent to _size_t?
(define _FT_PtrDist _ptrdiff) ;; equivalent to _longlong?
(define _FT_String _char)
(define _FT_String-pointer (_cpointer 'FT_String-pointer)) ;; char*
(define _FT_Tag _FT_UInt32)
(define _FT_Error _int)
(define _FT_Fixed _long)
(define _FT_Pointer _void-pointer)
(define _FT_Pos _long)
(define _FT_FWord _short)
(define _FT_UFWord _ushort)
(define _FT_F26Dot16 _short)
(define _FT_F26Dot6 _long)
(define _FT_Glyph_Format _int)
(define _FT_Encoding _int)
(define _FT_Generic_Finalizer (_cpointer '_FT_Generic_Finalizer (_fun _void-pointer -> _void)))
(define _FT_LibraryRec (_cpointer 'FT_LibraryRec))
(define _FT_Library (_cpointer 'FT_Library))
(define-cstruct _FT_Bitmap_Size
([height _FT_Short]
[width _FT_Short]
[size _FT_Pos]
[x_ppem _FT_Pos]
[y_ppem _FT_Pos]))
(define-cstruct _FT_CharMapRec
([face _void-pointer] ; should be FT_Face
[encoding _FT_Encoding]
[platform_id _FT_UShort]
[encoding_id _FT_UShort]))
(define _FT_Charmap _FT_CharMapRec-pointer)
(define _FT_CharMap-pointer (_cpointer 'FT_CharMap-pointer))
(define-cstruct _FT_Generic
([data _void-pointer]
[finalizer _FT_Generic_Finalizer]))
(define-cstruct _FT_BBox
([xMin _FT_Pos]
[yMin _FT_Pos]
[xMax _FT_Pos]
[yMax _FT_Pos]))
(define-cstruct _FT_Glyph_Metrics
([width _FT_Pos]
[height _FT_Pos]
[horiBearingX _FT_Pos]
[horiBearingY _FT_Pos]
[horiAdvance _FT_Pos]
[vertBearingX _FT_Pos]
[vertBearingY _FT_Pos]
[vertAdvance _FT_Pos]))
(define-cstruct _FT_Vector
([x _FT_Pos]
[y _FT_Pos]))
(provide (struct-out FT_Vector)
_FT_Vector _FT_Vector-pointer)
(define-cstruct _FT_Bitmap
([rows _int]
[width _int]
[pitch _int]
[buffer (_cpointer 'buffer)]
[num_grays _short]
[pixel_mode _ubyte]
[palette_mode _char]
[palette _void-pointer]))
(define-cstruct _FT_Outline
([n_contours _short]
[n_points _short]
[points _FT_Vector-pointer]
[tags (_cpointer 'tags)]
[contours (_cpointer 'contours)]
[flags _int]))
(define-cstruct _FT_GlyphSlotRec
([library _FT_Library]
[face _void-pointer]
[next _void-pointer]
[reserved _uint]
[generic _FT_Generic]
[metrics _FT_Glyph_Metrics]
[linearHoriAdvance _FT_Fixed]
[linearVertAdvance _FT_Fixed]
[advance _FT_Vector]
[format _FT_Glyph_Format]
[bitmap _FT_Bitmap]
[bitmap_left _FT_Int]
[bitmap_top _FT_Int]
[outline _FT_Outline]
[num_subglyphs _FT_UInt]
[subglyphs _void-pointer]
[control_data _void-pointer]
[control_len _long]
[lsb_delta _FT_Pos]
[rsb_delta _FT_Pos]
[other _void-pointer]
[internal _void-pointer]))
(define _FT_GlyphSlot _FT_GlyphSlotRec-pointer)
(provide (struct-out FT_GlyphSlotRec)
_FT_GlyphSlotRec _FT_GlyphSlotRec-pointer)
(define-cstruct _FT_Size_Metrics
([x_ppem _FT_UShort]
[y_ppem _FT_UShort]
[x_scale _FT_Fixed]
[y_scale _FT_Fixed]
[ascender _FT_Pos]
[descender _FT_Pos]
[height _FT_Pos]
[max_advance _FT_Pos]))
(define-cstruct _FT_SizeRec
([face _void-pointer]
[generic _FT_Generic]
[metrics _FT_Size_Metrics]
[internal _void-pointer]))
(define _FT_Size _FT_SizeRec-pointer)
(define-cstruct _FT_FaceRec
([num_faces _FT_Long]
[face_index _FT_Long]
[face_flag _FT_Long]
[style_flags _FT_Long]
[num_glyphs _FT_Long]
[family_name _string] ; probably _string is a better choice
[style_name _string]
[num_fixed_sizes _FT_Int]
[available_sizes _FT_Bitmap_Size-pointer]
[num_charmaps _FT_Int]
[charmaps _FT_CharMap-pointer]
[generic _FT_Generic]
[bbox _FT_BBox]
[units_per_EM _FT_UShort]
[ascender _FT_Short]
[descender _FT_Short]
[height _FT_Short]
[max_advance_width _FT_Short]
[max_advance_height _FT_Short]
[underline_position _FT_Short]
[underline_thickness _FT_Short]
[glyph _FT_GlyphSlot]
[size _FT_Size]
[charmap _FT_Charmap]
[driver _void-pointer]
[memory _void-pointer]
[stream _void-pointer]
[sizes_list_head _void-pointer]
[sizes_list_tail _void-pointer]
[autohint _FT_Generic]
[extensions _void-pointer]
[internal _void-pointer]))
(define _FT_Face _FT_FaceRec-pointer)
(provide (struct-out FT_FaceRec)
_FT_FaceRec _FT_FaceRec-pointer)
(define _full-path
(make-ctype _path
path->complete-path
values))
(define-freetype FT_Init_FreeType (_fun (ftl : (_ptr o _FT_Library))
-> (err : _FT_Error)
-> (if (zero? err) ftl (error 'FT_Init_FreeType))))
(define-freetype FT_New_Face (_fun _FT_Library _full-path _FT_Long
(ftf : (_ptr o (_or-null _FT_Face)))
-> (err : _FT_Error)
-> (if (zero? err) ftf (error 'FT_New_Face (format "error ~a" err)))))
(define-freetype FT_Done_Face (_fun _FT_Face
-> (err : _FT_Error)
-> (unless (zero? err) (error 'FT_Done_Face (format "error ~a" err)))))
(define-freetype FT_Done_FreeType (_fun _FT_Library -> (err : _FT_Error) -> (if (zero? err) (void) (error 'FT_Done_FreeType))))
(define-freetype FT_Get_Kerning (_fun _FT_Face _FT_UInt _FT_UInt _FT_UInt
(ftv : (_ptr o _FT_Vector))
-> (err : _FT_Error)
-> (if (zero? err) ftv (error 'FT_Get_Kerning (format "error ~a" err)))))
(define-freetype FT_Get_Char_Index (_fun _FT_Face _FT_ULong
-> _FT_UInt))
(define-freetype FT_Load_Glyph (_fun _FT_Face _FT_UInt _FT_Int32
-> (err : _FT_Error)))
(define-freetype FT_Load_Char (_fun _FT_Face _FT_ULong _FT_Int32
-> (err : _FT_Error)))
(define+provide FT_KERNING_UNSCALED 2)
(define+provide FT_LOAD_DEFAULT 0)
(define+provide FT_LOAD_RENDER (expt 2 2))
(define+provide FT_LOAD_LINEAR_DESIGN (expt 2 13))
(define+provide FT_LOAD_NO_RECURSE (expt 2 10))

@ -1,4 +1,3 @@
#lang info
(define compile-omit-paths '("old-master"))
(define test-omit-paths '("old-master"))
(define collection "quad")
(define version "0.0")

@ -1,21 +0,0 @@
#lang quad/dev
(require "quads.rkt" "typeset.rkt" "atomize.rkt" "render.rkt" "render-pdf.rkt" racket/list racket/string)
(provide (except-out (all-from-out quad/dev "quads.rkt") #%module-begin)
(rename-out [~module-begin #%module-begin]))
(define-syntax-rule (~module-begin lang-line-config-arg . args)
(#%module-begin
(define main-quad (apply quad #f (list . args))) ; at-reader splits lines, but we want one contiguous run
;; branch on config-arg to allow debug / inspection options on #lang line
(define config-pieces (string-split (string-trim lang-line-config-arg)))
(and (pair? config-pieces)
(let ([config-args (map string->number (cdr config-pieces))])
(case (car config-pieces)
[("in") (atomize main-quad)]
[("out") (time (apply fit (atomize main-quad) config-args))]
[("test") (time (debug-render (apply fit (atomize main-quad) config-args)))]
[("pdf") (time (render-pdf (apply fit (atomize main-quad) config-args)))]
[else (fit (atomize main-quad))])))))
(module reader syntax/module-reader
quad/main)

@ -1,45 +0,0 @@
#lang quad/dev
(require "freetype-ffi.rkt")
(provide (all-defined-out))
(define (measure! q)
(quad-dim-set! q
(cond
[(quad-printable? q)
(* (measure-char (quad-font q) (quad-val q)) (quad-font-size q))]
[else 0])))
(module+ test
(require rackunit)
(define q ($black '#hasheq((size . 12) (font . "sc.otf")) 0 #\n))
(check-equal? (measure-char (quad-font q) (quad-val q)) .6))
(define measure-char
(let ([measure-cache (make-hash)]
[glyph-idx-cache (make-hash)]
[glyph-width-cache (make-hash)]
[em-size-cache (make-hash)]
[ft-library (FT_Init_FreeType)]
[ft-face-cache (make-hash)])
(λ (font-pathstring char)
(define (do-measure)
(define ft-face (hash-ref! ft-face-cache font-pathstring
(λ () (unless (file-exists? font-pathstring)
(error 'measure-char (format "font path ~v does not exist" font-pathstring)))
(FT_New_Face ft-library font-pathstring 0))))
(define width
(let ([glyph-idx (hash-ref! glyph-idx-cache (cons char font-pathstring)
(λ () (FT_Get_Char_Index ft-face (char->integer char))))])
(hash-ref! glyph-width-cache (cons glyph-idx font-pathstring)
(λ ()
(FT_Load_Glyph ft-face glyph-idx FT_LOAD_NO_RECURSE) ; loads into FTFace's 'glyph' slot
(define width (FT_Vector-x (FT_GlyphSlotRec-advance (FT_FaceRec-glyph ft-face))))
(* width 1.0))))) ; store as inexact
(define em-size
(hash-ref! em-size-cache font-pathstring (λ () (FT_FaceRec-units_per_EM ft-face))))
(/ width em-size))
(hash-ref! measure-cache (cons font-pathstring char) do-measure))))
(module+ test
(require rackunit)
(check-equal? (measure-char "charter.ttf" #\f) .321))

@ -1,28 +0,0 @@
#lang racket
(require hyphenate "quads.rkt" "world.rkt" "render.rkt" "typeset.rkt" "utils.rkt")
(define (make-test-blocks string)
(let ([string string])
(add-between (list
(block '(quality 100 x-align justify) string)
; (block '(quality 100 x-align justify) string)
) (block-break))))
(define test-block (block '(font "Equity Text B" measure 265 leading 8 size 10 x-align-last-line left) (apply block #f
(make-test-blocks (hyphenate "“This is a PDF generated from my typesetting system, which is called Quad. Im writing this in a source file in DrRacket. When I click “Run”, a PDF pops out. Not bad — and no LaTeX needed. Quad, however, does use the fancy linebreaking algorithm developed for TeX. (It also includes a faster linebreaking algorithm for when speed is more important than quality.) This tutorial provides a brief introduction to the Racket programming language by using one of its picture-drawing libraries. Even if you dont intend to use Racket for your artistic endeavours, the picture library supports interesting and enlightening examples. After all, a picture is worth five hundred “hello world”s.")))))
;(block-break) (block #f (~a (random)))))
(define ti5 (block '(font "Equity Text B" x-align justify x-align-last-line left) (block #f (block '(x-align-last-line center weight bold font "Equity Caps B") "Greetings" (block-break) "Matthew & Robby!") (block-break) (block #f " This is a PDF generated from my Racket typesetting language, which is called " (word '(style italic) "Quad.") " Im writing this in a source file in DrRacket. When I click “Run”, a PDF pops out. Not bad — and no LaTeX needed." (block-break) (box '(width 15)) (word '(font "Concourse T2") "Quad takes some of the good ideas from TeX, like its fancy algorithm for breaking paragraphs into lines. Though respectfully to Prof. Knuth, I believe Ive even improved it.") (block-break) (box '(width 15)) "Of course, Quad can also handle " (word '(font "Avenir") "different fonts,") (word '(style italic) " styles, ") (word '(size 14 weight bold) "and sizes") " within the same line. As you can see, it can also justify paragraphs. (This sample also uses the new fractional point sizes.)" (block-break) (box '(width 15)) "Truly, the combination of Lisp and typesetting is an unprecedented confluence of geekery. I hope that Quad can become a useful part of the Racket ecosystem." (block-break) (word '(x-align-last-line center weight bold font "Equity Caps B") "thank you for your help" (block-break) "mb" )))))
(define ti6 (block #f (apply block '(column-count 3 column-gutter 15 measure 170) (add-between (map (λ(r) (quad-attr-set* ti5 'size r 'leading (* 1.25 r))) (range 8.5 13 .5)) (column-break)))
(page-break)
(apply block '(column-count 2 column-gutter 25 measure 240) (add-between (map (λ(r) (quad-attr-set* ti5 'size r 'leading (* 1.25 r))) (range 10 14 .5)) (column-break)))
(page-break)
(apply block '(column-count 1 column-gutter 15 measure 360) (add-between (map (λ(r) (quad-attr-set* ti5 'size r 'leading (* 1.25 r))) (range 15 18)) (column-break)))))
(parameterize ([world:quality-default world:max-quality]
[world:paper-width-default 792]
[world:paper-height-default 612])
(send (new pdf-renderer%) render-to-file (time (typeset ti6)) "foo.pdf")
)

@ -1,48 +0,0 @@
#lang racket/base
(require racket/runtime-path
racket/gui/base
racket/class
quad/render
quad/typeset
racket/system)
(provide make-drracket-buttons)
(define-namespace-anchor cache-module-ns)
(module test racket/base) ; suppress testing by `raco test`
(define-runtime-path html-png-path "cmd-char.png")
#| for toolbar callbacks, see
http://pkg-build.racket-lang.org/doc/tools/drracket_module-language-tools.html#%28elem._drracket~3atoolbar-buttons%29
|#
(define (make-render-pdf-button [open? #f])
(let ([label (format "Render ~aPDF" (if open? "and Open " ""))]
[bitmap (make-object bitmap% html-png-path 'png/mask)]
[callback (let ([open? open?])
(λ (drr-frame)
(define fn (send (send drr-frame get-definitions-text) get-filename))
(unless fn
(error 'render-pdf "Please save your file first"))
(define pdfn (path-replace-suffix fn #".pdf"))
(define fn-out (parameterize ([current-namespace (make-base-namespace)])
(namespace-attach-module (namespace-anchor->namespace cache-module-ns) 'quad/typeset)
(dynamic-require `(submod ,fn outy) 'out)))
(when fn-out
(define-values (fn-dir name dir?) (split-path fn))
(parameterize ([current-directory fn-dir])
(local-require "render.rkt" racket/class profile sugar/debug quad/logger quad/world)
(activate-logger quad-logger)
(send (new pdf-renderer%) render-to-file (typeset fn-out) pdfn))
(when open?
(parameterize ([current-input-port (open-input-string "")])
(system (format "open \"~a\"" (path->string pdfn))))))))]
[number (+ 99 (if open? 0 1))])
(list label bitmap callback number)))
(define (make-drracket-buttons)
(list (make-render-pdf-button) (make-render-pdf-button #t)))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 B

@ -1,73 +0,0 @@
#lang racket/base
(require racket/list sugar/define)
(require "samples.rkt" "quads.rkt" "utils.rkt")
(define ti (block '(measure 54 leading 18) "Meg is " (box '(foo 42)) " ally."))
(define tib (block '(measure 240 font "Equity Text B" leading 16 size 13.5 x-align justify x-align-last-line left) (block #f (block '(weight bold font "Equity Caps B") "Hello") (block-break) (box '(width 15)))))
;ti
;; convert quad into tokenized representation:
;; 1) vector of atomic tokens
;; 2) list of (attribute + range of tokens it applies to)
;; this representation is designed to:
;; 1) preserve all information in the original quad
;; 2) be compact / not duplicate information unnecessarily
;; 3) allow sequential access to the tokens
;; 4) allow fast computation of token state (i.e., attrs that apply)
(define+provide (make-tokens-and-attrs quad-in)
(define-values (all-tokens all-attrs _)
(let loop ([current-quad quad-in][attr-acc empty][starting-tidx 0])
(cond
[(empty? (quad-list current-quad)); no subelements, so treat this quad as single token
(let ([current-quad-attrs (quad-attrs current-quad)]
[ending-tidx (add1 starting-tidx)])
(values (quad (quad-name current-quad) #f empty)
(if current-quad-attrs
(cons (vector current-quad-attrs starting-tidx ending-tidx) attr-acc)
attr-acc)
ending-tidx))]
[else ; replace quad with its tokens, exploded
(define-values (tokens-from-fold subattrs-from-fold ending-tidx-from-fold)
(for/fold ([token-acc empty][subattr-acc empty][tidx starting-tidx])
([item (in-list (quad-list current-quad))])
(cond
[(quad? item)
(define-values (sub-tokens sub-attrs sub-last-tidx) (loop item attr-acc tidx))
(values (cons sub-tokens token-acc) (cons sub-attrs subattr-acc) sub-last-tidx)]
[else ; item is a string of length > 0 (quad contract guarantees this)
(define-values (exploded-chars last-idx-of-exploded-chars)
(for/fold ([chars empty][last-idx #f])([(c i) (in-indexed item)])
(values (cons c chars) i))) ; fold manually to get reversed items & length at same time
(values (cons exploded-chars token-acc) subattr-acc (+ tidx (add1 last-idx-of-exploded-chars)))])))
(values tokens-from-fold
(let ([current-quad-attrs (quad-attrs current-quad)])
(if current-quad-attrs
(cons (vector current-quad-attrs starting-tidx ending-tidx-from-fold) subattrs-from-fold)
subattrs-from-fold))
ending-tidx-from-fold)])))
(values (list->vector (reverse (flatten all-tokens))) (flatten all-attrs)))
(define-values (tokens attrs) (make-tokens-and-attrs (ti5)))
(define+provide current-tokens (make-parameter tokens))
(define+provide current-token-attrs (make-parameter attrs))
;(filter (λ(idx) (box? (vector-ref tokens idx))) (range (vector-length tokens)))
(define (attr-ref-hash a) (vector-ref a 0))
(define (attr-ref-start a) (vector-ref a 1))
(define (attr-ref-end a) (vector-ref a 2))
(define (calc-attrs tref)
(map attr-ref-hash (filter (λ(attr) (<= (attr-ref-start attr) tref (sub1 (attr-ref-end attr)))) (current-token-attrs))))
(module+ main
(require rackunit)
(define ti (block '(measure 54) "Meg is " (box '(foo 42)) " ally."))
(define-values (tokens attrs) (make-tokens-and-attrs ti))
(current-tokens tokens)
(current-token-attrs attrs)
;; todo: repair this test
#;(check-equal? tokens (vector #\M #\e #\g #\space #\i #\s #\space (box) #\space #\a #\l #\l #\y #\.))
(check-equal? attrs '(#(#hash((measure . 54)) 0 14) #(#hash((foo . 42)) 7 8))))

@ -1,75 +0,0 @@
#lang racket/base
(require ffi/unsafe
ffi/unsafe/define
ffi/unsafe/alloc
setup/dirs
racket/draw/private/libs
racket/draw/private/utils)
(define-runtime-lib fontconfig-lib
[(unix) (ffi-lib "libfontconfig" '("1" ""))]
[(macosx)
(ffi-lib "libpng16.16.dylib")
(ffi-lib "libexpat.1.dylib")
(ffi-lib "libfreetype.6.dylib")
(ffi-lib "libfontconfig.1.dylib")]
[(windows)
(ffi-lib "zlib1.dll")
(ffi-lib "libintl-8.dll")
(ffi-lib "libpng16-16.dll")
(ffi-lib "libexpat-1.dll")
(ffi-lib "libfreetype-6.dll")
(ffi-lib "libfontconfig-1.dll")])
(define-syntax-rule (_pfun spec ...)
(_fun #:in-original-place? #t spec ...))
(define-ffi-definer define-fc fontconfig-lib
#:provide provide)
;; datatype information from
;; http://www.freedesktop.org/software/fontconfig/fontconfig-devel/x31.html
(define FcConfig (_cpointer 'FcConfig))
(define FcPattern (_cpointer 'FcPattern))
(define FcPattern-pointer (_cpointer FcPattern))
(define FcObjectSet (_cpointer 'FcObjectSet))
(define FcBool _bool)
(define FcChar8 _bytes)
(define-cstruct _FcFontSet
([nfont _int]
[sfont _int]
[fonts FcPattern-pointer])) ;; ?? spec says "FcPattern **fonts" but I don't know how this translates to ffi
;; function information from
;; http://www.freedesktop.org/software/fontconfig/fontconfig-devel/x102.html
(define-fc FcGetVersion (_pfun -> _int))
(define-fc FcConfigCreate (_pfun -> FcConfig))
(define-fc FcInitLoadConfig (_pfun -> FcConfig))
(define-fc FcConfigAppFontAddFile (_pfun FcConfig FcChar8 -> FcBool))
(define-fc FcConfigHome (_pfun -> FcChar8))
(define-fc FcConfigGetSysRoot(_pfun FcConfig -> FcChar8))
(define-fc FcFontList(_pfun FcConfig FcPattern FcObjectSet -> _FcFontSet))
(define-fc FcPatternCreate (_pfun -> FcPattern))
(define-fc FcFontSetCreate (_pfun -> _FcFontSet))
(define-fc FcObjectSetCreate (_pfun -> FcObjectSet))
(define-fc FcPatternPrint (_pfun FcPattern -> _void))
(define-fc FcPatternEqual (_pfun FcPattern FcPattern -> FcBool))
;; attempting to replicate font-loading workaround shown at
;; https://bugzilla.gnome.org/show_bug.cgi?id=347237#c25
(define cfg (FcConfigCreate)) ; workaround step 1
(define path (string->bytes/utf-8 "/Users/MB/Desktop/reporter.otf"))
(FcConfigAppFontAddFile cfg path) ; workaround step 2
(define fcp (FcPatternCreate))
(define fcos (FcObjectSetCreate))
(define fs (FcFontList cfg fcp fcos))
(define pat (FcFontSet-fonts fs))
;; this crashes DrRacket, prob because I have mangled the _FcFontSet definition
;; (FcPatternPrint pat)

@ -1,357 +0,0 @@
#lang racket/base
(require ffi/unsafe
ffi/unsafe/define
ffi/unsafe/alloc
racket/draw/unsafe/glib
racket/draw/unsafe/cairo
racket/draw/private/utils
racket/draw/private/libs)
(define-runtime-lib pango-lib
[(unix) (ffi-lib "libpango-1.0" '("0" ""))]
[(macosx) (ffi-lib "libpango-1.0.0.dylib")]
[(windows) (ffi-lib "libpango-1.0-0.dll")])
(define-runtime-lib pangowin32-lib
[(unix) #f]
[(macosx)]
[(windows)
(ffi-lib "libpangowin32-1.0-0.dll")])
(define-runtime-lib pangocairo-lib
[(unix) (ffi-lib "libpangocairo-1.0" '("0" ""))]
[(macosx)
(ffi-lib "libharfbuzz.0.dylib")
(ffi-lib "libpangoft2-1.0.0.dylib")
(ffi-lib "libpangocairo-1.0.0.dylib")]
[(windows)
(ffi-lib "libintl-8.dll")
(ffi-lib "libpangowin32-1.0-0.dll")
(ffi-lib "libexpat-1.dll")
(ffi-lib "libfreetype-6.dll")
(ffi-lib "libfontconfig-1.dll")
(ffi-lib "libharfbuzz-0.dll")
(ffi-lib "libpangoft2-1.0-0.dll")
(ffi-lib "libpangocairo-1.0-0.dll")])
(define-ffi-definer define-pango pango-lib
#:provide provide)
(define-ffi-definer define-pangocairo pangocairo-lib
#:provide provide)
(define-ffi-definer define-pangowin32 pangowin32-lib
#:provide provide)
;; Pango's Core Text back-end can somehow go wrong if we're going to eventually
;; use AppKit but don't load AppKit it before using functions such as
;; `pango_cairo_font_map_get_default'. So, force AppKit now for the platform
;; where the Core Text back-end is used:
(when (equal? "x86_64-macosx/3m"
(path->string (system-library-subpath)))
(void (ffi-lib (format "/System/Library/Frameworks/AppKit.framework/AppKit"))))
;; ALLOCATION NOTE: since Pango calls into Cairo, it has the same
;; allocation constraints on arguments as Cairo functions; see
;; "cairo.rkt".
(define PangoContext (_cpointer 'PangoContext))
(define PangoLayout (_cpointer 'PangoLayout))
(define PangoFontDescription (_cpointer 'PangoFontDescription))
(define PangoFontFamily (_cpointer 'PangoFontFamily))
(define PangoFontFace (_cpointer 'PangoFontFace))
(define PangoFont (_cpointer 'PangoFont))
(define PangoFontMap (_cpointer 'PangoFontMap))
(define PangoFontMetrics (_cpointer 'PangoFontMetrics))
(define PangoAttrList (_cpointer 'PangoAttrList))
(define PangoAttribute (_cpointer 'PangoAttribute))
(define PangoLanguage (_cpointer 'PangoLanguage))
(define PangoCoverage (_cpointer 'PangoCoverage))
(define PangoLayoutIter (_cpointer 'PangoLayoutIter))
(define PangoLayoutLine (_cpointer 'PangoLayoutLine))
(define-cstruct _PangoRectangle ([x _int]
[y _int]
[width _int]
[height _int])
#:malloc-mode 'atomic-interior)
(provide make-PangoRectangle
PangoRectangle-x
PangoRectangle-y
PangoRectangle-width
PangoRectangle-height)
(define-cstruct _PangoItem
([offset _int]
[length _int]
[num_chars _int]
;; Inline PangoAnalysis:
[shape_engine _pointer]
[lang_engine _pointer]
[font (_or-null PangoFont)]
[level _uint8]
[gravity _uint8]
[flags _uint8]
[script _uint8]
[language _pointer]
[extra_attrs _pointer]))
(provide (struct-out PangoItem)
_PangoItem _PangoItem-pointer)
(define-cstruct _PangoGlyphInfo
([glyph _uint32]
[width _uint32]
[dx _uint32]
[dy _uint32]
[is_cluster_start _uint]))
(provide (struct-out PangoGlyphInfo)
_PangoGlyphInfo _PangoGlyphInfo-pointer)
(define-cstruct _PangoGlyphString
([num_glyphs _int]
[glyphs _pointer]
[log_clusters _pointer])
#:malloc-mode 'atomic-interior)
(provide (struct-out PangoGlyphString)
_PangoGlyphString)
(define-cstruct _PangoGlyphItem ([item _PangoItem-pointer]
[glyphs _PangoGlyphString-pointer]))
(provide (struct-out PangoGlyphItem))
;; As of Pango 1.28, Pango is not thread-safe at the C level, which
;; means that it isn't place-safe in Racket. Also, for some reason,
;; when parts of Pango are initialized in a non-main place under
;; Windows, then font operations start to fail when that place exits.
;; Run all Pango calls in the original place, which synchronizes them
;; and avoids Windows problems.
(define-syntax-rule (_pfun spec ...)
(_fun #:in-original-place? #t spec ...))
(provide g_object_unref g_free)
(define-gobj g_object_unref (_pfun _pointer -> _void)
#:wrap (deallocator))
(define-glib g_free (_pfun _pointer -> _void)
#:wrap (deallocator))
;; For working around a Win32 Pango bug (see `unref-font-map'):
(define _GQueue (_cpointer 'GQueue))
(define-cstruct _PangoWin32FontMap ([type-instance _pointer]
[ref_count _uint]
[qdata _pointer]
[font_cache _pointer]
[freed_fonts _GQueue]))
(define-glib g_queue_foreach (_pfun _GQueue _fpointer #;(_fun _pointer -> _void) _pointer -> _void))
(define-glib g_queue_free (_pfun _GQueue -> _void))
(define-glib g_queue_new (_pfun -> _GQueue))
(define-gobj raw_g_object_unref _fpointer #:c-id g_object_unref)
(define (unref-font-map v)
(when (eq? (system-type) 'windows)
;; For version 1.28 of Pango, reported as Bug 649293:
;; Under Windows, PangoWin32FontMap holds a queue of freed
;; fonts, and the fonts hold a weak link back to the map.
;; Unreffing the font map drops the weak links and *then*
;; tries to release the freed fonts, which leads to failures
;; releasing the fonts. Work around the bug by manually
;; flushing the queue of freed fonts before the font map is
;; unreffed.
(let ([fm (cast v _pointer _PangoWin32FontMap-pointer)])
(g_queue_foreach (PangoWin32FontMap-freed_fonts fm) raw_g_object_unref #f)
(g_queue_free (PangoWin32FontMap-freed_fonts fm))
(set-PangoWin32FontMap-freed_fonts! fm (g_queue_new))))
(g_object_unref v))
(define-pangocairo pango_cairo_font_map_get_default (_pfun -> PangoFontMap)) ;; not an allocator
(define-pangocairo pango_cairo_font_map_new (_pfun -> PangoFontMap)
#:wrap (allocator unref-font-map))
(define-pango pango_context_new (_pfun -> PangoContext)
#:wrap (allocator g_object_unref))
;; pango_font_map_create_context() is in 1.22 and later
(provide pango_font_map_create_context)
(define (pango_font_map_create_context fm)
(let ([c (pango_context_new)])
(pango_context_set_font_map c fm)
c))
(define-pangocairo pango_cairo_update_context (_pfun _cairo_t PangoContext -> _void))
;; The convenince function pango_cairo_create_context() is in 1.22 and later
(provide pango_cairo_create_context)
(define (pango_cairo_create_context cr)
(let ([ctx (pango_font_map_create_context
(pango_cairo_font_map_get_default))])
(pango_cairo_update_context cr ctx)
ctx))
(define-pangocairo pango_cairo_create_layout (_pfun _cairo_t -> PangoLayout)
#:wrap (allocator g_object_unref))
(define-pangocairo pango_cairo_update_layout (_pfun _cairo_t PangoLayout -> _void))
(define-pango pango_layout_set_text (_pfun PangoLayout [s : _string] [_int = -1] -> _void))
(define-pangocairo pango_cairo_show_layout (_pfun _cairo_t PangoLayout -> _void))
(define-pangocairo pango_cairo_show_layout_line (_pfun _cairo_t PangoLayoutLine -> _void))
(define-pangocairo pango_cairo_show_glyph_string (_pfun _cairo_t PangoFont _PangoGlyphString-pointer -> _void))
(define-pangocairo pango_cairo_layout_line_path (_pfun _cairo_t PangoLayoutLine -> _void))
(define-pango pango_layout_iter_free (_pfun PangoLayoutIter -> _void)
#:wrap (deallocator))
(define-pango pango_layout_get_iter (_pfun PangoLayout -> PangoLayoutIter)
#:wrap (allocator pango_layout_iter_free))
(define-pango pango_layout_iter_get_baseline (_pfun PangoLayoutIter -> _int))
(define-pango pango_layout_iter_next_run (_pfun PangoLayoutIter -> _bool))
(define-pango pango_layout_iter_get_run (_pfun PangoLayoutIter -> (_or-null _PangoGlyphItem-pointer)))
(define-pango pango_layout_iter_get_run_readonly (_pfun PangoLayoutIter -> (_or-null _PangoGlyphItem-pointer))
#:fail (lambda () pango_layout_iter_get_run))
(define-pango pango_layout_get_line (_pfun PangoLayout _int -> PangoLayoutLine))
(define-pango pango_layout_get_line_readonly (_pfun PangoLayout _int -> PangoLayoutLine)
#:fail (lambda () pango_layout_get_line))
(define-pango pango_layout_get_context (_pfun PangoLayout -> PangoContext)) ;; not an allocator
;(define-pango pango_layout_get_extents (_pfun PangoLayout _pointer _PangoRectangle-pointer -> _void))
(define-pango pango_layout_get_baseline (_pfun PangoLayout -> _int)
;; The convenince function pango_layout_get_baseline() is in 1.22 and later
#:fail (lambda ()
(lambda (layout)
(let ([iter (pango_layout_get_iter layout)])
(begin0
(pango_layout_iter_get_baseline iter)
(pango_layout_iter_free iter))))))
(define-pango pango_layout_get_spacing (_pfun PangoLayout -> _int))
(define-pango pango_layout_new (_pfun PangoContext -> PangoLayout)
#:wrap (allocator g_object_unref))
(define-pangocairo pango_cairo_context_get_font_options (_pfun PangoContext -> _cairo_font_options_t)) ;; not an allocator
(define-pangocairo pango_cairo_context_set_font_options (_pfun PangoContext _cairo_font_options_t -> _void)) ;; makes a copy
(define-pango pango_layout_set_font_description (_pfun PangoLayout PangoFontDescription -> _void)) ;; makes a copy
(define-pango pango_context_get_font_map (_pfun PangoContext -> PangoFontMap)) ;; not an allocator
(define-pango pango_context_set_font_map (_pfun PangoContext PangoFontMap -> _void))
(define-pango pango_font_family_get_name (_pfun PangoFontFamily -> _string)) ;; not an allocator
(define-pango pango_font_family_is_monospace (_pfun PangoFontFamily -> _bool))
(define-pango pango_language_get_default (_pfun -> PangoLanguage)
;; not available before 1.16
#:fail (lambda () (lambda () #f)))
(define-pango pango_font_map_load_font (_pfun PangoFontMap PangoContext PangoFontDescription -> (_or-null PangoFont)))
(define-pango pango_coverage_unref (_pfun PangoCoverage -> _void)
#:wrap (deallocator))
(define-pango pango_font_get_coverage (_pfun PangoFont PangoLanguage -> PangoCoverage)
#:wrap (allocator pango_coverage_unref))
(define-pango pango_coverage_get (_pfun PangoCoverage _int -> _int))
(define-pango pango_font_metrics_unref (_pfun PangoFontMetrics -> _void)
#:wrap (deallocator))
(define-pango pango_font_get_metrics (_pfun PangoFont (_or-null PangoLanguage) -> PangoFontMetrics)
#:wrap (allocator pango_font_metrics_unref))
(define-pango pango_font_metrics_get_approximate_char_width (_pfun PangoFontMetrics -> _int))
(define-pango pango_font_metrics_get_ascent (_pfun PangoFontMetrics -> _int))
(define-pango pango_font_metrics_get_descent (_pfun PangoFontMetrics -> _int))
(define-pango pango_layout_get_unknown_glyphs_count (_pfun PangoLayout -> _int)
;; not available in old versions:
#:fail (lambda () (lambda (lo) 0)))
(define-pango pango_attr_list_unref (_pfun PangoAttrList -> _void)
#:wrap (deallocator))
(define-pango pango_attr_list_new (_pfun -> PangoAttrList)
#:wrap (allocator pango_attr_list_unref))
(define-pango pango_attr_list_insert (_pfun PangoAttrList PangoAttribute -> _void)
;; takes ownership of the attribute
#:wrap (deallocator cadr))
(define-pango pango_attribute_destroy (_pfun PangoAttribute -> _void)
#:wrap (deallocator))
(define-pango pango_attr_underline_new (_pfun _int -> PangoAttribute)
#:wrap (allocator pango_attribute_destroy))
(define-pango pango_attr_fallback_new (_pfun _bool -> PangoAttribute)
#:wrap (allocator pango_attribute_destroy))
(define-pango pango_layout_set_attributes (_pfun PangoLayout PangoAttrList -> _void))
(define-pango pango_font_map_list_families (_pfun PangoFontMap
(fams : (_ptr o _pointer))
(len : (_ptr o _int))
-> _void
-> (begin0
(for/list ([i (in-range len)])
(ptr-ref fams PangoFontFamily i))
(g_free fams))))
(define-pango pango_font_family_list_faces (_pfun PangoFontFamily
(faces : (_ptr o _pointer))
(len : (_ptr o _int))
-> _void
-> (begin0
(for/list ([i (in-range len)])
(ptr-ref faces PangoFontFace i))
(g_free faces))))
(define-pango pango_font_face_get_face_name (_pfun PangoFontFace -> _string))
(define-pango pango_font_description_free (_pfun PangoFontDescription -> _void)
#:wrap (deallocator))
(define-pango pango_font_description_new (_pfun -> PangoFontDescription)
#:wrap (allocator pango_font_description_free))
(define-pango pango_font_description_from_string (_pfun _string -> PangoFontDescription)
#:wrap (allocator pango_font_description_free))
(define-pango pango_font_description_set_family (_pfun PangoFontDescription _string -> _void))
(define-pango pango_font_description_set_style (_pfun PangoFontDescription _int -> _void))
(define-pango pango_font_description_set_weight (_pfun PangoFontDescription _int -> _void))
(define-pango pango_font_description_set_size (_pfun PangoFontDescription _int -> _void))
(define-pango pango_font_description_set_absolute_size (_pfun PangoFontDescription _double* -> _void))
(define-pango pango_font_description_get_family (_pfun PangoFontDescription -> _string))
(define _PangoWin32FontCache (_cpointer 'PangoWin32FontCache))
(define _HFONT (_cpointer 'HFONT))
(define _LOGFONT-pointer _pointer)
(define-pangowin32 pango_win32_font_map_for_display (_pfun -> PangoFontMap)
#:make-fail make-not-available)
(define-pangowin32 pango_win32_font_logfont (_pfun PangoFont -> _LOGFONT-pointer)
#:make-fail make-not-available
#:wrap (allocator g_free))
(define-pangowin32 pango_win32_font_description_from_logfont (_pfun _LOGFONT-pointer -> PangoFontDescription)
#:make-fail make-not-available
#:wrap (allocator pango_font_description_free))
(define-pangowin32 pango_win32_font_cache_unload (_pfun _PangoWin32FontCache _HFONT -> _void)
#:make-fail make-not-available)
(define-pangowin32 pango_win32_font_cache_load (_pfun _PangoWin32FontCache _LOGFONT-pointer -> _HFONT)
#:make-fail make-not-available)
(define-pangowin32 pango_win32_font_cache_new (_pfun -> _PangoWin32FontCache)
#:make-fail make-not-available)
(define-enum
0
PANGO_STYLE_NORMAL
PANGO_STYLE_OBLIQUE
PANGO_STYLE_ITALIC)
(define-enum
0
PANGO_UNDERLINE_NONE
PANGO_UNDERLINE_SINGLE
PANGO_UNDERLINE_DOUBLE
PANGO_UNDERLINE_LOW
PANGO_UNDERLINE_ERROR)
(define/provide PANGO_WEIGHT_LIGHT 300)
(define/provide PANGO_WEIGHT_MEDIUM 500)
(define/provide PANGO_WEIGHT_BOLD 700)
(define/provide PANGO_SCALE 1024)
(define-pango pango_layout_set_width (_pfun PangoLayout _int -> _void))
(define-pango pango_layout_get_width (_pfun PangoLayout -> _int))
(define-pango pango_layout_get_character_count (_pfun PangoLayout -> _int))
(define-pango pango_layout_copy (_pfun PangoLayout -> PangoLayout))
(define-pango pango_layout_get_serial (_pfun PangoLayout -> _uint))
(define-pango pango_layout_get_text (_pfun PangoLayout -> _string/utf-8))
(define-pango pango_layout_get_attributes (_pfun PangoLayout -> PangoAttrList))
(define-pango pango_layout_get_font_description (_pfun PangoLayout -> PangoFontDescription))
(define-pango pango_font_description_get_size (_pfun PangoFontDescription -> _int))
(define-pango pango_layout_get_extents (_pfun PangoLayout _PangoRectangle-pointer _PangoRectangle-pointer -> _void))
(define-pango pango_layout_get_pixel_extents (_pfun PangoLayout _PangoRectangle-pointer _PangoRectangle-pointer -> _void))

@ -1,14 +0,0 @@
#lang sugar/debug racket
(define 1-out
(block '(measure 240.0 font "Times New Roman" leading 16.0 vmeasure 300.0 size 13.5 x-align justify x-align-last-line left) (box '(width 15.0)) (block '() (block '(weight bold) "Hot " (word '(size 22.0) "D") "ang, My Fellow Americans.") " This " (block '(no-break #t) "is some truly") " nonsense generated from my typesetting system, which is called Quad. Im writing this in a source file in DrRacket. When I click [Run], a PDF pops out. Not bad\u200a\u200aand no LaTeX needed. Quad, however, does use the fancy linebreaking algorithm developed for TeX. (It also includes a faster linebreaking algorithm for when speed is more important than quality.) Of course, it can also handle " (block '(font "Courier") "different fonts,") (block '(style italic) " styles, ") (word '(size 14.0 weight bold) "and sizes-") " within the same line. As you can see, it can also justify paragraphs.")))
(require quad/typeset quad/quads quad/render)
;(time (send (new pdf-renderer%) render-to-file (typeset 1-out) "f1-test.pdf"))
(require (prefix-in 2- "foo2.rkt"))
(time (send (new pdf-renderer%) render-to-file (typeset 2-out) "f2-test.pdf"))
(require (prefix-in 3- "foo3.rkt"))
;(time (send (new pdf-renderer%) render-to-file (typeset 3-out) "f3-test.pdf"))

@ -1,3 +0,0 @@
#lang quad
match-select specifies the collected results. The default of car means that the result is the list of matches without returning parenthesized sub-patterns. It can be given as a selector function which chooses an item from a list, or it can choose a list of items. For example, you can use cdr to get a list of lists of parenthesized sub-patterns matches, or values (as an identity function) to get the full matches as well. (Note that the selector must choose an element of its input list or a list of elements, but it must not inspect its input as they can be either a list of strings or a list of position pairs. Furthermore, the selector must be consistent in its choice(s).)

@ -1,3 +0,0 @@
#lang quad
◊block['(measure 240.0 font "Times New Roman" leading 16.0 vmeasure 300.0 size 13.5 x-align justify x-align-last-line left) ◊box['(width 15.0)] ◊block[#f]{◊block['(weight bold)]{Hot ◊word['(size 22.0)]{D}ang, My Fellow Americans.} This ◊block['(no-break #t)]{is some truly} nonsense generated from my typesetting system, which is called Quad. Im writing this in a source file in DrRacket. When I click [Run], a PDF pops out. Not bad\u200a—\u200aand no LaTeX needed. Quad, however, does use the fancy linebreaking algorithm developed for TeX. (It also includes a faster linebreaking algorithm for when speed is more important than quality.) Of course, it can also handle ◊block['(font "Courier")]{different fonts,} ◊block['(style italic)]{ styles, }◊word['(size 14.0 weight bold)]{and sizes-} within the same line. As you can see, it can also justify paragraphs.}]

@ -1,5 +0,0 @@
#lang info
(define scribblings '(("scribblings/quad.scrbl" ())))
(define compile-omit-paths '("tests.rkt" "tests-ocm.rkt"))
(define test-omit-paths 'all)

@ -1,50 +0,0 @@
#lang racket/base
(require (for-syntax racket/base racket/syntax) sugar/coerce racket/contract)
(require "world.rkt" racket/match sugar/debug racket/date racket/list)
(module+ test (require rackunit))
(provide (all-defined-out))
(define-syntax-rule (define-orphan-logger name)
(begin
(define remember-cl (current-logger))
(define dummy-cl (make-logger))
(current-logger dummy-cl)
(define-logger name)
(current-logger remember-cl)))
(define-logger quad)
(define-syntax-rule (activate-logger logger)
(begin
(define logger-receiver (make-log-receiver logger (world:logging-level)))
(define log-file (build-path (current-directory) (format "~a.txt" 'logger)))
(with-output-to-file log-file #:exists 'truncate void)
(void (thread
(λ ()
(let loop ()
(match (sync logger-receiver)
[(vector event-level event-message event-value name)
(define msg (format "[~a] ~a\n" event-level event-message))
; (eprintf msg)
(flush-output)
(with-output-to-file log-file #:exists 'append (λ () (display msg)))])
(loop))))
(log-quad-info "started at ~a" (date->string (current-date) #t)))))
(define-syntax-rule (log-quad-debug-report x)
(begin
(log-quad-debug "~a = ~a" 'x x)
x))
(define-syntax-rule (log-quad-debug* xs)
(when (equal? (world:logging-level) 'debug)
(map (λ(x) (log-quad-debug x)) xs)))
(module+ main
(activate-logger quad-logger)
(log-quad-fatal "Exterminate! Exterminate!")
(log-quad-error "~a" (time (apply + (range 1000))))
(log-quad-debug "What's the red button for?"))

@ -1,52 +0,0 @@
#lang racket/base
(provide (except-out (all-from-out racket/base) #%module-begin)
(rename-out [quad-module-begin #%module-begin]))
(require (for-syntax racket/base syntax/strip-context))
(require quad/quads quad/typeset quad/world quad/render racket/class)
(define-syntax (quad-module-begin stx)
(syntax-case stx ()
[(_ expr ...)
(replace-context #'(expr ...)
#'(#%module-begin
(module outy racket/base
(require quad/quads)
(define out (block '(font "Times New Roman" measure 360.0 leading 14.0 column-count 1 column-gutter 10.0 size 11.5 x-align justify x-align-last-line left) expr ...))
(provide out))
(require 'outy)
(provide (all-from-out 'outy))
(displayln out)))]))
(module reader syntax/module-reader
quad/main
#:read quad-read
#:read-syntax quad-read-syntax
#:whole-body-readers? #t ;; need this to make at-reader work
#:info custom-get-info
(require scribble/reader)
(define (quad-read p)
(syntax->datum (quad-read-syntax (object-name p) p)))
(define quad-command-char #\@)
(define (quad-read-syntax path-string p)
(define quad-at-reader (make-at-reader
#:command-char quad-command-char
#:syntax? #t
#:inside? #t))
(define source-stx (quad-at-reader path-string p))
source-stx)
(define (custom-get-info key default [proc (λ _ #f)])
(case key
[(color-lexer)
(define my-make-scribble-inside-lexer
(dynamic-require 'syntax-color/scribble-lexer 'make-scribble-inside-lexer (λ () #f)))
(cond [my-make-scribble-inside-lexer
(my-make-scribble-inside-lexer #:command-char quad-command-char)]
[else default])]
[(drracket:toolbar-buttons)
(define my-make-drracket-buttons (dynamic-require 'quad/buttons 'make-drracket-buttons))
(my-make-drracket-buttons)]
[else default])))

@ -1,68 +0,0 @@
#lang racket/base
(require math/flonum racket/draw racket/class sugar/debug sugar/list racket/list sugar/cache racket/serialize racket/file)
(provide measure-text measure-ascent round-float update-text-cache-file load-text-cache-file)
(define precision 4.0)
(define base (flexpt 10.0 precision))
(define-syntax-rule (round-float x)
(fl/ (flround (fl* base (fl x))) base))
(define dc (new record-dc%))
(define max-size 1024) ; use fixnum to trigger faster bitshift division
;; changing max-size invalidates font cache (because it's based on max size, duh)
(define/caching (make-font/caching font weight style)
(make-font #:size max-size #:style style #:weight weight #:face font))
(define (get-cache-file-path)
(build-path "font.cache"))
(define (update-text-cache-file)
(when (current-text-cache-changed?)
(write-to-file (serialize (current-text-cache)) (get-cache-file-path) #:exists 'replace)
(current-text-cache-changed? #f)))
(define (load-text-cache-file)
(define cache-file-path (get-cache-file-path))
(current-text-cache (if (file-exists? cache-file-path)
(deserialize (file->value cache-file-path))
(make-hash))))
(define current-text-cache (make-parameter (make-hash)))
(define current-text-cache-changed? (make-parameter #f))
(define current-font-cache (make-parameter (make-hash)))
(define/caching (measure-max-size text font [weight 'normal] [style 'normal])
;((string? string?) (symbol? symbol?) . ->* . number?)
(define font-instance (hash-ref! (current-font-cache) (list font weight style) (λ() (make-font #:size max-size #:style style #:weight weight #:face font))))
;; 'combine' boolean only makes a difference for two or more chars
(hash-ref! (current-text-cache) (list text font weight style) (λ() (current-text-cache-changed? #t)
(values->list (send dc get-text-extent text font-instance (>= (string-length text) 1))))))
(define-syntax-rule (width x) (first x))
(define-syntax-rule (height x) (second x))
(define-syntax-rule (descent x) (third x))
(define-syntax-rule (extra x) (fourth x))
(define-syntax-rule (measure-text-max-size text font weight style)
(width (measure-max-size text font weight style)))
(define (measure-text text size font [weight 'normal] [style 'normal])
; ((string? flonum? string?) (symbol? symbol?) . ->* . flonum?)
;; Native function only accepts integers, so get max-size and scale down to size needed.
(define raw-measure (measure-text-max-size text font weight style))
(round-float (/ (* (exact->inexact raw-measure) (exact->inexact size)) max-size)))
(define-syntax-rule (measure-ascent-max-size text font weight style)
(let ([result-list (measure-max-size text font weight style)])
(- (height result-list) (descent result-list))))
(define (measure-ascent text size font [weight 'normal] [style 'normal])
; ((string? flonum? string?) (symbol? symbol?) . ->* . flonum?)
;; Native function only accepts integers, so get max-size and scale down to size needed.
(define raw-baseline-distance (measure-ascent-max-size text font weight style))
(round-float (/ (* (exact->inexact raw-baseline-distance) (exact->inexact size)) max-size)))

@ -1,448 +0,0 @@
#lang racket/base
(require (for-syntax racket/base racket/syntax))
(require racket/list sugar/debug rackunit racket/function racket/vector sugar/cache "logger.rkt")
(define-logger ocm)
;(activate-logger ocm-logger)
#|
Totally monotone matrix searching algorithms.
The offline algorithm in ConcaveMinima is from Agarwal, Klawe, Moran,
Shor, and Wilbur, Geometric applications of a matrix searching algorithm,
Algorithmica 2, pp. 195-208 (1987).
The online algorithm in OnlineConcaveMinima is from Galil and Park,
A linear time algorithm for concave one-dimensional dynamic programming,
manuscript, 1989, which simplifies earlier work on the same problem
by Wilbur (J. Algorithms 1988) and Eppstein (J. Algorithms 1990).
D. Eppstein, March 2002, significantly revised August 2005
|#
(provide smawky? make-ocm reduce reduce2 (prefix-out ocm- (combine-out min-value min-index)))
(define (select-elements xs is)
(map (curry list-ref xs) is))
(define (odd-elements xs)
(select-elements xs (range 1 (length xs) 2)))
(define (vector-odd-elements xs)
(for/vector ([i (in-range (vector-length xs))] #:when (odd? i))
(vector-ref xs i)))
(define (even-elements xs)
(select-elements xs (range 0 (length xs) 2)))
;; Wrapper for the matrix procedure
;; that automatically maintains a hash cache of previously-calculated values
;; because the minima operations tend to hit the same values.
;; Assuming here that (matrix i j) is invariant
;; and that the matrix function is more expensive than the cache lookup.
(define-syntax-rule (vector-append-item xs value)
(vector-append xs (vector value)))
(define-syntax-rule (vector-set vec idx val)
(begin
(vector-set! vec idx val)
vec))
(define-syntax-rule (vector-cdr vec)
(vector-drop vec 1))
(define-syntax-rule (vector-empty? vec)
(= 0 (vector-length vec)))
(define (integers? x) (and (list? x) (andmap integer? x)))
;; Reduce phase: make number of rows at most equal to number of cols
(define (reduce row-indices col-indices matrix-proc entry->value)
;(vector? vector? procedure? procedure? . -> . vector?)
(log-ocm-debug "starting reduce phase with")
(log-ocm-debug "row-indices = ~a" row-indices)
(log-ocm-debug "col-indices = ~a" col-indices)
(define (process-stack stack row-idx)
(log-ocm-debug "row stack = ~a" stack)
(let ([last-stack-idx (sub1 (vector-length stack))])
(cond
[(and (>= (vector-length stack) 1)
(log-ocm-debug "comparing row values at column ~a" (vector-ref col-indices last-stack-idx))
(log-ocm-debug "end of row stack (~a) value at column ~a = ~a" (vector-ref stack last-stack-idx) (vector-ref col-indices last-stack-idx) (entry->value (matrix-proc (vector-ref stack last-stack-idx) (vector-ref col-indices last-stack-idx))))
(log-ocm-debug "challenger row (~a) value at column ~a = ~a" row-idx (vector-ref col-indices last-stack-idx) (entry->value (matrix-proc row-idx (vector-ref col-indices last-stack-idx))))
(> (entry->value (matrix-proc (vector-ref stack last-stack-idx) (vector-ref col-indices last-stack-idx)))
(entry->value (matrix-proc row-idx (vector-ref col-indices last-stack-idx)))))
(log-ocm-debug "challenger row (~a) wins with a new minimum ~a, so end of row stack (~a) is removed" row-idx (entry->value (matrix-proc row-idx (vector-ref col-indices last-stack-idx))) (vector-ref stack last-stack-idx))
(process-stack (vector-drop-right stack 1) row-idx)]
[else
(log-ocm-debug (if (< (vector-length stack) 1)
(format "row stack too short for challenge, pushing row ~a" row-idx)
(format "challenger row (~a) loses to end of row stack (~a), so ~a joins stack" row-idx (vector-ref stack last-stack-idx) row-idx)))
stack])))
(define reduced-row-indexes
(for/fold ([stack (vector)]) ([row-idx (in-vector row-indices)])
(let ([stack (process-stack stack row-idx)])
(if (= (vector-length stack) (vector-length col-indices))
stack
(vector-append stack (vector row-idx))))))
(log-ocm-debug "finished reduce. row indexes = ~v" reduced-row-indexes)
reduced-row-indexes)
(define (reduce2 row-indices col-indices matrix-proc entry->value)
(let find-survivors ([rows row-indices][survivors empty])
(cond
[(vector-empty? rows) (list->vector (reverse survivors))]
[else
(define challenger-row (vector-ref rows 0))
(cond
;; no survivors yet, so push first row and keep going
[(empty? survivors) (find-survivors (vector-cdr rows) (cons challenger-row survivors))]
[else
(define index-of-last-survivor (sub1 (length survivors)))
(define col-head (vector-ref col-indices index-of-last-survivor))
(define-syntax-rule (test-function r) (entry->value (matrix-proc r col-head)))
(cond
;; this is the challenge: is the head cell of challenger a new minimum?
;; use < not <=, so the recorded winner is the earliest row with the new minimum, not the latest row
;; if yes, challenger wins. pop element from stack, and let challenger try again (= leave rows alone)
[(< (test-function challenger-row) (test-function (car survivors))) (find-survivors rows (cdr survivors))]
;; if not, challenger lost.
;; If we're in the last column, ignore the loser by recurring on the same values
[(= col-head (vector-last col-indices)) (find-survivors (vector-cdr rows) survivors)]
;; otherwise challenger lost and we're not in last column,
;; so add challenger to survivor stack
[else (find-survivors (vector-cdr rows) (cons challenger-row survivors))])])])))
(define (make-minimum value row-idx)
(define ht (make-hash))
(! ht 'value value)
(! ht 'row-idx row-idx)
ht)
;; Interpolate phase: in the minima hash, add results for even rows
(define-syntax-rule (vector-last v)
(vector-ref v (sub1 (vector-length v))))
(define (interpolate minima row-indices col-indices matrix-proc entry->value)
;(hash? vector? vector? procedure? procedure? . -> . hash?)
(for ([col-idx (in-range 0 (vector-length col-indices) 2)]) ;; even-col-indices
(define col (vector-ref col-indices col-idx))
(define idx-of-last-row
(if (= col-idx (sub1 (vector-length col-indices)))
(vector-last row-indices)
(: (hash-ref minima (vector-ref col-indices (add1 col-idx))) 'row-idx)))
(define smallest-value-entry
(vector-argmin (compose1 entry->value car)
(for/vector ([row-idx (in-list (dropf-right (vector->list row-indices) (negate (curry = idx-of-last-row))))])
(list (matrix-proc row-idx col) row-idx))))
(! minima col (apply make-minimum smallest-value-entry)))
minima)
(define (interpolate2 minima row-indices col-indices matrix-proc entry->value)
(define idx-of-last-col (sub1 (vector-length col-indices)))
(define (smallest-value-entry col idx-of-last-row)
(argmin (compose1 entry->value car)
(for/list ([row-idx (stop-after (in-vector row-indices) (curry = idx-of-last-row))])
(list (matrix-proc row-idx col) row-idx))))
(for ([(col col-idx) (in-indexed col-indices)] #:when (even? col-idx))
(define idx-of-last-row (if (= col-idx idx-of-last-col)
(vector-last row-indices)
(: (hash-ref minima (vector-ref col-indices (add1 col-idx))) 'row-idx)))
(! minima col (apply make-minimum (smallest-value-entry col idx-of-last-row))))
minima)
#|
Search for the minimum value in each column of a matrix.
The return value is a dictionary mapping ColIndices to pairs
(value,rowindex). We break ties in favor of earlier rows.
The matrix is defined implicitly as a function, passed
as the third argument to this routine, where Matrix(i,j)
gives the matrix value at row index i and column index j.
The matrix must be concave, that is, satisfy the property
Matrix(i,j) > Matrix(i',j) => Matrix(i,j') > Matrix(i',j')
for every i<i' and j<j'; that is, in every submatrix of
the input matrix, the positions of the column minima
must be monotonically nondecreasing.
The rows and columns of the matrix are labeled by the indices
given in order by the first two arguments. In most applications,
these arguments can simply be integer ranges.
|#
;; The return value `minima` is a hash:
;; the keys are col-indices (integers)
;; the values are pairs of (value row-index).
(require rackunit)
(define (concave-minima row-indices [col-indices null] [matrix-proc (make-caching-proc identity)] [entry->value identity])
;((vector?) ((or/c #f vector?) procedure? procedure?) . ->* . hash?)
(define reduce-proc reduce2)
(define interpolate-proc interpolate2)
(if (= 0 (vector-length col-indices))
(make-hash)
(let ([row-indices (reduce-proc row-indices col-indices matrix-proc entry->value)])
(define odd-column-minima (concave-minima row-indices (vector-odd-elements col-indices) matrix-proc entry->value))
(interpolate-proc odd-column-minima row-indices col-indices matrix-proc entry->value))))
#|
Online concave minimization algorithm of Galil and Park.
OnlineConcaveMinima(Matrix,initial) creates a sequence of pairs
(self.value(j),self.index(j)), where
self.value(0) = initial,
self.value(j) = min { Matrix(i,j) | i < j } for j > 0,
and where self.index(j) is the value of j that provides the minimum.
Matrix(i,j) must be concave, in the same sense as for ConcaveMinima.
We never call Matrix(i,j) until value(i) has already been computed,
so that the Matrix function may examine previously computed values.
Calling value(i) for an i that has not yet been computed forces
the sequence to be continued until the desired index is reached.
Calling iter(self) produces a sequence of (value,index) pairs.
Matrix(i,j) should always return a value, rather than raising an
exception, even for j larger than the range we expect to compute.
If j is out of range, a suitable value to return that will not
violate concavity is Matrix(i,j) = -i. It will not work correctly
to return a flag value such as None for large j, because the ties
formed by the equalities among such flags may violate concavity.
|#
;; Online Concave Minima object
;(struct $ocm (values indices finished matrix-proc base tentative) #:transparent #:mutable)
;; State used by self.value(), self.index(), and iter(self) =
;; $ocm-values, $ocm-indices, $ocm-finished
#|
State used by the internal algorithm:
$ocm-matrix, $ocm-base, $ocm-tentative
We allow self._values to be nonempty for indices > finished,
keeping invariant that
(1) self._values[i] = Matrix(self._indices[i], i),
(2) if the eventual correct value of self.index(i) < base,
then self._values[i] is nonempty and correct.
In addition, we keep a column index self._tentative, such that
(3) if i <= tentative, and the eventual correct value of
self.index(i) <= finished, then self._values[i] is correct.
|#
(define no-value 'none)
(define-syntax-rule (: hashtable key)
(hash-ref hashtable key))
(define-syntax-rule (! hashtable key value)
(hash-set! hashtable key value))
(define-syntax-rule (ocm-ref ocm key)
(vector-ref ocm key))
(define-syntax-rule (ocm-set! ocm key value)
(vector-set! ocm key value))
(define o:min-values 0)
(define o:min-row-indices 1)
(define o:finished 2)
(define o:matrix-proc 3)
(define o:entry->value 4)
(define o:base 5)
(define o:tentative 6)
(define (make-ocm matrix-proc [initial-value 0][entry->value identity])
(log-ocm-debug "making new ocm")
(define ocm (make-vector 7))
(ocm-set! ocm o:min-values (vector initial-value))
(ocm-set! ocm o:min-row-indices (vector no-value))
(ocm-set! ocm o:finished 0)
(ocm-set! ocm o:matrix-proc (make-caching-proc matrix-proc))
(ocm-set! ocm o:entry->value entry->value) ; for converting matrix values to an integer
(ocm-set! ocm o:base 0)
(ocm-set! ocm o:tentative 0)
ocm)
;; Return min { Matrix(i,j) | i < j }.
(define (min-value ocm j)
(if (< (ocm-ref ocm o:finished) j)
(begin (advance! ocm) (min-value ocm j))
(vector-ref (ocm-ref ocm o:min-values) j)))
;; Return argmin { Matrix(i,j) | i < j }.
(define (min-index ocm j)
(if (< (ocm-ref ocm o:finished) j)
(begin (advance! ocm) (min-index ocm j))
(vector-ref (ocm-ref ocm o:min-row-indices) j)))
;; Finish another value,index pair.
(define (advance! ocm)
(define next (add1 (ocm-ref ocm o:finished)))
(log-ocm-debug "advance! ocm to next = ~a" (add1 (ocm-ref ocm o:finished)))
(cond
;; First case: we have already advanced past the previous tentative
;; value. We make a new tentative value by applying ConcaveMinima
;; to the largest square submatrix that fits under the base.
[(> next (ocm-ref ocm o:tentative))
(log-ocm-debug "advance: first case because next (~a) > tentative (~a)" next (ocm-ref ocm o:tentative))
(define rows (list->vector (range (ocm-ref ocm o:base) next)))
(ocm-set! ocm o:tentative (+ (ocm-ref ocm o:finished) (vector-length rows)))
(define cols (list->vector (range next (add1 (ocm-ref ocm o:tentative)))))
(define minima (concave-minima rows cols (ocm-ref ocm o:matrix-proc) (ocm-ref ocm o:entry->value)))
(for ([col (in-vector cols)])
(cond
[(>= col (vector-length (ocm-ref ocm o:min-values)))
(ocm-set! ocm o:min-values (vector-append-item (ocm-ref ocm o:min-values) (: (: minima col) 'value)))
(ocm-set! ocm o:min-row-indices (vector-append-item (ocm-ref ocm o:min-row-indices) (: (: minima col) 'row-idx)))]
[(< ((ocm-ref ocm o:entry->value) (: (: minima col) 'value)) ((ocm-ref ocm o:entry->value) (vector-ref (ocm-ref ocm o:min-values) col)))
(ocm-set! ocm o:min-values (vector-set (ocm-ref ocm o:min-values) col (: (: minima col) 'value)))
(ocm-set! ocm o:min-row-indices (vector-set (ocm-ref ocm o:min-row-indices) col (: (: minima col) 'row-idx)))]))
(ocm-set! ocm o:finished next)]
[else
;; Second case: the new column minimum is on the diagonal.
;; All subsequent ones will be at least as low,
;; so we can clear out all our work from higher rows.
;; As in the fourth case, the loss of tentative is
;; amortized against the increase in base.
(define diag ((ocm-ref ocm o:matrix-proc) (sub1 next) next))
(cond
[(< ((ocm-ref ocm o:entry->value) diag) ((ocm-ref ocm o:entry->value) (vector-ref (ocm-ref ocm o:min-values) next)))
(log-ocm-debug "advance: second case because column minimum is on the diagonal")
(ocm-set! ocm o:min-values (vector-set (ocm-ref ocm o:min-values) next diag))
(ocm-set! ocm o:min-row-indices (vector-set (ocm-ref ocm o:min-row-indices) next (sub1 next)))
(ocm-set! ocm o:base (sub1 next))
(ocm-set! ocm o:tentative next)
(ocm-set! ocm o:finished next)]
;; Third case: row i-1 does not supply a column minimum in
;; any column up to tentative. We simply advance finished
;; while maintaining the invariant.
[(>= ((ocm-ref ocm o:entry->value) ((ocm-ref ocm o:matrix-proc) (sub1 next) (ocm-ref ocm o:tentative)))
((ocm-ref ocm o:entry->value) (vector-ref (ocm-ref ocm o:min-values) (ocm-ref ocm o:tentative))))
(log-ocm-debug "advance: third case because row i-1 does not suppply a column minimum")
(ocm-set! ocm o:finished next)]
;; Fourth and final case: a new column minimum at self._tentative.
;; This allows us to make progress by incorporating rows
;; prior to finished into the base. The base invariant holds
;; because these rows cannot supply any later column minima.
;; The work done when we last advanced tentative (and undone by
;; this step) can be amortized against the increase in base.
[else
(log-ocm-debug "advance: fourth case because new column minimum")
(ocm-set! ocm o:base (sub1 next))
(ocm-set! ocm o:tentative next)
(ocm-set! ocm o:finished next)])]))
(define (print ocm)
(displayln (ocm-ref ocm o:min-values))
(displayln (ocm-ref ocm o:min-row-indices)))
(define (smawky? m)
(define (position-of-minimum xs)
;; put each element together with its list index
(let ([xs (map cons (range (length xs)) xs)])
;; find the first one with the min value, and grab the list index
(car (argmin cdr (filter (compose1 not negative? cdr) xs)))))
;; tests if penalty matrix is monotone for non-negative values.
(define increasing-minima? (apply <= (map position-of-minimum m)))
(define monotone?
(for*/and ([ridx (in-range 1 (length m))]
[cidx (in-range (sub1 (length (car m))))])
(let* ([prev-row (list-ref m (sub1 ridx))]
[row (list-ref m ridx)]
[a (list-ref prev-row cidx)]
[b (list-ref prev-row (add1 cidx))]
[c (list-ref row cidx)]
[d (list-ref row (add1 cidx))])
(if (andmap (compose1 not negative?) (list a b c d)) ;; smawk disregards negative values
(cond
[(< c d) (if (< a b) #t (error (format "Submatrix ~a not monotone in ~a" (list (list a b) (list c d)) m)))]
[(= c d) (if (<= a b) #t (error (format "Submatrix ~a not monotone in ~a" (list (list a b) (list c d)) m)))]
[else #t])
#t))))
(and increasing-minima? monotone?))
(module+ test
(require rackunit)
(define m '((25 42 57 78 90 103 123 142 151)
(21 35 48 65 76 85 105 123 130)
(13 26 35 51 58 67 86 100 104)
(10 20 28 42 48 56 75 86 88)
(20 29 33 44 49 55 73 82 80)
(13 21 24 35 39 44 59 65 59)
(19 25 28 38 42 44 57 61 52)
(35 37 40 48 48 49 62 62 49)
(37 36 37 42 39 39 51 50 37)
(41 39 37 42 35 33 44 43 29)
(58 56 54 55 47 41 50 47 29)
(66 64 61 61 51 44 52 45 24)
(82 76 72 70 56 49 55 46 23)
(99 91 83 80 63 56 59 46 20)
(124 116 107 100 80 71 72 58 28)
(133 125 113 106 86 75 74 59 25)
(156 146 131 120 97 84 80 65 31)
(178 164 146 135 110 96 92 73 39)))
(define m2 (apply map list m))
(check-true (smawky? m))
(check-true (smawky? m2))
;; proc must return a value even for out-of-bounds i and j
(define (simple-proc i j) (with-handlers [(exn:fail? (λ(exn) (* -1 i)))]
(list-ref (list-ref m i) j)))
(define (simple-proc2 i j) (with-handlers [(exn:fail? (λ(exn) (* -1 i)))]
(list-ref (list-ref m2 i) j)))
(check-equal? (simple-proc 0 2) 57) ; 0th row, 2nd col
(check-equal? (simple-proc2 2 0) 57) ; flipped
(define o (make-ocm simple-proc))
(define row-indices (list->vector (range (length m))))
(define col-indices (list->vector (range (length (car m)))))
(define result (concave-minima row-indices col-indices simple-proc identity))
(check-equal?
(for/list ([j (in-vector col-indices)])
(define h (hash-ref result j))
(list (hash-ref h 'value) (hash-ref h 'row-idx)))
'((10 3) (20 3) (24 5) (35 5) (35 9) (33 9) (44 9) (43 9) (20 13))) ; checked against SMAWK.py
(check-equal?
(for/list ([j (in-vector col-indices)])
(list (min-value o j) (min-index o j)))
'((0 none) (42 0) (48 1) (51 2) (48 3) (55 4) (59 5) (61 6) (49 7))) ; checked against SMAWK.py
(define o2 (make-ocm simple-proc2))
(define row-indices2 (list->vector (range (length m2))))
(define col-indices2 (list->vector (range (length (car m2)))))
(define result2 (concave-minima row-indices2 col-indices2 simple-proc2 identity))
(check-equal?
(for/list ([j (in-vector col-indices2)])
(define h (hash-ref result2 j))
(list (hash-ref h 'value) (hash-ref h 'row-idx)))
'((25 0) (21 0) (13 0) (10 0) (20 0) (13 0) (19 0) (35 0) (36 1) (29 8) (29 8) (24 8) (23 8) (20 8) (28 8) (25 8) (31 8) (39 8))) ; checked against SMAWK.py
(check-equal?
(for/list ([j (in-vector col-indices2)])
(list (min-value o2 j) (min-index o2 j)))
'((0 none) (21 0) (13 0) (10 0) (20 0) (13 0) (19 0) (35 0) (36 1) (29 8) (-9 9) (-10 10) (-11 11) (-12 12) (-13 13) (-14 14) (-15 15) (-16 16))) ; checked against SMAWK.py
)

@ -1,192 +0,0 @@
"""SMAWK.py
Totally monotone matrix searching algorithms.
The offline algorithm in ConcaveMinima is from Agarwal, Klawe, Moran,
Shor, and Wilbur, Geometric applications of a matrix searching algorithm,
Algorithmica 2, pp. 195-208 (1987).
The online algorithm in OnlineConcaveMinima is from Galil and Park,
A linear time algorithm for concave one-dimensional dynamic programming,
manuscript, 1989, which simplifies earlier work on the same problem
by Wilbur (J. Algorithms 1988) and Eppstein (J. Algorithms 1990).
D. Eppstein, March 2002, significantly revised August 2005
"""
def ConcaveMinima(RowIndices,ColIndices,Matrix):
"""
Search for the minimum value in each column of a matrix.
The return value is a dictionary mapping ColIndices to pairs
(value,rowindex). We break ties in favor of earlier rows.
The matrix is defined implicitly as a function, passed
as the third argument to this routine, where Matrix(i,j)
gives the matrix value at row index i and column index j.
The matrix must be concave, that is, satisfy the property
Matrix(i,j) > Matrix(i',j) => Matrix(i,j') > Matrix(i',j')
for every i<i' and j<j'; that is, in every submatrix of
the input matrix, the positions of the column minima
must be monotonically nondecreasing.
The rows and columns of the matrix are labeled by the indices
given in order by the first two arguments. In most applications,
these arguments can simply be integer ranges.
"""
# Base case of recursion
if not ColIndices: return {}
# Reduce phase: make number of rows at most equal to number of cols
stack = []
for r in RowIndices:
while len(stack) >= 1 and \
Matrix(stack[-1], ColIndices[len(stack)-1]) \
> Matrix(r, ColIndices[len(stack)-1]):
stack.pop()
if len(stack) != len(ColIndices):
stack.append(r)
RowIndices = stack
# Recursive call to search for every odd column
minima = ConcaveMinima(RowIndices,
[ColIndices[i] for i in range(1,len(ColIndices),2)],
Matrix)
# Go back and fill in the even rows
r = 0
for c in range(0,len(ColIndices),2):
col = ColIndices[c]
row = RowIndices[r]
if c == len(ColIndices) - 1:
lastrow = RowIndices[-1]
else:
lastrow = minima[ColIndices[c+1]][1]
pair = (Matrix(row,col),row)
while row != lastrow:
r += 1
row = RowIndices[r]
pair = min(pair,(Matrix(row,col),row))
minima[col] = pair
return minima
class OnlineConcaveMinima:
"""
Online concave minimization algorithm of Galil and Park.
OnlineConcaveMinima(Matrix,initial) creates a sequence of pairs
(self.value(j),self.index(j)), where
self.value(0) = initial,
self.value(j) = min { Matrix(i,j) | i < j } for j > 0,
and where self.index(j) is the value of j that provides the minimum.
Matrix(i,j) must be concave, in the same sense as for ConcaveMinima.
We never call Matrix(i,j) until value(i) has already been computed,
so that the Matrix function may examine previously computed values.
Calling value(i) for an i that has not yet been computed forces
the sequence to be continued until the desired index is reached.
Calling iter(self) produces a sequence of (value,index) pairs.
Matrix(i,j) should always return a value, rather than raising an
exception, even for j larger than the range we expect to compute.
If j is out of range, a suitable value to return that will not
violate concavity is Matrix(i,j) = -i. It will not work correctly
to return a flag value such as None for large j, because the ties
formed by the equalities among such flags may violate concavity.
"""
def __init__(self,Matrix,initial):
"""Initialize a OnlineConcaveMinima object."""
# State used by self.value(), self.index(), and iter(self)
self._values = [initial] # tentative solution values...
self._indices = [None] # ...and their indices
self._finished = 0 # index of last non-tentative value
# State used by the internal algorithm
#
# We allow self._values to be nonempty for indices > finished,
# keeping invariant that
# (1) self._values[i] = Matrix(self._indices[i], i),
# (2) if the eventual correct value of self.index(i) < base,
# then self._values[i] is nonempty and correct.
#
# In addition, we keep a column index self._tentative, such that
# (3) if i <= tentative, and the eventual correct value of
# self.index(i) <= finished, then self._values[i] is correct.
#
self._matrix = Matrix
self._base = 0
self._tentative = 0
def __str__(self):
return "%s" % self._values
def __iter__(self):
"""Loop through (value,index) pairs."""
i = 0
while True:
yield self.value(i),self.index(i)
i += 1
def value(self,j):
"""Return min { Matrix(i,j) | i < j }."""
while self._finished < j:
self._advance()
return self._values[j]
def index(self,j):
"""Return argmin { Matrix(i,j) | i < j }."""
while self._finished < j:
self._advance()
return self._indices[j]
def _advance(self):
"""Finish another value,index pair."""
# First case: we have already advanced past the previous tentative
# value. We make a new tentative value by applying ConcaveMinima
# to the largest square submatrix that fits under the base.
i = self._finished + 1
if i > self._tentative:
rows = range(self._base,self._finished+1)
self._tentative = self._finished+len(rows)
cols = range(self._finished+1,self._tentative+1)
minima = ConcaveMinima(rows,cols,self._matrix)
for col in cols:
if col >= len(self._values):
self._values.append(minima[col][0])
self._indices.append(minima[col][1])
elif minima[col][0] < self._values[col]:
self._values[col],self._indices[col] = minima[col]
self._finished = i
return
# Second case: the new column minimum is on the diagonal.
# All subsequent ones will be at least as low,
# so we can clear out all our work from higher rows.
# As in the fourth case, the loss of tentative is
# amortized against the increase in base.
diag = self._matrix(i-1,i)
if diag < self._values[i]:
self._values[i] = diag
self._indices[i] = self._base = i-1
self._tentative = self._finished = i
return
# Third case: row i-1 does not supply a column minimum in
# any column up to tentative. We simply advance finished
# while maintaining the invariant.
if self._matrix(i-1,self._tentative) >= self._values[self._tentative]:
self._finished = i
return
# Fourth and final case: a new column minimum at self._tentative.
# This allows us to make progress by incorporating rows
# prior to finished into the base. The base invariant holds
# because these rows cannot supply any later column minima.
# The work done when we last advanced tentative (and undone by
# this step) can be amortized against the increase in base.
self._base = i-1
self._tentative = self._finished = i
return

Binary file not shown.

@ -1,66 +0,0 @@
import SMAWK
m1 = [
[25, 42, 57, 78, 90, 103, 123, 142, 151],
[21, 35, 48, 65, 76, 85, 105, 123, 130],
[13, 26, 35, 51, 58, 67, 86, 100, 104],
[10, 20, 28, 42, 48, 56, 75, 86, 88],
[20, 29, 33, 44, 49, 55, 73, 82, 80],
[13, 21, 24, 35, 39, 44, 59, 65, 59],
[19, 25, 28, 38, 42, 44, 57, 61, 52],
[35, 37, 40, 48, 48, 49, 62, 62, 49],
[37, 36, 37, 42, 39, 39, 51, 50, 37],
[41, 39, 37, 42, 35, 33, 44, 43, 29],
[58, 56, 54, 55, 47, 41, 50, 47, 29],
[66, 64, 61, 61, 51, 44, 52, 45, 24],
[82, 76, 72, 70, 56, 49, 55, 46, 23],
[99, 91, 83, 80, 63, 56, 59, 46, 20],
[124, 116, 107, 100, 80, 71, 72, 58, 28],
[133, 125, 113, 106, 86, 75, 74, 59, 25],
[156, 146, 131, 120, 97, 84, 80, 65, 31],
[178, 164, 146, 135, 110, 96, 92, 73, 39]]
num_rows = len(m1)
row_indices = range(num_rows)
num_cols = len(m1[0])
col_indices = range(num_cols)
def matrix_func1(i, j):
try:
return m1[i][j]
except:
return -1 * i
def matrix_func2(i, j):
try:
return m1[j][i]
except:
return -1 * i
cm_hash = SMAWK.ConcaveMinima(row_indices, col_indices, matrix_func1)
cm_hash2 = SMAWK.ConcaveMinima(col_indices, row_indices, matrix_func2)
print cm_hash2
'''
{0: (10, 3), 1: (20, 3), 2: (24, 5), 3: (35, 5), 4: (35, 9), 5: (33, 9), 6: (44, 9), 7: (43, 9), 8: (20, 13)}
'''
ocm = SMAWK.OnlineConcaveMinima(matrix_func1, 0)
ocm2 = SMAWK.OnlineConcaveMinima(matrix_func2, 0)
ocm_hash = dict()
for j in col_indices:
ocm_hash[j] = (ocm.value(j), ocm.index(j))
ocm_hash2 = dict()
for j in row_indices:
ocm_hash2[j] = (ocm2.value(j), ocm2.index(j))
print ocm_hash2
'''
{0: (0, None), 1: (42, 0), 2: (48, 1), 3: (51, 2), 4: (48, 3), 5: (55, 4), 6: (59, 5), 7: (61, 6), 8: (49, 7)}'''

@ -1,239 +0,0 @@
#lang racket/base
(require (for-syntax racket/base racket/syntax racket/string) racket/string racket/contract racket/serialize sugar/list racket/format racket/list sugar/debug sugar/coerce racket/bool racket/function sugar/unstable/string)
(require "world.rkt")
(provide (all-defined-out))
;; struct implementation
(serializable-struct quad (name attrs list) #:transparent
#:methods gen:custom-write
[(define write-proc (λ(b port mode)
(display (format "(~a)" (string-join (filter-not void? (list
(~a (quad-name b))
(if (and (hash? (quad-attrs b)) (> (length (hash-keys (quad-attrs b))) 0)) (~v (flatten (hash->list (quad-attrs b)))) "#f")
(if (> (length (quad-list b)) 0) (~a (string-join (map ~v (quad-list b)) " ")) (void)))) " ")) port)))]
#:property prop:sequence (λ(q) (quad-list q)))
;; vector implementation
#|
(define (quad-name q) (vector-ref q 0))
(define (quad-attrs q) (vector-ref q 1))
(define (quad-list q) (vector-ref q 2))
(define (quad? x)
(and (vector? x)
(symbol? (quad-name x))
(or (false? (quad-attrs x)) (hash? (quad-attrs x)))
(list? (quad-list x))))
(define (quad name attrs xs)
(vector name attrs xs))
|#
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; hash implementation
#|
(define (quad-name q) (hash-ref q 'name))
(define (quad-attrs q) (hash-ref q 'attrs))
(define (quad-list q) (hash-ref q 'list))
(define (quad? x)
(and (hash? x)
(andmap (λ(k) (hash-has-key? x k)) (list 'name 'attrs 'list))
(symbol? (quad-name x))
(ormap (λ(pred) (pred (quad-attrs x))) (list false? hash?))
(list? (quad-list x))))
(define (quad name attrs xs)
(hash 'name name 'attrs attrs 'list xs))
|#
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (quad-name? x) (symbol? x))
(define (hashable-list? x) (and (list? x) (even? (length x))))
(define (quad-attrs? x) (or (false? x) (hash? x)))
(define (quad-list? x) (and (list? x) (andmap (λ(xi) (or (quad? xi) (and (string? xi) (< 0 (string-length xi))))) x)))
(define (quads? x) (and (list? x) (andmap quad? x)))
(define (lists-of-quads? x) (and (list? x) (andmap quads? x)))
(define quad= equal?)
(define token? quad?)
(define (quad/c x) (λ(x) (and (quad? x) (symbol? (quad-name x)) (hash? (quad-attrs x))
(andmap (λ(xi) (or (quad/c xi) (string? xi))) (quad-list x)))))
(define quad-attr-ref
(case-lambda
[(q key)
(if (quad-attrs q)
(hash-ref (quad-attrs q) key)
(error 'quad-attr-ref (format "no attrs in quad ~a" q)))]
[(q key default)
(if (quad-attrs q)
(hash-ref (quad-attrs q) key default)
default)]))
(define-syntax (quad-attr-ref/parameter stx)
(syntax-case stx ()
[(_ q key)
(with-syntax ([world:key-default (format-id stx "~a-default" (string-trim (symbol->string (syntax->datum #'key)) "-key"))])
#'(quad-attr-ref q key (world:key-default)))]))
(define (quad-has-attr? q key)
(define qa (quad-attrs q))
(and qa (hash-has-key? qa key)))
(define-syntax (define-quad-list-function stx)
(syntax-case stx ()
[(_ proc)
(with-syntax ([quad-proc (format-id stx "quad-~a" #'proc)])
#'(define (quad-proc q) (proc (quad-list q))))]))
(define-quad-list-function first)
(define-quad-list-function car)
(define-quad-list-function cdr)
(define-quad-list-function last)
(define (quad-cons item q)
(quad (quad-name q) (quad-attrs q) (cons item (quad-list q))))
(define-syntax-rule (quad-ref q r)
(list-ref (quad-list q) r))
(define (quad-ends-with? q str)
(quad? string? . -> . boolean?)
(cond
[(not (empty? (quad-list q)))
(define last-item (last (quad-list q)))
(cond
[(string? last-item) (ends-with? last-item str)]
[(quad? last-item) (quad-ends-with? last-item str)])]
[else #f]))
(define (quad-append q new-item)
(quad? (or/c quad? string?) . -> . quad?)
(quad (quad-name q) (quad-attrs q) (append (quad-list q) (list new-item))))
(define (quad->string x)
(quad? . -> . string?)
(cond
[(quad? x) (string-append* (map quad->string (quad-list x)))]
[(string? x) x]
[else ""]))
(define-syntax-rule (report-quadstring q)
(begin
(report (quad->string q) 'q)
q))
(define cannot-be-common-attrs '(width x y page)) ;; todo: how to specify these better? this-* prefix?
;; make this a macro because qs-in is often huge
;; and the macro avoids allocation + garbage collection
(define attr-missing (gensym))
(define (gather-common-attrs qs)
(let loop ([qs qs]
[common-attrs (if (quad-attrs (car qs))
(for/list ([kv-pair (in-hash-pairs (quad-attrs (car qs)))]
#:unless (member (car kv-pair) cannot-be-common-attrs))
kv-pair)
empty)])
(cond
[(empty? common-attrs) empty]
[(empty? qs) (flatten common-attrs)]
[else (loop (cdr qs)
(filter (λ(ca) (equal? (quad-attr-ref (car qs) (car ca) attr-missing) (cdr ca)))
common-attrs))])))
(define-syntax (define-box-type stx)
(syntax-case stx ()
[(_ id)
(with-syntax ([id? (format-id #'id "~a?" #'id)]
[ids? (format-id #'id "~as?" #'id)]
[lists-of-ids? (format-id #'id "list-of-~as?" #'id)]
[quads->id (format-id #'id "quads->~a" #'id)]
[inline/quads->id (format-id #'id "inline/quads->~a" #'id)])
#'(begin
;; quad predicate - ok to be relaxed here if we're strict when making the struct
(define (id? x)
(and (quad? x) (equal? (quad-name x) 'id)))
;; quad constructor
;; put contract here rather than on struct, because this is the main interface
;; and this contract is more liberal.
;; but don't put a separate contract on struct, because it's superfluous.
(define (id [attrs empty] . xs)
(() ((or/c quad-attrs? hashable-list?)) #:rest quad-list? . ->* . id?)
(quad 'id (and attrs (if (hash? attrs) attrs (apply hash attrs))) xs))
;; quad list predicate and list-of-list predicate.
;; These are faster than the listof contract combinator.
(define (ids? x)
(and (list? x) (andmap id? x)))
(define (lists-of-ids? x)
(and (list? x) (andmap ids? x)))
;; quad converter macro
(define (quads->id qs)
(apply id (gather-common-attrs qs) qs))))]))
;; do not treat empty string as whitespace.
;; throws off tests that rely on adjacency to positive whitespace.
(define (whitespace? x [nbsp? #f])
((any/c)(boolean?) . ->* . coerce/boolean?)
(cond
[(quad? x) (whitespace? (quad-list x) nbsp?)]
[(string? x) (or (and (regexp-match #px"\\p{Zs}" x) ; Zs = unicode whitespace category
(or nbsp? (not (regexp-match #px"\u00a0" x)))))] ; 00a0: nbsp
[(list? x) (and (not (empty? x)) (andmap (curryr whitespace? nbsp?) x))] ; andmap returns #t for empty lists
[else #f]))
(define (whitespace/nbsp? x)
(whitespace? x #t))
(define-syntax (define-break-type stx)
(syntax-case stx ()
[(_ id)
(with-syntax ([split-on-id-breaks (format-id #'id "split-on-~a-breaks" #'id)]
[id-break (format-id #'id "~a-break" #'id)]
[id-break? (format-id #'id "~a-break?" #'id)]
[multi-id (format-id #'id "multi~a" #'id)]
[multi-id? (format-id #'id "multi~a?" #'id)]
[quads->multi-id (format-id #'id "quads->multi~a" #'id)])
#'(begin
(define-box-type id)
(define-box-type id-break)
(define-box-type multi-id)
;; breaker
(define (split-on-id-breaks x)
(quads? . -> . lists-of-quads?)
;; omit leading & trailing whitespace, because they're superfluous next to a break
(map (curryr trimf whitespace?) (filter-split x id-break?)))))]))
(define-box-type box)
(define-break-type word)
(define (word-string c) (car (quad-list c)))
(define-box-type spacer)
(define-box-type kern)
(define-box-type optical-kern)
(define-box-type flag)
(define-box-type doc)
(define-box-type input)
(define-box-type piece)
(define-box-type run)
(define-break-type page)
(define-break-type column)
(define-break-type block)
(define-break-type line)
(define (->input q) (input empty q))
(define coerce/input? (make-coercion-contract input))

@ -1,6 +0,0 @@
#lang typed/racket/base/no-check
(require "quads.rkt" racket/file racket/string racket/function racket/list racket/include)
(provide quick-sample)
(define quick-sample
(block '(measure 480.0 font "Times New Roman" leading 16.0 vmeasure 300.0 size 13.5 x-align justify x-align-last-line left) (box '(width 15.0)) (block '() (block '(weight bold) "Hot " (word '(size 22.0) "D") "ang, My Fellow Americans.") " This " (block '(no-break #t) "is some truly") " nonsense generated from my typesetting system, which is called Quad. Im writing this in a source file in DrRacket. When I click [Run], a PDF pops out. Not bad\u200a\u200aand no LaTeX needed. Quad, however, does use the fancy linebreaking algorithm developed for TeX. (It also includes a faster linebreaking algorithm for when speed is more important than quality.) Of course, it can also handle " (block '(font "Courier") "different fonts,") (block '(style italic) " styles, ") (word '(size 14.0 weight bold) "and sizes-") " within the same line. As you can see, it can also justify paragraphs." (block-break) (box '(width 15.0)) (block '() "“Each horizontal row represents " (box '(color "Red" background "Yellow") "an OS-level thread,") " and the colored dots represent important events in the execution of the program (they are color-coded to distinguish one event type from another). The upper-left blue dot in the timeline represents the futures creation. The future executes for a brief period (represented by a green bar in the second line) on thread 1, and then pauses to allow the runtime thread to perform a future-unsafe operation.") (column-break) (box '(width 15.0))(block '() "In the Racket implementation, future-unsafe operations fall into one of two categories. A blocking operation halts the evaluation of the future, and will not allow it to continue until it is touched. After the operation completes within touch, the remainder of the futures work will be evaluated sequentially by the runtime thread. A synchronized operation also halts the future, but the runtime thread may perform the operation at any time and, once completed, the future may continue running in parallel. Memory allocation and JIT compilation are two common examples of synchronized operations." (page-break) "another page"))))

@ -1,2 +0,0 @@
(define (quick-sample)
(block '(measure 240.0 font "Times New Roman" leading 16.0 vmeasure 300.0 size 13.5 x-align justify x-align-last-line left) (box '(width 15.0)) (block '() (block '(weight bold) "Hot " (word '(size 22.0) "D") "ang, My Fellow Americans.") " This " (block '(no-break #t) "is some truly") " nonsense generated from my typesetting system, which is called Quad. Im writing this in a source file in DrRacket. When I click [Run], a PDF pops out. Not bad\u200a\u200aand no LaTeX needed. Quad, however, does use the fancy linebreaking algorithm developed for TeX. (It also includes a faster linebreaking algorithm for when speed is more important than quality.) Of course, it can also handle " (block '(font "Courier") "different fonts,") (block '(style italic) " styles, ") (word '(size 14.0 weight bold) "and sizes-") " within the same line. As you can see, it can also justify paragraphs." (block-break) (box '(width 15.0)) (block '() "“Each horizontal row represents " (box '(color "Red" background "Yellow") "an OS-level thread,") " and the colored dots represent important events in the execution of the program (they are color-coded to distinguish one event type from another). The upper-left blue dot in the timeline represents the futures creation. The future executes for a brief period (represented by a green bar in the second line) on thread 1, and then pauses to allow the runtime thread to perform a future-unsafe operation.") (column-break) (box '(width 15.0))(block '() "In the Racket implementation, future-unsafe operations fall into one of two categories. A blocking operation halts the evaluation of the future, and will not allow it to continue until it is touched. After the operation completes within touch, the remainder of the futures work will be evaluated sequentially by the runtime thread. A synchronized operation also halts the future, but the runtime thread may perform the operation at any time and, once completed, the future may continue running in parallel. Memory allocation and JIT compilation are two common examples of synchronized operations." (page-break) "another page"))))

@ -1,10 +0,0 @@
#lang racket/base
(require "typeset.rkt" "world.rkt" "quick-sample.rkt"
"render.rkt" racket/class quad/quads)
(parameterize ([world:quality-default world:draft-quality])
(displayln "Untyped Quad")
(displayln "Typesetting:")
(define to (time (typeset (dynamic-require "foo.rkt" 'ts))))
(displayln "PDF rendering:")
(time (send (new pdf-renderer%) render-to-file to "quick-test-untyped.pdf")))

@ -1,106 +0,0 @@
#lang racket/base
(require racket/class racket/contract sugar/debug sugar/cache racket/list racket/file racket/draw data/gvector)
(require "utils.rkt" "quads.rkt" "world.rkt")
(provide (all-defined-out))
(define abstract-renderer%
#;(class/c [render (quad? . ->m . any/c)]
[render-element (quad? . ->m . quad?)])
(class object%
(super-new)
(define renderable-quads '(word box))
;; hash implementation
(define/public (render0 doc-quad)
(finalize
(let ([rendering-input (flatten-quad (setup doc-quad))])
(define page-quad-hash (make-hash))
(for ([q (in-list rendering-input)])
(when (member (quad-name q) renderable-quads)
(hash-update! page-quad-hash (quad-attr-ref q world:page-key) (λ(v) (cons q v)) null)))
(map (λ(k) (render-page (hash-ref page-quad-hash k))) (sort (hash-keys page-quad-hash) <)))))
;; gvector implementation
(define/public (render doc-quad)
(finalize
(let ([rendering-input (flatten-quad (setup doc-quad))])
(define page-quad-vector (make-gvector))
(for ([q (in-list rendering-input)] #:when (member (quad-name q) renderable-quads))
(define p (quad-attr-ref q world:page-key))
(gvector-set! page-quad-vector p (cons q (gvector-ref page-quad-vector p null))))
(for/list ([pq (in-gvector page-quad-vector)])
(render-page pq)))))
(define/public (render-element q)
(cond
[(word? q) (render-word q)]
[else q]))
(define/public (setup q) q)
(abstract render-page)
(abstract render-word)
(define/public (finalize q) q)))
(define-syntax-rule (map/send method xs)
(map (λ(x) (method x)) xs))
(define pdf-renderer%
(class abstract-renderer%
(super-new)
(send* (current-ps-setup) (set-margin 0 0) (set-scaling 1.0 1.0))
(define dc-output-port (open-output-bytes))
(define dc (new pdf-dc% [interactive #f][use-paper-bbox #f][as-eps #f]
[output dc-output-port]
[width (world:paper-width-default)][height (world:paper-height-default)]))
(define/override (setup tx)
(send* dc
(start-doc "boing")
(set-pen "black" 1 'solid)
(set-brush "black" 'transparent)) ; no fill by default
tx)
(inherit render-element)
(define/caching (make-font/caching font size style weight)
(make-font #:face font #:size size #:style style #:weight weight))
(define/override-final (render-word w)
(define word-font (quad-attr-ref/parameter w world:font-name-key))
(define word-size (quad-attr-ref/parameter w world:font-size-key))
(define word-style (quad-attr-ref/parameter w world:font-style-key))
(define word-weight (quad-attr-ref/parameter w world:font-weight-key))
(define word-color (quad-attr-ref/parameter w world:font-color-key))
(define word-background (quad-attr-ref/parameter w world:font-background-key))
(send dc set-font (make-font/caching word-font word-size word-style word-weight))
(send dc set-text-foreground (send the-color-database find-color word-color))
(define background-color (send the-color-database find-color word-background))
(if background-color ; all invalid color-string values will return #f
(send* dc (set-text-mode 'solid) (set-text-background background-color))
(send dc set-text-mode 'transparent))
(define word-text (quad-car w))
(send dc draw-text word-text (quad-attr-ref w world:x-position-key)
;; we want to align by baseline rather than top of box
;; thus, subtract ascent from y to put baseline at the y coordinate
(- (quad-attr-ref w world:y-position-key) (quad-attr-ref w world:ascent-key 0)) #t))
(define/override-final (render-page elements)
(send dc start-page)
(map/send render-element (filter-not whitespace/nbsp? elements))
(send dc end-page))
(define/override-final (finalize xs)
(send dc end-doc)
(get-output-bytes dc-output-port))
(define/public (render-to-file doc-quad path)
(define result-bytes (send this render doc-quad))
(display-to-file result-bytes path #:exists 'replace #:mode 'binary))))

@ -1,30 +0,0 @@
;(define ti (block '(measure 54.0 leading 18.0) "Meg is an ally."))
(define (ti2) (block '(leading 10.0 measure 400.0 size 13.0 x-align left x-align-last-line left font "Equity Text B") (block '() "Foo-d" (word '(size 13.0) "og ") "and " (box) "Zu" (word-break '(nb "c" bb "k-")) "kermans. Instead of a circle, the result is a picture of the code that, if it were used as an expression, would produce a circle. In other words, code is not a function, but instead a " (block '(style italic) "new syntactic form") " for creating pictures; the bit between the opening " (block '(weight bold) "parenthesis") " with code is not an expression, but instead manipulated by the code syntactic form. " (word '(font "Triplicate T4" size 22.5 color "Orchid" background "Yellow") "Bangazoom!") " This helps explain what we meant in the previous section when we said that racket provides require and the function-calling syntax.") (block-break) (block '() "Libraries are not restricted to exporting values, such as functions; they can also define new syntactic forms. In this sense, Racket isnt exactly a language at all; its more of an idea for how to structure a language so that you can extend it or create entirely new ones.")))
(define (ti3) (block '(measure 54.0 leading 18.0) "Meg is an ally."))
(define (ti4) (block '(measure 300.0 x-align justify x-align-last-line right leading 18.0) "In this Madagascarian hoo-ha, Racket isnt exactly a language at all"))
(define (ti5) (block '(measure 240.0 font "Equity Text B" leading 16.0 vmeasure 300.0 size 13.5 x-align justify x-align-last-line left) (box '(width 15.0)) (block '() (block '(weight bold font "Equity Caps B") "Hot" (word '(size 22.0) "Z") "ogs, My Fellow Americans.") " This " (block '(no-break #t) "is some truly") " bullshit generated from my typesetting system, which is called Quad. Im writing this in a source file in DrRacket. When I click [Run], a PDF pops out. Not bad\u200a\u200aand no LaTeX needed. Quad, however, does use the fancy linebreaking algorithm developed for TeX. (It also includes a faster linebreaking algorithm for when speed is more important than quality.) Of course, it can also handle " (block '(font "Triplicate C4") "different fonts,") (block '(style italic) " styles, ") (word '(size 14.0 weight bold) "and sizes-") " within the same line. As you can see, it can also justify paragraphs." (block-break) (box '(width 15.0)) (block '() "“Each horizontal row represents an OS-level thread, and the colored dots represent important events in the execution of the program (they are color-coded to distinguish one event type from another). The upper-left blue dot in the timeline represents the futures creation. The future executes for a brief period (represented by a green bar in the second line) on thread 1, and then pauses to allow the runtime thread to perform a future-unsafe operation.") (column-break) (box '(width 15.0))(block '() "In the Racket implementation, future-unsafe operations fall into one of two categories. A blocking operation halts the evaluation of the future, and will not allow it to continue until it is touched. After the operation completes within touch, the remainder of the futures work will be evaluated sequentially by the runtime thread. A synchronized operation also halts the future, but the runtime thread may perform the operation at any time and, once completed, the future may continue running in parallel. Memory allocation and JIT compilation are two common examples of synchronized operations." (page-break) "another page"))))
(define (ti6) (block '(font "Equity Text B" measure 210.0 leading 14.0 size 20.0 x-align justify x-align-last-line left)
"Firstlinerhere" (column-break) "Secondlinerhere" (column-break) "Thirdlinerhere"))
(: make-sample : ((String) ((Option Integer)) . ->* . Quad))
(define (make-sample jude-text [line-limit #f])
(define sample-string : String
(if line-limit
(let ([lines : (Listof String) (file->lines jude-text)])
(string-join (take lines (min line-limit (length lines))) "\n"))
(file->string jude-text)))
(define jude-blocks ((inst map String String) (λ(s) (regexp-replace* #rx"\n" s " ")) (string-split sample-string "\n\n")))
(apply block '(font "Equity Text B" measure 360.0 leading 14.0 column-count 1 column-gutter 10.0 size 11.5 x-align justify x-align-last-line left) (add-between (map (λ([jb : String]) (block '() (box '(width 10.0)) (optical-kern) jb)) (filter (λ([jb : String]) (< 0 (string-length jb))) jude-blocks)) (block-break))))
(define (jude) (make-sample "texts/jude.txt"))
(define (jude0) (make-sample "texts/jude0.txt"))
(define (judebig) (make-sample "texts/judebig.txt"))
(define (segfault) (make-sample "texts/segfault.txt"))
(define (jude1) (block '(font "Equity Text B" measure 150.0 leading 14.0 column-count 4 size 11.0 x-align justify x-align-last-line left) "this—is—a—test—of—em—dashes—breakable—or—not?"))

@ -1,4 +0,0 @@
#lang typed/racket/base/no-check
(require "quads.rkt" racket/file racket/string racket/function racket/list racket/include)
(provide (all-defined-out))
(include "samples-base.rktd")

File diff suppressed because one or more lines are too long

@ -1,319 +0,0 @@
/* See the beginning of "manual.css". */
/* Monospace: */
.RktIn, .RktRdr, .RktPn, .RktMeta,
.RktMod, .RktKw, .RktVar, .RktSym,
.RktRes, .RktOut, .RktCmt, .RktVal,
.RktBlk, .RktErr {
font-family: 'Source Code Pro', monospace;
white-space: inherit;
font-size: 1rem;
}
/* this selctor grabs the first linked Racket symbol
in a definition box (i.e., the symbol being defined) */
a.RktValDef, a.RktStxDef, a.RktSymDef,
span.RktValDef, span.RktStxDef, span.RktSymDef
{
font-size: 1.15rem;
color: black;
font-weight: 600;
}
.inheritedlbl {
font-family: 'Fira', sans;
}
.RBackgroundLabelInner {
font-family: inherit;
}
/* ---------------------------------------- */
/* Inherited methods, left margin */
.inherited {
width: 95%;
margin-top: 0.5em;
text-align: left;
background-color: inherit;
}
.inherited td {
font-size: 82%;
padding-left: 0.5rem;
line-height: 1.3;
text-indent: 0;
padding-right: 0;
}
.inheritedlbl {
font-style: normal;
}
/* ---------------------------------------- */
/* Racket text styles */
.RktIn {
color: #cc6633;
background-color: #eee;
}
.RktInBG {
background-color: #eee;
}
.refcolumn .RktInBG {
background-color: white;
}
.RktRdr {
}
.RktPn {
color: #843c24;
}
.RktMeta {
color: black;
}
.RktMod {
color: inherit;
}
.RktOpt {
color: black;
}
.RktKw {
color: black;
}
.RktErr {
color: red;
font-style: italic;
font-weight: 400;
}
.RktVar {
position: relative;
left: -1px; font-style: italic;
color: #444;
}
.SVInsetFlow .RktVar {
font-weight: 400;
color: #444;
}
.RktSym {
color: inherit;
}
.RktValLink, .RktStxLink, .RktModLink {
text-decoration: none;
color: #07A;
font-weight: 500;
font-size: 1rem;
}
/* for syntax links within headings */
h2 a.RktStxLink, h3 a.RktStxLink, h4 a.RktStxLink, h5 a.RktStxLink,
h2 a.RktValLink, h3 a.RktValLink, h4 a.RktValLink, h5 a.RktValLink,
h2 .RktSym, h3 .RktSym, h4 .RktSym, h5 .RktSym,
h2 .RktMod, h3 .RktMod, h4 .RktMod, h5 .RktMod,
h2 .RktVal, h3 .RktVal, h4 .RktVal, h5 .RktVal,
h2 .RktPn, h3 .RktPn, h4 .RktPn, h5 .RktPn {
color: #333;
font-size: 1.65rem;
font-weight: 400;
}
.toptoclink .RktStxLink, .toclink .RktStxLink,
.toptoclink .RktValLink, .toclink .RktValLink,
.toptoclink .RktModLink, .toclink .RktModLink {
color: inherit;
}
.tocset .RktValLink, .tocset .RktStxLink, .tocset .RktModLink {
color: black;
font-weight: 400;
font-size: 0.9rem;
}
.tocset td a.tocviewselflink .RktValLink,
.tocset td a.tocviewselflink .RktStxLink,
.tocset td a.tocviewselflink .RktMod,
.tocset td a.tocviewselflink .RktSym {
font-weight: lighter;
color: white;
}
.RktRes {
color: #0000af;
}
.RktOut {
color: #960096;
}
.RktCmt {
color: #c2741f;
}
.RktVal {
color: #228b22;
}
/* ---------------------------------------- */
/* Some inline styles */
.together { /* for definitions grouped together in one box */
width: 100%;
border-top: 2px solid white;
}
tbody > tr:first-child > td > .together {
border-top: 0px; /* erase border on first instance of together */
}
.RktBlk {
white-space: pre;
text-align: left;
}
.highlighted {
font-size: 1rem;
background-color: #fee;
}
.defmodule {
font-family: 'Source Code Pro';
padding: 0.25rem 0.75rem 0.25rem 0.5rem;
margin-bottom: 1rem;
width: 100%;
background-color: hsl(60, 29%, 94%);
}
.defmodule a {
color: #444;
}
.defmodule td span.hspace:first-child {
position: absolute;
width: 0;
display: inline-block;
}
.defmodule .RpackageSpec .Smaller,
.defmodule .RpackageSpec .stt {
font-size: 1rem;
}
.specgrammar {
float: none;
padding-left: 1em;
}
.RBibliography td {
vertical-align: text-top;
padding-top: 1em;
}
.leftindent {
margin-left: 2rem;
margin-right: 0em;
}
.insetpara {
margin-left: 1em;
margin-right: 1em;
}
.SCodeFlow .Rfilebox {
margin-left: -1em; /* see 17.2 of guide, module languages */
}
.Rfiletitle {
text-align: right;
background-color: #eee;
}
.SCodeFlow .Rfiletitle {
border-top: 1px dotted gray;
border-right: 1px dotted gray;
}
.Rfilename {
border-top: 0;
border-right: 0;
padding-left: 0.5em;
padding-right: 0.5em;
background-color: inherit;
}
.Rfilecontent {
margin: 0.5em;
}
.RpackageSpec {
padding-right: 0;
}
/* ---------------------------------------- */
/* For background labels */
.RBackgroundLabel {
float: right;
width: 0px;
height: 0px;
}
.RBackgroundLabelInner {
position: relative;
width: 25em;
left: -25.5em;
top: 0.20rem; /* sensitive to monospaced font choice */
text-align: right;
z-index: 0;
font-weight: 300;
font-family: 'Source Code Pro';
font-size: 0.9rem;
color: gray;
}
.RpackageSpec .Smaller {
font-weight: 300;
font-family: 'Source Code Pro';
font-size: 0.9rem;
}
.RForeground {
position: relative;
left: 0px;
top: 0px;
z-index: 1;
}
/* ---------------------------------------- */
/* For section source modules & tags */
.RPartExplain {
background: #eee;
font-size: 0.9rem;
margin-top: 0.2rem;
padding: 0.2rem;
text-align: left;
}

@ -1,98 +0,0 @@
/* For the Racket manual style */
AddOnLoad(function() {
/* Look for header elements that have x-source-module and x-part tag.
For those elements, add a hidden element that explains how to
link to the section, and set the element's onclick() to display
the explanation. */
var tag_names = ["h1", "h2", "h3", "h4", "h5"];
for (var j = 0; j < tag_names.length; j++) {
elems = document.getElementsByTagName(tag_names[j]);
for (var i = 0; i < elems.length; i++) {
var elem = elems.item(i);
AddPartTitleOnClick(elem);
}
}
})
function AddPartTitleOnClick(elem) {
var mod_path = elem.getAttribute("x-source-module");
var tag = elem.getAttribute("x-part-tag");
if (mod_path && tag) {
// Might not be present:
var prefixes = elem.getAttribute("x-part-prefixes");
var info = document.createElement("div");
info.className = "RPartExplain";
/* The "top" tag refers to a whole document: */
var is_top = (tag == "\"top\"");
info.appendChild(document.createTextNode("Link to this "
+ (is_top ? "document" : "section")
+ " with "));
/* Break `secref` into two lines if the module path and tag
are long enough: */
var is_long = (is_top ? false : ((mod_path.length
+ tag.length
+ (prefixes ? (16 + prefixes.length) : 0))
> 60));
var line1 = document.createElement("div");
var line1x = ((is_long && prefixes) ? document.createElement("div") : line1);
var line2 = (is_long ? document.createElement("div") : line1);
function add(dest, str, cn) {
var s = document.createElement("span");
s.className = cn;
s.style.whiteSpace = "nowrap";
s.appendChild(document.createTextNode(str));
dest.appendChild(s);
}
/* Construct a `secref` call with suitable syntax coloring: */
add(line1, "\xA0@", "RktRdr");
add(line1, (is_top ? "other-doc" : "secref"), "RktSym");
add(line1, "[", "RktPn");
if (!is_top)
add(line1, tag, "RktVal");
if (is_long) {
/* indent additional lines: */
if (prefixes)
add(line1x, "\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0", "RktPn");
add(line2, "\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0", "RktPn");
}
if (prefixes) {
add(line1x, " #:tag-prefixes ", "RktPn");
add(line1x, "'", "RktVal");
add(line1x, prefixes, "RktVal");
}
if (!is_top)
add(line2, " #:doc ", "RktPn");
add(line2, "'", "RktVal");
add(line2, mod_path, "RktVal");
add(line2, "]", "RktPn");
info.appendChild(line1);
if (is_long)
info.appendChild(line1x);
if (is_long)
info.appendChild(line2);
info.style.display = "none";
/* Add the new element afterthe header: */
var n = elem.nextSibling;
if (n)
elem.parentNode.insertBefore(info, n);
else
elem.parentNode.appendChild(info);
/* Clicking the header shows the explanation element: */
elem.onclick = function () {
if (info.style.display == "none")
info.style.display = "block";
else
info.style.display = "none";
}
}
}

@ -1,743 +0,0 @@
/* See the beginning of "scribble.css".
This file is used by the `scribble/manual` language, along with
"manual-racket.css". */
@import url("manual-fonts.css");
* {
margin: 0;
padding: 0;
}
@media all {html {font-size: 15px;}}
@media all and (max-width:940px){html {font-size: 14px;}}
@media all and (max-width:850px){html {font-size: 13px;}}
@media all and (max-width:830px){html {font-size: 12px;}}
@media all and (max-width:740px){html {font-size: 11px;}}
/* CSS seems backward: List all the classes for which we want a
particular font, so that the font can be changed in one place. (It
would be nicer to reference a font definition from all the places
that we want it.)
As you read the rest of the file, remember to double-check here to
see if any font is set. */
/* Monospace: */
.maincolumn, .refpara, .refelem, .tocset, .stt, .hspace, .refparaleft, .refelemleft {
font-family: 'Source Code Pro', monospace;
white-space: inherit;
font-size: 1rem;
}
.stt {
font-weight: 500;
}
h2 .stt {
font-size: 2.7rem;
}
.toptoclink .stt {
font-size: inherit;
}
.toclink .stt {
font-size: 90%;
}
.RpackageSpec .stt {
font-weight: 300;
font-family: 'Source Code Pro';
font-size: 0.9rem;
}
h3 .stt, h4 .stt, h5 .stt {
color: #333;
font-size: 1.65rem;
font-weight: 400;
}
/* Serif: */
.main, .refcontent, .tocview, .tocsub, .sroman, i {
font-family: 'Charter', serif;
font-size: 1.18rem;
}
/* Sans-serif: */
.version, .versionNoNav, .ssansserif {
font-family: 'Fira', sans-serif;
}
.ssansserif {
font-family: 'Fira';
font-weight: 500;
font-size: 0.9em;
}
.tocset .ssansserif {
font-size: 100%;
}
/* ---------------------------------------- */
p, .SIntrapara {
display: block;
margin: 0 0 1em 0;
line-height: 1.4;
}
.compact {
padding: 0 0 1em 0;
}
li {
list-style-position: outside;
margin-left: 1.2em;
}
h1, h2, h3, h4, h5, h6, h7, h8 {
font-family: 'Fira';
font-weight: 300;
font-size: 1.6rem;
color: #333;
margin-top: inherit;
margin-bottom: 1rem;
line-height: 1.25;
-moz-font-feature-settings: 'tnum=1';
-moz-font-feature-settings: 'tnum' 1;
-webkit-font-feature-settings: 'tnum' 1;
-o-font-feature-settings: 'tnum' 1;
-ms-font-feature-settings: 'tnum' 1;
font-feature-settings: 'tnum' 1;
}
h3, h4, h5, h6, h7, h8 {
border-top: 1px solid black;
}
h2 { /* per-page main title */
font-family: 'Miso';
font-weight: bold;
margin-top: 4rem;
font-size: 3rem;
line-height: 1.1;
width: 90%;
}
h3, h4, h5, h6, h7, h8 {
margin-top: 2em;
padding-top: 0.1em;
margin-bottom: 0.75em;
}
/* ---------------------------------------- */
/* Main */
body {
color: black;
background-color: white;
}
.maincolumn {
width: auto;
margin-top: 4rem;
margin-left: 17rem;
margin-right: 2rem;
margin-bottom: 10rem; /* to avoid fixed bottom nav bar */
max-width: 700px;
min-width: 370px; /* below this size, code samples don't fit */
}
a {
text-decoration: inherit;
}
a, .toclink, .toptoclink, .tocviewlink, .tocviewselflink, .tocviewtoggle, .plainlink,
.techinside, .techoutside:hover, .techinside:hover {
color: #07A;
}
a:hover {
text-decoration: underline;
}
/* ---------------------------------------- */
/* Navigation */
.navsettop, .navsetbottom {
left: 0;
width: 15rem;
height: 6rem;
font-family: 'Fira';
font-size: 0.9rem;
border-bottom: 0px solid hsl(216, 15%, 70%);
background-color: inherit;
padding: 0;
}
.navsettop {
position: absolute;
top: 0;
left: 0;
margin-bottom: 0;
border-bottom: 0;
}
.navsettop a, .navsetbottom a {
color: black;
}
.navsettop a:hover, .navsetbottom a:hover {
background: hsl(216, 78%, 95%);
text-decoration: none;
}
.navleft, .navright {
position: static;
float: none;
margin: 0;
white-space: normal;
}
.navleft a {
display: inline-block;
}
.navright a {
display: inline-block;
text-align: center;
}
.navleft a, .navright a, .navright span {
display: inline-block;
padding: 0.5rem;
min-width: 1rem;
}
.navright {
height: 2rem;
white-space: nowrap;
}
.navsetbottom {
display: none;
}
.nonavigation {
color: #889;
}
.searchform {
display: block;
margin: 0;
padding: 0;
border-bottom: 1px solid #eee;
height: 4rem;
}
.nosearchform {
margin: 0;
padding: 0;
height: 4rem;
}
.searchbox {
font-size: 1rem;
width: 12rem;
margin: 1rem;
padding: 0.25rem;
vertical-align: middle;
background-color: white;
}
#search_box {
font-size: 0.8rem;
}
/* ---------------------------------------- */
/* Version */
.versionbox {
position: absolute;
float: none;
top: 0.25rem;
left: 17rem;
z-index: 11000;
height: 2em;
font-size: 70%;
font-weight: lighter;
width: inherit;
margin: 0;
}
.version, .versionNoNav {
font-size: inherit;
}
.version:before, .versionNoNav:before {
content: "v.";
}
/* ---------------------------------------- */
/* Margin notes */
/* cancel scribble.css styles: */
.refpara, .refelem {
position: static;
float: none;
height: auto;
width: auto;
margin: 0;
}
.refcolumn {
position: static;
display: block;
width: auto;
font-size: inherit;
margin: 2rem;
margin-left: 2rem;
padding: 0.5em;
padding-left: 0.75em;
padding-right: 1em;
background: hsl(60, 29%, 94%);
border: 1px solid #ccb;
border-left: 0.4rem solid #ccb;
}
/* slightly different handling for margin-note* on narrow screens */
@media all and (max-width:1260px) {
span.refcolumn {
float: right;
width: 50%;
margin-left: 1rem;
margin-bottom: 0.8rem;
margin-top: 1.2rem;
}
}
.refcontent, .refcontent p {
line-height: 1.5;
margin: 0;
}
.refcontent p + p {
margin-top: 1em;
}
.refcontent a {
font-weight: 400;
}
.refpara, .refparaleft {
top: -1em;
}
@media all and (max-width:600px) {
.refcolumn {
margin-left: 0;
margin-right: 0;
}
}
@media all and (min-width:1260px) {
.refcolumn {
position: absolute;
left: 66rem; right: 3em;
margin: 0;
float: right;
max-width: 18rem;
}
}
.refcontent {
font-family: 'Fira';
font-size: 1rem;
line-height: 1.6;
margin: 0 0 0 0;
}
.refparaleft, .refelemleft {
position: relative;
float: left;
right: 2em;
height: 0em;
width: 13em;
margin: 0em 0em 0em -13em;
}
.refcolumnleft {
background-color: hsl(60, 29%, 94%);
display: block;
position: relative;
width: 13em;
font-size: 85%;
border: 0.5em solid hsl(60, 29%, 94%);
margin: 0 0 0 0;
}
/* ---------------------------------------- */
/* Table of contents, left margin */
.tocset {
position: absolute;
float: none;
left: 0;
top: 0rem;
width: 14rem;
padding: 7rem 0.5rem 0.5rem 0.5rem;
background-color: hsl(216, 15%, 70%);
margin: 0;
}
.tocset td {
vertical-align: text-top;
padding-bottom: 0.4rem;
padding-left: 0.2rem;
line-height: 1.1;
font-family: 'Fira';
-moz-font-feature-settings: 'tnum=1';
-moz-font-feature-settings: 'tnum' 1;
-webkit-font-feature-settings: 'tnum' 1;
-o-font-feature-settings: 'tnum' 1;
-ms-font-feature-settings: 'tnum' 1;
font-feature-settings: 'tnum' 1;
}
.tocset td a {
color: black;
font-weight: 400;
}
.tocview {
text-align: left;
background-color: inherit;
}
.tocview td, .tocsub td {
line-height: 1.3;
}
.tocview table, .tocsub table {
width: 90%;
}
.tocset td a.tocviewselflink {
font-weight: lighter;
font-size: 110%; /* monospaced styles below don't need to enlarge */
color: white;
}
.tocviewselflink {
text-decoration: none;
}
.tocsub {
text-align: left;
margin-top: 0.5em;
background-color: inherit;
}
.tocviewlist, .tocsublist {
margin-left: 0.2em;
margin-right: 0.2em;
padding-top: 0.2em;
padding-bottom: 0.2em;
}
.tocviewlist table {
font-size: 82%;
}
.tocviewlisttopspace {
margin-bottom: 1em;
}
.tocviewsublist, .tocviewsublistonly, .tocviewsublisttop, .tocviewsublistbottom {
margin-left: 0.4em;
border-left: 1px solid #99a;
padding-left: 0.8em;
}
.tocviewsublist {
margin-bottom: 1em;
}
.tocviewsublist table,
.tocviewsublistonly table,
.tocviewsublisttop table,
.tocviewsublistbottom table,
table.tocsublist {
font-size: 1rem;
}
.tocviewsublist td, .tocviewsublistbottom td, .tocviewsublisttop td, .tocsub td,
.tocviewsublistonly td {
font-size: 90%;
}
.tocviewtoggle {
font-size: 75%; /* looks better, and avoids bounce when toggling sub-sections due to font alignments */
}
.tocsublist td {
padding-left: 0.5rem;
padding-top: 0.25rem;
text-indent: 0;
}
.tocsublinknumber {
font-size: 100%;
}
.tocsublink {
font-size: 82%;
text-decoration: none;
}
.tocsubseclink {
font-size: 100%;
text-decoration: none;
}
.tocsubnonseclink {
font-size: 82%;
text-decoration: none;
margin-left: 1rem;
padding-left: 0;
display: inline-block;
}
/* the label "on this page" */
.tocsubtitle {
display: block;
font-size: 62%;
font-family: 'Fira';
font-weight: bolder;
font-style: normal;
letter-spacing: 2px;
text-transform: uppercase;
margin: 0.5em;
}
.toptoclink {
font-weight: bold;
font-size: 110%;
margin-bottom: 0.5rem;
margin-top: 1.5rem;
display: inline-block;
}
.toclink {
font-size: inherit;
}
/* ---------------------------------------- */
/* Some inline styles */
.indexlink {
text-decoration: none;
}
pre {
margin-left: 2em;
}
blockquote {
margin-left: 2em;
margin-right: 2em;
margin-bottom: 1em;
}
.SCodeFlow {
border-left: 1px dotted black;
padding-left: 1em;
padding-right: 1em;
margin-top: 1em;
margin-bottom: 1em;
margin-left: 0em;
margin-right: 2em;
white-space: nowrap;
line-height: 1.5;
}
.SCodeFlow img {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.boxed {
margin: 0;
margin-top: 2em;
padding: 0.25em;
padding-bottom: 0.5em;
background: #f3f3f3;
box-sizing:border-box;
border-top: 1px solid #99b;
background: hsl(216, 78%, 95%);
background: -moz-linear-gradient(to bottom left, hsl(0, 0%, 99%) 0%, hsl(216, 78%, 95%) 100%);
background: -webkit-linear-gradient(to bottom left, hsl(0, 0%, 99%) 0%, hsl(216, 78%, 95%) 100%);
background: -o-linear-gradient(to bottom left, hsl(0, 0%, 99%) 0%, hsl(216, 78%, 95%) 100%);
background: -ms-linear-gradient(to bottom left, hsl(0, 0%, 99%) 0%, hsl(216, 78%, 95%) 100%);
background: linear-gradient(to bottom left, hsl(0, 0%, 99%) 0%, hsl(216, 78%, 95%) 100%);
}
blockquote > blockquote.SVInsetFlow {
/* resolves issue in e.g. /reference/notation.html */
margin-top: 0em;
}
.leftindent .SVInsetFlow { /* see e.g. section 4.5 of Racket Guide */
margin-top: 1em;
margin-bottom: 1em;
}
.SVInsetFlow a, .SCodeFlow a {
color: #07A;
font-weight: 500;
}
.SubFlow {
display: block;
margin: 0em;
}
.boxed {
width: 100%;
background-color: inherit;
}
.techoutside { text-decoration: none; }
.SAuthorListBox {
position: static;
float: none;
font-family: 'Fira';
font-weight: 300;
font-size: 110%;
margin-top: 1rem;
margin-bottom: 3rem;
width: 30rem;
height: auto;
}
.author > a { /* email links within author block */
font-weight: inherit;
color: inherit;
}
.SAuthorList {
font-size: 82%;
}
.SAuthorList:before {
content: "by ";
}
.author {
display: inline;
white-space: nowrap;
}
/* phone + tablet styles */
@media all and (max-width:720px){
@media all and (max-width:720px){
@media all {html {font-size: 15px;}}
@media all and (max-width:700px){html {font-size: 14px;}}
@media all and (max-width:630px){html {font-size: 13px;}}
@media all and (max-width:610px){html {font-size: 12px;}}
@media all and (max-width:550px){html {font-size: 11px;}}
@media all and (max-width:520px){html {font-size: 10px;}}
.navsettop, .navsetbottom {
display: block;
position: absolute;
width: 100%;
height: 4rem;
border: 0;
background-color: hsl(216, 15%, 70%);
}
.searchform {
display: inline;
border: 0;
}
.navright {
position: absolute;
right: 1.5rem;
margin-top: 1rem;
border: 0px solid red;
}
.navsetbottom {
display: block;
margin-top: 8rem;
}
.tocset {
display: none;
}
.tocset table, .tocset tbody, .tocset tr, .tocset td {
display: inline;
}
.tocview {
display: none;
}
.tocsub .tocsubtitle {
display: none;
}
.versionbox {
top: 4.5rem;
left: 1rem; /* same distance as main-column */
z-index: 11000;
height: 2em;
font-size: 70%;
font-weight: lighter;
}
.maincolumn {
margin-left: 1em;
margin-top: 7rem;
margin-bottom: 0rem;
}
}
}
/* print styles : hide the navigation elements */
@media print {
.tocset,
.navsettop,
.navsetbottom { display: none; }
.maincolumn {
width: auto;
margin-right: 13em;
margin-left: 0;
}
}

File diff suppressed because one or more lines are too long

@ -1,312 +0,0 @@
#lang scribble/manual
@(require (for-label racket/base racket/draw))
@title[#:style 'toc]{Quad: document processor}
@author[(author+email "Matthew Butterick" "mb@mbtype.com")]
@defmodulelang[quad]
@italic{This documentation explains software that is under development. It is therefore rife with optimistic claims and wishful thinking.}
@section{Installing Quad}
At the command line:
@verbatim{raco pkg install quad}
After that, you can update the package like so:
@verbatim{raco pkg update quad}
Quad is not usable software. It is currently in ``documented demo'' mode. There is no need to submit issues or bug reports. Fiddle with it at your own risk.
@section{Why care about document processors?}
A document processor is a rule-driven typesetter. It takes a text-based source file as input and converts it into a page layout.
For instance, LaTeX is a document processor. So are web browsers. Quad borrows from both traditions — it's an attempt to modernize the good ideas in LaTeX, and generalize the good ideas in web browsers.
Document processors sit opposite WYSIWYG tools like Word and InDesign. There, the user controls the layout by manipulating a representation of the page on the screen. This is fine as far as it goes. But changes to the layout — for instance, a new page size — often require a new round of manual adjustments.
A document processor, by contrast, relies on markup codes within the text to determine the layout programmatically. Compared to WYSIWYG, this approach offers less granular control. But it also creates a more flexible relationship between the source and its possible layouts.
Another benefit of document processors is that it permits every document to have a high-level, text-based source file that's independent of any particular output format (rather than the opaque binary formats endemic to Word, InDesign, et al.)
@subsection{Why not keep using LaTeX?}
I wouldn't want to criticize software merely for being old. It's a great compliment to LaTeX that it's endured this long. But 1) it's never made much headway beyond its original audience of scientific & technical writers; 2) the last 25 years of advances in digital typesetting have been implemented as a huge (occasionally tenuous) tower of patches; 3) the core codebase is increasingly opaque to today's programmers. Meaning, if LaTeX were plausibly rewritable, it would've been rewritten by now.
Instead, let's take its good ideas — there are many — and terraform a new planet.
@subsection{Why not use more HTML/CSS?}
In principle, it's possible to generate PDF documents from a web browser. Support for paper-based layouts has been part of the CSS concept @link["https://www.w3.org/People/howcome/p/cascade.html"]{since the beginning} (though it's been lightly used).
But web browsers have a few limitations. First, web browsers only render HTML, and many typesetting concepts (e.g., footnotes) don't correspond to any HTML entity. So there is a narrowing of possiblities. Second, browsers are built for speed, so high-quality typesetting (e.g., the KnuthPlass linebreaking algorithm) is off the table. Third, browsers are inconsistent in how they render pages. Fourth — taking off my typography-snob tiara here — browsers are unstable. What seems well supported today can be broken or removed tomorrow. So browsers can't be a part of a dependable publishing workflow that yields reproducible results.
@section{What does Quad do?}
Quad produces finished document layouts using three ingredients:
@itemlist[#:style 'ordered
@item{A @bold{markup-based language} for embedding high-level typesetting instructions in a text document. (Sort of like XML/HTML.)}
@item{A @bold{typesetting engine} that converts these typesetting instructions into an output-independent layout — e.g., putting characters into lines, and lines into pages.}
@item{A @bold{rendering engine} that takes this layout and prepares it for a particular output format (e.g., PDF, SVG).}
]
While there's no reason Quad couldn't produce an HTML layout, that's an easier problem, because most of the document-layout chores can (and should) be delegated to the web browser. For now, most of Quad's apparatus is devoted to its typesetting engine so it can produce layouts for PDF.
@section{What doesn't Quad do?}
@itemlist[#:style 'ordered
@item{Quad is not a WYSIWYG or interactive previewing tool.}
@item{Quad does not have user-level representations of formatting, à la Word style sheets.}
@item{Quad does not handle semantic or configurable markup. Its markup is limited to its specific, layout-based vocabulary.}
]
Rather, it is designed to cooperate with tools that offer these facilities.
@section{Theory of operation}
A document processor starts with input that we can think of as one giant line of text. It breaks this into smaller lines, and then distributes these lines across pages. Conceptually, it's a bin-packing problem.
@itemlist[#:style 'ordered
@item{Quad starts with an input file written in the @code{#lang quad} markup language. For the most part, it's text with markup codes (though it may also include things like diagrams and images).}
@item{Each markup entity is called a @defterm{quad}. A quad roughly corresponds to a box. ``Roughly'' because quads can have zero or negative dimension. Also, at the input stage, the contents of some quads may end up being spread across multiple non-overlapping boxes (e.g., a quad containing a word might be hyphenated to appear on two lines). The more precise description of a quad is therefore ``contiguous formatting region.'' Quads can be recursively nested inside other quads, thus the input file is tree-shaped.}
@item{This tree-shaped input file is flattened into a list of atomic quads. ``Atomic'' because these are the smallest items the typesetter can manipulate. (For instance, the word @italic{bar} would become three one-character quads. An image or other indivisible box would remain as is.) During the flattening, tags from higher in the tree are propagated downward by copying them into the atomic quads. The result is a ``stateless'' representation of the input, in the sense that all the information needed to typeset an atomic quad is contained within the quad itself.
@margin-note{The input is flattened because typesetting operations are easier to think about as a linear sequence (i.e., an imperative model). To see why, consider how you'd handle a page-break instruction within a tree model. No matter how deep you were in your typesetting tree, you'd have to jump back to the top level to handle your page break (because it affects the positioning of all subsequent items). Then you'd have to jump back to where you were, deep in the tree. That's not a natural way to traverse any tree. This is also why, to my mind, typesetting does not lend itself to a class- or object-based approach, as these create hierarchies that just lead you back to this tree problem.}}
@item{Atomic quads are composed into lines using one of three algorithms. (A line is just a quad of a certain width.) The first-fit algorithm puts as many quads onto a line as it can before moving on to the next. The best-fit algorithm minimizes the total looseness of all the lines in a paragraph (aka the KnuthPlass linebreaking algorithm developed for TeX). Because best-fit is more expensive, Quad also has an adaptive-fit algorithm that uses a statistical heuristic to guess whether the paragraph will benefit from best-fit; if not, it uses first-fit.}
@item{If a typeset paragraph still exceeds certain looseness tolerances, it is hyphenated and the lines recalculated.}
@item{Once the lines are broken, extra space is distributed within each line according to whether the line should appear centered, left-aligned, justified, etc. The result is a list of quads that fills the full column width.}
@item{Lines are composed into columns. (A column is just a quad of a certain height.) To support things like footnotes, columns are composed using a backtracking constraint-satisfaction algorithm.}
@item{Columns are composed into pages.}
@item{This completes the typesetting phase. Note that at every step in the process, the document is represented in the Quad markup language. There isn't a distinction between the public and private markup interface, or the high- and low-level markup entities. Thus, external tools that generate Quad markup have some latitude.}
@item{Before the typeset markup is passed to the renderer, it goes through a simplification phase — a lot of adjacent quads will have the same formatting characteristics, and these can be consolidated into runs of text.}
@item{The renderer walks through the markup and draws each quad, using information in the markup attributes to determine position, color, font, size, style, etc.}
]
@section{Enough talk — let's rock}
Open DrRacket and start a new document with @code{#lang quad} as the first line:
@codeblock|{
#lang quad
Brennan and Dale like fancy sauce.
}|
Save the document. Any place, any name is fine.
Run the document. You'll get output like this:
@racketvalfont{@code{(block '(measure 360.0 font "Times New Roman" x-align justify leading 14.0 size 11.5 x-align-last-line left column-count 1 column-gutter 10.0) "\n" "Brennan and Dale like fancy sauce. ")}}
This is the compiled Quad markup, showing what will get sent to the typesetting engine. This output is itself valid Quad markup (meaning you could put it back in the definitions window and it would compile again).
@code{#lang quad} uses the @"@"-expression reader for ease of use. But these @"@"-expressions become S-expressions in the usual manner. Also as usual, you can prefix any S-expressionized Quad markup with a @litchar{@"@"} in the definitions window to turn it back into an @"@"-expression.
@margin-note{@secref["how-to:reader" #:doc '(lib "scribblings/scribble/scribble.scrbl")] introduces @"@"-expressions.}
Now click the @onscreen{Render and Open PDF} button. After a moment, this should open your PDF previewing program with the Quad-generated PDF, which will say, unsurprisingly, ``Brennan and Dale like fancy sauce.''
As you work through the demo, you can alternatively use the @onscreen{Render PDF} button to regenerate the PDF without opening your previewer. (The Preview app on OS X, for instance, will automatically refresh when it detects the PDF has changed, which prevents a welter of windows.)
@code{#lang quad} is a Racket-implemented DSL (= domain-specific language). It's not a language in the sense of Turing-complete. Rather, a @code{#lang quad} ``program'' resembles text annotated with high-level layout-description commands (not unlike XML/HTML). @code{#lang quad} programs can be written directly, or generated as the output of other programs.
Each @"@"-expression in @code{#lang quad} is interpreted as a @italic{quad} (roughly a box; more precisely a contiguous formatting region). A quad has the following syntax:
@code|{@quad-name[(list 'attr-name attr-val ...)]{text-or-more-quads ...}}|
The @code{(list 'attr-name attr-val ...)} is an interleaved list of symbols and values, as you might provide to @racket[hash]. The attribute list is mandatory. If a quad has no attributes of its own, this can be signaled with either @racket[empty] or @racket[#f]:
@codeblock|{
@quad-name[empty]{text-or-more-quads ...}
@quad-name[#f]{text-or-more-quads ...}
}|
If you thought this resembled an @link["http://docs.racket-lang.org/pollen/second-tutorial.html#%28part._.X-expressions%29"]{X-expression}, you wouldn't be wrong. Like X-expressions, quads are recursively composable. Also like X-expressions, the attributes in a quad apply to all the text or quads within, unless superseded by another attribute declaration deeper down.
Let's see how this works. The simplest kind of quad is a @code{block}. If we wrap our text in a @code{block} without attributes, what happens to the PDF?
@codeblock|{
#lang quad
@block[#f]{Brennan and Dale like fancy sauce.}
}|
Right — nothing. A block without attributes just evaporates. Move the boundaries of the block:
@codeblock|{
#lang quad
@block[#f]{Brennan and Dale} like fancy sauce.
}|
Still the same. Let's add some bold formatting with the @code{weight} attribute:
@codeblock|{
#lang quad
@block['(weight bold)]{Brennan and Dale} like fancy sauce.
}|
What an accomplishment. To show you that attributes are additive, we'll put a quad inside our quad:
@codeblock|{
#lang quad
@block['(weight bold)]{Brennan and @block['(color "red")]{Dale}} like fancy sauce.
}|
You're getting the idea. In terms of type styling, here are the attributes and values that Quad understands:
@code{'weight} = @code{'normal} (default) or @code{'bold}
@(linebreak)@code{'style} = @code{'normal} (default) or @code{'italic}
@(linebreak)@code{'font} = family name as string
@(linebreak)@code{'size} = point size as floating-point number
@(linebreak)@code{leading} = baseline-to-baseline measure in points
@(linebreak)@code{'color} = color string from @racket[color-database<%>]
Feel free to impose these on your demo program.
Though we're using @"@"-expressions, a @code{#lang quad} source file doesn't imply any formatting characteristics as it would in Scribble or Pollen. For instance, see what happens if you add two line breaks and some more text:
@codeblock|{
#lang quad
@block['(size 16)]{Brennan and @block['(color "red")]{Dale}} like fancy sauce.
Derek does not.
}|
The text ``Derek does not'' appears flush against the first sentence. In Scribble those linebreaks would suggest a paragraph break. In HTML they would suggest a word space. In @code{#lang quad} they suggest neither. Why not? Because @code{#lang quad} is strictly a language for describing explicit typesetting. Newlines have no meaning.
OK, so how do we create a paragraph? Quad supports a special set of quads called @italic{breaks} that move the typesetting position. For instance, @code{@"@"(block-break)} will act like a carriage return, moving the next typeset item below the previous item and all the way to the left edge of the column:
@codeblock|{
#lang quad
@block['(size 16)]{Brennan and @block['(color "red")]{Dale}} like fancy sauce.
@(block-break)
Derek does not.
}|
Now ``Derek does not'' appears on its own line. What about things like paragraph spacing and first-line indents? Again, because @code{#lang quad} is about explicit typesetting, all these things need to be inserted explicitly in the code. For instance, to make an indent, we add a @code{box} with a @code{'width} attribute:
@codeblock|{
#lang quad
@block['(size 16)]{Brennan and @block['(color "red")]{Dale}} like fancy sauce.
@(block-break)
@box['(width 15)]
Derek does not.
}|
Quad also handles @code|{@(line-break)}|, @code|{@(column-break)}|, and @code|{@(page-break)}|. Try the last one:
@codeblock|{
#lang quad
@block['(size 16)]{Brennan and @block['(color "red")]{Dale}} like fancy sauce.
@(page-break)
@box['(width 15)]
Derek does not.
}|
Next, let's look at Quad's linebreaking mechanism. For the next sample, please paste in a large block of plain text between the curly braces so you'll get some linewrapping:
@codeblock|{
#lang quad
@block[#f]{A text that goes on for a while ...}
}|
You will see a block of justified text. The demo is running at maximum quality, so two other things will also be true.
First, the lines are broken using the Knuth-Plass algorithm developed for TeX. This is a very nice algorithm that looks at all possible ways of breaking the lines in the paragraph and picks the one that leaves the smallest total gap at the right edge. (Quad will also hyphenate if necessary, but only if all the unhyphenated possibilities are bad.)
Second, notice that punctuation hangs a little outside the text block. This is an optical adjustment that makes for neater-looking blocks. Whether you literally care about this kind of optical adjustment is not the point. The point is that the Quad typesetting engine @italic{permits} it. And that is really what we are going for here: a hackable typesetting engine. When you have fine control over all the page elements, then other things become possible (for instance, mathematical-equation typesetting, which is quite a bit more involved than just hanging punctuation off the edges).
Justification is the default setting for the demo. To override this setting, use these attributes:
@code{'x-align} = @code{'justify} (default), @code{'left}, @code{'center}, or @code{'right}
@code{'x-align-last-line} = @code{'justify} (default), @code{'left}, @code{'center}, or @code{'right}
@codeblock|{
#lang quad
@block['(x-align center x-align-last-line center)]{A text that goes on for a while ...}
}|
Then you can combine blocks with different styles:
@codeblock|{
#lang quad
@block['(x-align center x-align-last-line center size 24)]{Very important headline}
@(block-break)
@block['(style italic)]{A subhead that maybe lasts a few lines}
@(block-break)
@box['(width 10)]@block[#f]{A text that goes on for a while ...}
}|
In sum, you can build up complex typesetting with a relatively small vocabulary of typesetting commands.
You are welcome to shovel large quantities of plain text into your @code{#lang quad} window to see it broken into lines and paginated.
@section{Bottlenecks, roadblocks, & unanswered questions}
In no particular order.
@itemlist[#:style 'ordered
@item{@bold{Flattening is wasteful.} Exploding the input into atomic quads and copying the attributes works, but it creates an enormous data structure with a huge amount of repetition. But, how do you create a stateless representation of the input?
@italic{Possible improvements}: Put the attributes into a separate data structure that treats each attribute as having a scope. But this makes editing the input data more difficult & fragile, because you have two parallel structures to keep sychronized. Also, there's probably no reason that the attributes have to allow arbitrary keyvalue pairs. If the keys and certain values were reduced to a fixed vocabulary, they could be encoded as (smaller, quicker) integers rather than symbols and strings.}
@item{@bold{Allocation is wasteful.} Many typesetting operations break bigger quads into smaller ones, or group smaller quads into bigger ones, etc. The result is that there's a lot of allocation & garbage collection relative to the typical Racket program.
@italic{Possible improvements}: Perhaps the input can be fixed some structure and results of each typesetting operation stored as a set of edits (like a diff) rather than copying the whole structure.}
@item{@bold{Pango text measuring is slow.} The most cumulatively expensive operation is measuring text so linebreaks can be calculated. @racketmodname[racket/draw] relies on Pango, which is fine for occasional UI stuff, but not zillions of lookups. (BTW Pango does have higher-level text-layout facilities which are of course faster than measuring characters individually. But the point of Quad is to micromanage the typesetting and thereby make things possible that are not in Pango.)
@italic{Possible improvements}: First, use the FFI to measure text through the underlying FreeType library. This is a lot faster, but costs some functionality. Second, better caching (but see next note).
}
@item{@bold{Caching is tricky.} Caching is an essential ingredient in a text-rendering system because so many measurements are reused. Two hard parts, however. First, simplifying the key logic so you don't end up with immensely huge hashtables with commensurately costly lookups. Second, preserving caches between runs of the program. Sure, save it on disk, but a giant hashtable in a .rktd file is still going to take a moment to be reconstituted into memory.
@italic{Possible improvements}: Rely on disk-based hashtables, i.e., cache files that can be read & updated without having to reconstitute the whole file into a RAM-resident hashtable, and then write it all out again. I'm sure someone figured this out in 1972, I just haven't researched it yet.}
@item{@bold{Cairo's PDFs are weak.} Cairo's PDF generator is missing key features (e.g., @link["http://cairographics.org/roadmap/"]{hyperlinks}) and in general makes PDFs that are bigger and less capable than, say, @tt{tex2pdf}. Since PDFs are undoubtedly the #1 target format for a document processor, this is a major liability. OTOH, the idea of writing a PostScript/PDF compiler is, for me anyhow, daunting.
@italic{Possible improvements}: Bite the bullet and make a PDF compiler. If one wants to be free of LaTeX, and have better-quality PDFs than Cairo allows, there's not really a second option.}
@item{@bold{Overall performance is slow.} Outside of text measurement, most of Quad consists of simple mathematical operations. It seems like it should be highly optimizable. (Using Typed Racket, however, wasn't the answer.)
@italic{Possible improvements}: Use more unsafe math operations, gingerly.}
@item{@bold{Dependencies are broad.} One reason switching to Typed Racket did nothing for Quad is that it touches a lot of other pieces of Racket. In TR's case, creating typed interfaces for untyped libraries consumed all the potential performance gains from static typing. But still, using a small slice of a lot of libraries adds a certain overhead.}
@item{@bold{Glyph shaping is nowhere.} A proper 21st-century typesetting engine needs OpenType glyph shaping, and the only open-source game in town is @link["https://www.freedesktop.org/wiki/Software/HarfBuzz/"]{HarfBuzz}. Haven't used it, don't know how to integrate it.}
@item{@bold{Parallel processing is difficult.} It's unclear to me how to exploit Racket's parallel-processing facilities to speed up typesetting. A typeset document is likely to have a lot of finely interdependent pieces (e.g., table of contents, table of authorities, footnotes, etc.)}
@item{@bold{Run-to-run caching is difficult.} By this I mean that a common workflow in typesetting is to edit the document, preview the typesetting, make adjustments, preview again, etc. At each step, potentially not that much of the document is changing. But the typesetter needs to run start to finish anyhow.
@italic{Possible improvements}: The most expensive operation is linebreaking. It would be nice to find a way to cache linebreaking between runs — e.g., ``this paragraph hasn't changed, so we can just reuse the linebreaks from last time.'' But this would require some kind of checksumming of each paragraph and disk caching, which itself would get expensive.}
]
@section{Why is it called Quad?}
In letterpress printing, a @italic{quad} was a piece of metal used as spacing material within a line.

@ -1,249 +0,0 @@
/* See the beginning of "scribble.css". */
/* Monospace: */
.RktIn, .RktRdr, .RktPn, .RktMeta,
.RktMod, .RktKw, .RktVar, .RktSym,
.RktRes, .RktOut, .RktCmt, .RktVal,
.RktBlk {
font-family: monospace;
white-space: inherit;
}
/* Serif: */
.inheritedlbl {
font-family: serif;
}
/* Sans-serif: */
.RBackgroundLabelInner {
font-family: sans-serif;
}
/* ---------------------------------------- */
/* Inherited methods, left margin */
.inherited {
width: 100%;
margin-top: 0.5em;
text-align: left;
background-color: #ECF5F5;
}
.inherited td {
font-size: 82%;
padding-left: 1em;
text-indent: -0.8em;
padding-right: 0.2em;
}
.inheritedlbl {
font-style: italic;
}
/* ---------------------------------------- */
/* Racket text styles */
.RktIn {
color: #cc6633;
background-color: #eeeeee;
}
.RktInBG {
background-color: #eeeeee;
}
.RktRdr {
}
.RktPn {
color: #843c24;
}
.RktMeta {
color: black;
}
.RktMod {
color: black;
}
.RktOpt {
color: black;
}
.RktKw {
color: black;
}
.RktErr {
color: red;
font-style: italic;
}
.RktVar {
color: #262680;
font-style: italic;
}
.RktSym {
color: #262680;
}
.RktSymDef { /* used with RktSym at def site */
}
.RktValLink {
text-decoration: none;
color: blue;
}
.RktValDef { /* used with RktValLink at def site */
}
.RktModLink {
text-decoration: none;
color: blue;
}
.RktStxLink {
text-decoration: none;
color: black;
}
.RktStxDef { /* used with RktStxLink at def site */
}
.RktRes {
color: #0000af;
}
.RktOut {
color: #960096;
}
.RktCmt {
color: #c2741f;
}
.RktVal {
color: #228b22;
}
/* ---------------------------------------- */
/* Some inline styles */
.together {
width: 100%;
}
.prototype, .argcontract, .RBoxed {
white-space: nowrap;
}
.prototype td {
vertical-align: text-top;
}
.RktBlk {
white-space: inherit;
text-align: left;
}
.RktBlk tr {
white-space: inherit;
}
.RktBlk td {
vertical-align: baseline;
white-space: inherit;
}
.argcontract td {
vertical-align: text-top;
}
.highlighted {
background-color: #ddddff;
}
.defmodule {
width: 100%;
background-color: #F5F5DC;
}
.specgrammar {
float: right;
}
.RBibliography td {
vertical-align: text-top;
}
.leftindent {
margin-left: 1em;
margin-right: 0em;
}
.insetpara {
margin-left: 1em;
margin-right: 1em;
}
.Rfilebox {
}
.Rfiletitle {
text-align: right;
margin: 0em 0em 0em 0em;
}
.Rfilename {
border-top: 1px solid #6C8585;
border-right: 1px solid #6C8585;
padding-left: 0.5em;
padding-right: 0.5em;
background-color: #ECF5F5;
}
.Rfilecontent {
margin: 0em 0em 0em 0em;
}
.RpackageSpec {
padding-right: 0.5em;
}
/* ---------------------------------------- */
/* For background labels */
.RBackgroundLabel {
float: right;
width: 0px;
height: 0px;
}
.RBackgroundLabelInner {
position: relative;
width: 25em;
left: -25.5em;
top: 0px;
text-align: right;
color: white;
z-index: 0;
font-weight: bold;
}
.RForeground {
position: relative;
left: 0px;
top: 0px;
z-index: 1;
}
/* ---------------------------------------- */
/* History */
.SHistory {
font-size: 82%;
}

@ -1,170 +0,0 @@
// Common functionality for PLT documentation pages
// Page Parameters ------------------------------------------------------------
var page_query_string = location.search.substring(1);
var page_args =
((function(){
if (!page_query_string) return [];
var args = page_query_string.split(/[&;]/);
for (var i=0; i<args.length; i++) {
var a = args[i];
var p = a.indexOf('=');
if (p >= 0) args[i] = [a.substring(0,p), a.substring(p+1)];
else args[i] = [a, false];
}
return args;
})());
function GetPageArg(key, def) {
for (var i=0; i<page_args.length; i++)
if (page_args[i][0] == key) return decodeURIComponent(page_args[i][1]);
return def;
}
function MergePageArgsIntoLink(a) {
if (page_args.length == 0 ||
(!a.attributes["data-pltdoc"]) || (a.attributes["data-pltdoc"].value == ""))
return;
a.href = MergePageArgsIntoUrl(a.href);
}
function MergePageArgsIntoUrl(href) {
var mtch = href.match(/^([^?#]*)(?:\?([^#]*))?(#.*)?$/);
if (mtch == undefined) { // I think this never happens
return "?" + page_query_string;
}
if (!mtch[2]) {
return mtch[1] + "?" + page_query_string + (mtch[3] || "");
}
// need to merge here, precedence to arguments that exist in `a'
var i, j;
var prefix = mtch[1], str = mtch[2] || "", suffix = mtch[3] || "";
var args = str.split(/[&;]/);
for (i=0; i<args.length; i++) {
j = args[i].indexOf('=');
if (j) args[i] = args[i].substring(0,j);
}
var additions = "";
for (i=0; i<page_args.length; i++) {
var exists = false;
for (j=0; j<args.length; j++)
if (args[j] == page_args[i][0]) { exists = true; break; }
if (!exists) str += "&" + page_args[i][0] + "=" + page_args[i][1];
}
return prefix + "?" + str + suffix;
}
// Cookies --------------------------------------------------------------------
// Actually, try localStorage (a la HTML 5), first.
function GetCookie(key, def) {
try {
var v = localStorage[key];
if (!v) v = def;
return v;
} catch (e) {
var i, cookiestrs;
try {
if (document.cookie.length <= 0) return def;
cookiestrs = document.cookie.split(/; */);
} catch (e) { return def; }
for (i = 0; i < cookiestrs.length; i++) {
var cur = cookiestrs[i];
var eql = cur.indexOf('=');
if (eql >= 0 && cur.substring(0,eql) == key)
return unescape(cur.substring(eql+1));
}
return def;
}
}
function SetCookie(key, val) {
try {
localStorage[key] = val;
} catch(e) {
var d = new Date();
d.setTime(d.getTime()+(365*24*60*60*1000));
try {
document.cookie =
key + "=" + escape(val) + "; expires="+ d.toGMTString() + "; path=/";
} catch (e) {}
}
}
// note that this always stores a directory name, ending with a "/"
function SetPLTRoot(ver, relative) {
var root = location.protocol + "//" + location.host
+ NormalizePath(location.pathname.replace(/[^\/]*$/, relative));
SetCookie("PLT_Root."+ver, root);
}
// adding index.html works because of the above
function GotoPLTRoot(ver, relative) {
var u = GetCookie("PLT_Root."+ver, null);
if (u == null) return true; // no cookie: use plain up link
// the relative path is optional, default goes to the toplevel start page
if (!relative) relative = "index.html";
location = u + relative;
return false;
}
// Utilities ------------------------------------------------------------------
var normalize_rxs = [/\/\/+/g, /\/\.(\/|$)/, /\/[^\/]*\/\.\.(\/|$)/];
function NormalizePath(path) {
var tmp, i;
for (i = 0; i < normalize_rxs.length; i++)
while ((tmp = path.replace(normalize_rxs[i], "/")) != path) path = tmp;
return path;
}
// `noscript' is problematic in some browsers (always renders as a
// block), use this hack instead (does not always work!)
// document.write("<style>mynoscript { display:none; }</style>");
// Interactions ---------------------------------------------------------------
function DoSearchKey(event, field, ver, top_path) {
var val = field.value;
if (event && event.keyCode == 13) {
var u = GetCookie("PLT_Root."+ver, null);
if (u == null) u = top_path; // default: go to the top path
u += "search/index.html?q=" + encodeURIComponent(val);
u = MergePageArgsIntoUrl(u);
location = u;
return false;
}
return true;
}
function TocviewToggle(glyph, id) {
var s = document.getElementById(id).style;
var expand = s.display == "none";
s.display = expand ? "block" : "none";
glyph.innerHTML = expand ? "&#9660;" : "&#9658;";
}
// Page Init ------------------------------------------------------------------
// Note: could make a function that inspects and uses window.onload to chain to
// a previous one, but this file needs to be required first anyway, since it
// contains utilities for all other files.
var on_load_funcs = [];
function AddOnLoad(fun) { on_load_funcs.push(fun); }
window.onload = function() {
for (var i=0; i<on_load_funcs.length; i++) on_load_funcs[i]();
};
AddOnLoad(function(){
var links = document.getElementsByTagName("a");
for (var i=0; i<links.length; i++) MergePageArgsIntoLink(links[i]);
var label = GetPageArg("ctxtname",false);
if (!label) return;
var indicator = document.getElementById("contextindicator");
if (!indicator) return;
indicator.innerHTML = label;
indicator.style.display = "block";
});

@ -1,484 +0,0 @@
/* This file is used by default by all Scribble documents.
See also "manual.css", which is added by default by the
`scribble/manual` language. */
/* CSS seems backward: List all the classes for which we want a
particular font, so that the font can be changed in one place. (It
would be nicer to reference a font definition from all the places
that we want it.)
As you read the rest of the file, remember to double-check here to
see if any font is set. */
/* Monospace: */
.maincolumn, .refpara, .refelem, .tocset, .stt, .hspace, .refparaleft, .refelemleft {
font-family: monospace;
}
/* Serif: */
.main, .refcontent, .tocview, .tocsub, .sroman, i {
font-family: serif;
}
/* Sans-serif: */
.version, .versionNoNav, .ssansserif {
font-family: sans-serif;
}
.ssansserif {
font-size: 80%;
font-weight: bold;
}
/* ---------------------------------------- */
p, .SIntrapara {
display: block;
margin: 1em 0;
}
h2 { /* per-page main title */
margin-top: 0;
}
h3, h4, h5, h6, h7, h8 {
margin-top: 1.75em;
margin-bottom: 0.5em;
}
.SSubSubSubSection {
font-weight: bold;
font-size: 0.83em; /* should match h5; from HTML 4 reference */
}
/* Needed for browsers like Opera, and eventually for HTML 4 conformance.
This means that multiple paragraphs in a table element do not have a space
between them. */
table p {
margin-top: 0;
margin-bottom: 0;
}
/* ---------------------------------------- */
/* Main */
body {
color: black;
background-color: #ffffff;
}
table td {
padding-left: 0;
padding-right: 0;
}
.maincolumn {
width: 43em;
margin-right: -40em;
margin-left: 15em;
}
.main {
text-align: left;
}
/* ---------------------------------------- */
/* Navigation */
.navsettop, .navsetbottom {
background-color: #f0f0e0;
padding: 0.25em 0 0.25em 0;
}
.navsettop {
margin-bottom: 1.5em;
border-bottom: 2px solid #e0e0c0;
}
.navsetbottom {
margin-top: 2em;
border-top: 2px solid #e0e0c0;
}
.navleft {
margin-left: 1ex;
position: relative;
float: left;
white-space: nowrap;
}
.navright {
margin-right: 1ex;
position: relative;
float: right;
white-space: nowrap;
}
.nonavigation {
color: #e0e0e0;
}
.searchform {
display: inline;
margin: 0;
padding: 0;
}
.nosearchform {
display: none;
}
.searchbox {
width: 16em;
margin: 0px;
padding: 0px;
background-color: #eee;
border: 1px solid #ddd;
text-align: center;
vertical-align: middle;
}
#contextindicator {
position: fixed;
background-color: #c6f;
color: #000;
font-family: monospace;
font-weight: bold;
padding: 2px 10px;
display: none;
right: 0;
bottom: 0;
}
/* ---------------------------------------- */
/* Version */
.versionbox {
position: relative;
float: right;
left: 2em;
height: 0em;
width: 13em;
margin: 0em -13em 0em 0em;
}
.version {
font-size: small;
}
.versionNoNav {
font-size: xx-small; /* avoid overlap with author */
}
.version:before, .versionNoNav:before {
content: "Version ";
}
/* ---------------------------------------- */
/* Margin notes */
.refpara, .refelem {
position: relative;
float: right;
left: 2em;
height: 0em;
width: 13em;
margin: 0em -13em 0em 0em;
}
.refpara, .refparaleft {
top: -1em;
}
.refcolumn {
background-color: #F5F5DC;
display: block;
position: relative;
width: 13em;
font-size: 85%;
border: 0.5em solid #F5F5DC;
margin: 0 0 0 0;
}
.refcontent {
margin: 0 0 0 0;
}
.refcontent p {
margin-top: 0;
margin-bottom: 0;
}
.refparaleft, .refelemleft {
position: relative;
float: left;
right: 2em;
height: 0em;
width: 13em;
margin: 0em 0em 0em -13em;
}
.refcolumnleft {
background-color: #F5F5DC;
display: block;
position: relative;
width: 13em;
font-size: 85%;
border: 0.5em solid #F5F5DC;
margin: 0 0 0 0;
}
/* ---------------------------------------- */
/* Table of contents, inline */
.toclink {
text-decoration: none;
color: blue;
font-size: 85%;
}
.toptoclink {
text-decoration: none;
color: blue;
font-weight: bold;
}
/* ---------------------------------------- */
/* Table of contents, left margin */
.tocset {
position: relative;
float: left;
width: 12.5em;
margin-right: 2em;
}
.tocset td {
vertical-align: text-top;
}
.tocview {
text-align: left;
background-color: #f0f0e0;
}
.tocsub {
text-align: left;
margin-top: 0.5em;
background-color: #f0f0e0;
}
.tocviewlist, .tocsublist {
margin-left: 0.2em;
margin-right: 0.2em;
padding-top: 0.2em;
padding-bottom: 0.2em;
}
.tocviewlist table {
font-size: 82%;
}
.tocviewlisttopspace {
margin-bottom: 1em;
}
.tocviewsublist, .tocviewsublistonly, .tocviewsublisttop, .tocviewsublistbottom {
margin-left: 0.4em;
border-left: 1px solid #bbf;
padding-left: 0.8em;
}
.tocviewsublist {
margin-bottom: 1em;
}
.tocviewsublist table,
.tocviewsublistonly table,
.tocviewsublisttop table,
.tocviewsublistbottom table {
font-size: 75%;
}
.tocviewtitle * {
font-weight: bold;
}
.tocviewlink {
text-decoration: none;
color: blue;
}
.tocviewselflink {
text-decoration: underline;
color: blue;
}
.tocviewtoggle {
text-decoration: none;
color: blue;
font-size: 75%; /* looks better, and avoids bounce when toggling sub-sections due to font alignments */
}
.tocsublist td {
padding-left: 1em;
text-indent: -1em;
}
.tocsublinknumber {
font-size: 82%;
}
.tocsublink {
font-size: 82%;
text-decoration: none;
}
.tocsubseclink {
font-size: 82%;
text-decoration: none;
}
.tocsubnonseclink {
font-size: 82%;
text-decoration: none;
padding-left: 0.5em;
}
.tocsubtitle {
font-size: 82%;
font-style: italic;
margin: 0.2em;
}
/* ---------------------------------------- */
/* Some inline styles */
.indexlink {
text-decoration: none;
}
.nobreak {
white-space: nowrap;
}
pre { margin-left: 2em; }
blockquote { margin-left: 2em; }
ol { list-style-type: decimal; }
ol ol { list-style-type: lower-alpha; }
ol ol ol { list-style-type: lower-roman; }
ol ol ol ol { list-style-type: upper-alpha; }
.SCodeFlow {
display: block;
margin-left: 1em;
margin-bottom: 0em;
margin-right: 1em;
margin-top: 0em;
white-space: nowrap;
}
.SVInsetFlow {
display: block;
margin-left: 0em;
margin-bottom: 0em;
margin-right: 0em;
margin-top: 0em;
}
.SubFlow {
display: block;
margin: 0em;
}
.boxed {
width: 100%;
background-color: #E8E8FF;
}
.hspace {
}
.slant {
font-style: oblique;
}
.badlink {
text-decoration: underline;
color: red;
}
.plainlink {
text-decoration: none;
color: blue;
}
.techoutside { text-decoration: underline; color: #b0b0b0; }
.techoutside:hover { text-decoration: underline; color: blue; }
/* .techinside:hover doesn't work with FF, .techinside:hover>
.techinside doesn't work with IE, so use both (and IE doesn't
work with inherit in the second one, so use blue directly) */
.techinside { color: black; }
.techinside:hover { color: blue; }
.techoutside:hover>.techinside { color: inherit; }
.SCentered {
text-align: center;
}
.imageleft {
float: left;
margin-right: 0.3em;
}
.Smaller {
font-size: 82%;
}
.Larger {
font-size: 122%;
}
/* A hack, inserted to break some Scheme ids: */
.mywbr {
display: inline-block;
height: 0;
width: 0;
font-size: 1px;
}
.compact li p {
margin: 0em;
padding: 0em;
}
.noborder img {
border: 0;
}
.SAuthorListBox {
position: relative;
float: right;
left: 2em;
top: -2.5em;
height: 0em;
width: 13em;
margin: 0em -13em 0em 0em;
}
.SAuthorList {
font-size: 82%;
}
.SAuthorList:before {
content: "by ";
}
.author {
display: inline;
white-space: nowrap;
}
/* print styles : hide the navigation elements */
@media print {
.tocset,
.navsettop,
.navsetbottom { display: none; }
.maincolumn {
width: auto;
margin-right: 13em;
margin-left: 0;
}
}

@ -1,17 +0,0 @@
#lang racket/base
(require "typeset.rkt" "samples.rkt" "render.rkt" "world.rkt" racket/class "logger.rkt")
(module+ main
(define line-limit (with-handlers ([exn:fail? (λ(exn) #f)])
(string->number (vector-ref (current-command-line-arguments) 0))))
(activate-logger quad-logger)
(parameterize ([world:quality-default world:max-quality]
[world:paper-width-default 412]
[world:paper-height-default 600])
(define path "texts/segfault.txt")
(displayln "Making text sample")
(define text-sample (time (make-sample path line-limit)))
(displayln "Typsetting sample")
(define typeset-sample (time (typeset text-sample)))
(displayln "Rendering sample to PDF")
(time (send (new pdf-renderer%) render-to-file typeset-sample "texts/segfault.pdf"))))

File diff suppressed because one or more lines are too long

@ -1,194 +0,0 @@
#lang racket
(require math/statistics sugar racket/serialize plot)
(require (except-in "quads.rkt" line) "utils.rkt" "wrap.rkt" "world.rkt" "measure.rkt" "logger.rkt" "typeset.rkt")
(define+provide (make-wrap-proc-bps
#:make-pieces-proc make-pieces-proc
#:measure-quad-proc measure-quad-proc
#:compose-line-proc compose-line-proc
#:fast-measure-pieces-proc [fast-measure-pieces-proc (compose1 measure-quad-proc compose-line-proc)]
#:find-breakpoints-proc find-breakpoints-proc)
(λ(qs [measure #f])
(let* ([measure (or measure (quad-attr-ref/parameter (car qs) world:measure-key))]
[qs (if (quad-has-attr? (car qs) world:measure-key)
qs
(map (curryr quad-attr-set world:measure-key measure) qs))])
(log-quad-debug "wrapping on measure = ~a" measure)
(define pieces (make-pieces-proc qs))
(log-quad-debug "avg piece length for breakpoints = ~a"
(/ (for/sum ([p (in-list pieces)])
(for/sum ([q (in-list (quad-list p))])
(define str (quad->string q))
(if (equal? str "")
(quad-attr-ref q world:width-key 0)
(apply measure-text (quad->string q) (font-attributes-with-defaults q)))))
(length pieces)))
(define bps (find-breakpoints-proc (list->vector pieces) (+ 0.0 measure)))
(values pieces bps (map (curryr compose-line-proc measure-quad-proc) (break-at pieces bps))))))
;; wrap proc based on greedy proc
(define wrap-first-bps (make-wrap-proc-bps
#:make-pieces-proc (make-caching-proc make-pieces)
#:measure-quad-proc quad-width
#:compose-line-proc pieces->line
#:fast-measure-pieces-proc measure-potential-line
#:find-breakpoints-proc wrap-first))
;; wrap proc based on penalty function
(define wrap-best-bps (make-wrap-proc-bps
#:make-pieces-proc (make-caching-proc make-pieces)
#:measure-quad-proc quad-width
#:compose-line-proc pieces->line
#:fast-measure-pieces-proc measure-potential-line
#:find-breakpoints-proc wrap-best))
(define ti (block '(measure 54 leading 18) "Meg is an ally."))
(define (looseness-spread lines)
(if (<= (length lines) 1)
0
(let ([lines-to-measure (drop-right lines 1)]) ; exclude last line from looseness calculation
(define measures (map (λ(line) (quad-attr-ref line world:line-looseness-key 0)) lines-to-measure))
(round-float (- (apply max measures) (apply min measures))))))
(define (geometric-mean lines)
(if (<= (length lines) 1)
0
(let ([lines-to-measure (drop-right lines 1)]) ; exclude last line from looseness calculation
(define measures (map (λ(line) (quad-attr-ref line world:line-looseness-key 0)) lines-to-measure))
(expt (apply * measures) (/ 1 (length measures))))))
(define (looseness-stddev lines)
(if (<= (length lines) 1)
0
(let ([lines-to-measure (drop-right lines 1)]) ; exclude last line from looseness calculation
(define measures (map (λ(line) (quad-attr-ref line world:line-looseness-key 0)) lines-to-measure))
(stddev measures))))
(define (looseness-var lines)
(if (<= (length lines) 1)
0
(let ([lines-to-measure (drop-right lines 1)]) ; exclude last line from looseness calculation
(define measures (map (λ(line) (quad-attr-ref line world:line-looseness-key 0)) lines-to-measure))
(variance measures))))
(define (list->hash0 . xs)
(define mh (make-hash))
(for ([(k v) (in-hash (apply hash xs))])
(hash-set! mh k v))
mh)
(define (piece-length p)
(apply + (map quad-width (quad-list p))))
(define (trial wrap-proc ti measure)
(match-define-values (result time _ _) (time-apply wrap-proc (append (list ti) (list measure))))
(match-define (list pieces bps lines) result)
(define line-count (length lines))
(define piece-count (length pieces))
(define avg-piece-length (/ (apply + (map piece-length pieces)) (length pieces)))
(define avg-looseness (average-looseness lines))
(define looseness-sd (looseness-stddev lines))
(define looseness-variance (looseness-var lines))
(define spread (looseness-spread lines))
(define looses (map (λ(line) (quad-attr-ref line world:line-looseness-key 0)) lines))
(hash 'bps bps 'time time 'line-count line-count 'looses looses
'piece-count piece-count 'avg-piece-length avg-piece-length
'avg-looseness avg-looseness 'looseness-spread spread
'looseness-sd looseness-sd
'looseness-variance looseness-variance))
(define (improved? h1 h2)
(define h1-bps (if (hash? h1) (hash-ref h1 'bps) h1))
(define h2-bps (if (hash? h2) (hash-ref h2 'bps) h2))
(define min-length (min (length h1-bps) (length h2-bps)))
(apply + (map abs (map - (take h1-bps min-length) (take h2-bps min-length)))))
(define (trial-set measure ti)
(define text (quad->string ti))
(define ti-unhyphenated (split-quad ti))
(define fu (trial wrap-first-bps ti-unhyphenated measure))
(define bu (trial wrap-best-bps ti-unhyphenated measure))
(define ti-hyphenated (split-quad (hyphenate-quad ti)))
(define fh (trial wrap-first-bps ti-hyphenated measure))
(define bh (trial wrap-best-bps ti-hyphenated measure))
(hash 'fu fu 'bu bu 'fh fh 'bh bh
'text text
'measure measure
'fh-improved (improved? fu fh)
'bu-improved (improved? fu bu)
'bh-improved (improved? fh bh)))
(define (make-blocks textfile)
(define strings (filter (λ(s) (> (string-length s) 10)) (map (λ(s) (regexp-replace* #rx"\n" s " ")) (map string-trim (string-split (file->string textfile) "\n\n")))))
(map (λ(t) (block '(font "Equity Text B" leading 14 column-count 1 column-gutter 10 size 11.5 x-align justify x-align-last-line left) t)) strings))
(define (refresh [filename "jude0.txt"] [measures '(150 180 210 240 270 300 330 360 390)])
(define paragraphs (make-blocks filename))
;; only use paragraphs > 2 lines because last line is disregarded for looseness spread calculation
(define results (filter
(λ(rec) (> (hash-ref (hash-ref rec 'fu) 'line-count) 2)) (append-map (compose1 (λ(m) (map (λ(p) (trial-set m p)) paragraphs)) (λ(m) (report m))) measures)))
(write-to-file (serialize results) "stats-data.txt" #:exists 'replace))
(define (load-data-records)
(deserialize (file->value "stats-data.txt")))
(define recs (load-data-records))
(define (plot-lists xs ys zs kx ky kz)
(parameterize ([plot-width 700]
[plot-height 700]
[plot-x-label kx]
[plot-y-label ky]
[plot-z-label kz])
(plot3d
;#:x-max 2 #:x-min -2
#:y-min 140 #:y-max 400
#:z-max 1.5
#:angle 0
#:altitude 32
(points3d (map vector xs ys zs)
#:sym 'fullcircle7
#:alpha 0.1
#:color 42))))
(define (fu-formula rec)
(define pieces-per-line (/ (hash-ref (hash-ref rec 'fu) 'piece-count)
(hash-ref (hash-ref rec 'fu) 'line-count) 1.0))
(+ 2.2 (log (abs (hash-ref (hash-ref rec 'fu) 'looseness-sd)))
(* 0.09 pieces-per-line)))
(define (geo-mean rec)
(define looses (hash-ref (hash-ref rec 'fu) 'looses))
(expt (abs (apply * looses)) (/ 1 (length looses))))
(define (magic2 rec)
(define looses (map abs (hash-ref (hash-ref rec 'fu) 'looses)))
(expt (log (+ 1 (/ (variance looses) (expt (mean looses) 2)))) 0.5))
(define (plot-it)
(define-values (helped or-not) (partition (λ(rec) (< 0 (hash-ref rec 'bu-improved))) recs))
(define-values (fu-pos fu-neg) (partition (λ(rec) (> (fu-formula rec) 0)) recs))
(report* (length helped) (length or-not) (length fu-pos) (length fu-neg))
(let ([recs recs])
(plot-lists
(map fu-formula recs)
(map (λ(rec) (hash-ref rec 'measure)) recs)
(map (λ(rec) (if (= 0 (hash-ref rec 'bu-improved)) 0 1)) recs) "fu-formula" "measure" "improve?")))
(plot-it)
(define (looseness-improvement rec)
(/ (abs (- (hash-ref (hash-ref rec 'bu) 'avg-looseness) (hash-ref (hash-ref rec 'fu) 'avg-looseness)))
(hash-ref (hash-ref rec 'bu) 'avg-looseness)))
(define zs (filter positive? (sort (map looseness-improvement recs) <)))

@ -1,3 +0,0 @@
#lang quad
◊block['(measure 240.0 font "Times New Roman" leading 16.0 vmeasure 300.0 size 13.5 x-align justify x-align-last-line left)]{My Fellow Americans.}

@ -1,3 +0,0 @@
#lang quad
◊block[#f]{hi there everyone}

@ -1,2 +0,0 @@
#lang racket
(require "test-quadlangmod.rkt")

@ -1,79 +0,0 @@
#lang racket/base
(require racket/list racket/function rackunit "ocm.rkt" sugar)
(define (vector-range n)
(build-vector n identity))
(define (random-ascending len start finish)
(if (= len 0)
null
(let ([r (+ start (random (- finish start)))])
(cons r (random-ascending (sub1 len) r finish)))))
(define (concave-list len min [lowval 0][highval 100])
(append (reverse (random-ascending min lowval highval)) (random-ascending (- len min) lowval highval)))
(define (make-matrix2 rows cols)
(define row-minima-indexes (random-ascending rows 0 cols))
(reverse (for/list ([row-minima-index (in-list row-minima-indexes)])
(concave-list cols row-minima-index 20 400))))
(define (make-matrix rows cols)
(define seed (for/list ([i (in-range (max rows cols))])
(random 100)))
(for/list ([i (in-range rows)])
(for/list ([j (in-range cols)])
(if (< i j)
(apply + (sublist seed i (add1 j)))
(apply + (sublist seed j (add1 i)))))))
(define (make-matrix-proc m [is (range (length m))] [js (range (length (car m)))])
(let ([ipairs (apply hash (flatten (map cons is (range (length is)))))]
[jpairs (apply hash (flatten (map cons js (range (length js)))))])
(λ(i j)
(define my-i (hash-ref ipairs i))
(define my-j (hash-ref jpairs j))
(with-handlers [(exn:fail? (λ(exn) (* -1 i)))]
(list-ref (list-ref m my-i) my-j)))))
(define (compare-reductions m)
(check-equal?
(reduce2 (vector-range (length m)) (vector-range (length (car m))) (make-matrix-proc m) identity)
(reduce (vector-range (length m)) (vector-range (length (car m))) (make-matrix-proc m) identity)))
(define (do-it x)
(repeat x
(define rows (+ 2 (random 40)))
(define cols (+ 2 (random rows)))
(define m (make-matrix rows cols))
(check-true (smawky? m))
(compare-reductions m)))
(define me '((25 21 13 10 20 13 19 35 37 41 58 66 82 99 124 133 156 178) (42 35 26 20 29 21 25 37 36 39 56 64 76 91 116 125 146 164) (57 48 35 28 33 24 28 40 37 37 54 61 72 83 107 113 131 146) (78 65 51 42 44 35 38 48 42 42 55 61 70 80 100 106 120 135) (90 76 58 48 49 39 42 48 39 35 47 51 56 63 80 86 97 110) (103 85 67 56 55 44 44 49 39 33 41 44 49 56 71 75 84 96) (123 105 86 75 73 59 57 62 51 44 50 52 55 59 72 74 80 92) (142 123 100 86 82 65 61 62 50 43 47 45 46 46 58 59 65 73) (151 130 104 88 80 59 52 49 37 29 29 24 23 20 28 25 31 39)))
(define (bug-test bugmatrix bugrows bugcols)
(define bugproc (make-matrix-proc bugmatrix bugrows bugcols))
(check-equal? (reduce (list->vector bugrows) (list->vector bugcols) bugproc identity)
(reduce2 (list->vector bugrows) (list->vector bugcols) bugproc identity)))
(bug-test '((19496.0 14025.0 7134.0 5027.0) (108793.0 102427.0 93819.0 90268.0) (101409.0 93357.0 81509.0 75236.0) (106662.0 93357.0 71417.0 56665.0))
'(0 1 2 3)
'(4 5 6 7))
(bug-test '((25 42 57 78 90 103 123 142 151) (21 35 48 65 76 85 105 123 130) (13 26 35 51 58 67 86 100 104) (10 20 28 42 48 56 75 86 88) (20 29 33 44 49 55 73 82 80) (13 21 24 35 39 44 59 65 59) (19 25 28 38 42 44 57 61 52) (35 37 40 48 48 49 62 62 49) (37 36 37 42 39 39 51 50 37) (41 39 37 42 35 33 44 43 29) (58 56 54 55 47 41 50 47 29) (66 64 61 61 51 44 52 45 24) (82 76 72 70 56 49 55 46 23) (99 91 83 80 63 56 59 46 20) (124 116 107 100 80 71 72 58 28) (133 125 113 106 86 75 74 59 25) (156 146 131 120 97 84 80 65 31) (178 164 146 135 110 96 92 73 39))
'(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17)
'(0 1 2 3 4 5 6 7 8))
(do-it 10)

@ -1,95 +0,0 @@
#lang racket/base
(require "utils.rkt" "wrap.rkt" "quads.rkt" "world.rkt" racket/list racket/format)
(require rackunit)
(check-equal? (join-attrs (list (box '(width 10)) (box '() "foobar") (hash 'x 10) (list 'width 20)))
(list (cons 'width 10) (cons 'x 10) (cons 'width 20)))
(check-equal? (flatten-attrs (hash 'foo 'bar) (hash 'x 10)) (apply hash '(foo bar x 10)))
(check-equal? (flatten-attrs (hash 'x -5) (hash 'x 10)) (apply hash '(x 5)))
(check-equal? (merge-attrs (hash 'x -5) (hash 'x 10)) (apply hash '(x 10)))
(check-equal? (gather-common-attrs (list (box '(foo bar)) (box '(foo bar goo bar zam zino)) (box '(foo bar)))) '(foo bar))
(check-equal? (gather-common-attrs (list (box) (box '(foo bar goo bar zam zino)) (box '(foo bar)))) '())
(check-equal? (gather-common-attrs (list (box '(width bar)) (box '(width bar)) (box '(width bar)))) '())
(define b1 (box '(x 10) "1st" (box '(foo bar) "2nd") "3rd"))
(define b1-flattened (list (box '(x 10) "1st") (box '(x 10 foo bar) "2nd") (box '(x 10) "3rd")))
(define b3 (box '() (word) (line) (page)))
(check-true (sequence? b3))
;(check-equal? (for/list ([i (in-quad b3)]) i) (list (word) (line) (page)))
(check-true (quad= (flatten-quad b1) b1-flattened))
(define b2 (box '(x 10) (spacer) (box '(x 15) (spacer) (spacer)) (spacer)))
(define b2-flattened (list (spacer '(x 10)) (spacer '(x 25)) (spacer '(x 25)) (spacer '(x 10))))
(check-true (quad= (flatten-quad b2) b2-flattened))
(check-true (quad= (split-quad b2) b2-flattened))
(check-true (quad= (flatten-quad (box '(foo 10) (spacer) (box) (spacer))) (list (spacer '(foo 10)) (box '(foo 10)) (spacer '(foo 10)))))
(check-equal? (compute-absolute-positions (page '(x 100 y 100) (line '(x 10 y 10) (word '(x 1 y 1) "hello")
(word '(x 2 y 2) "world"))))
(page '(y 100.0 x 100.0) (line '(y 110.0 x 110.0) (word '(y 111.0 x 111.0) "hello")(word '(y 112.0 x 112.0) "world"))))
(define b2-exploded (list (word '(x 10) "1") (word '(x 10) "s") (word '(x 10) "t") (word '(x 10 foo bar) "2") (word '(x 10 foo bar) "n") (word '(x 10 foo bar) "d") (word '(x 10) "3") (word '(x 10) "r") (word '(x 10) "d")))
(check-true (quad= (split-quad b1) b2-exploded))
(check-false (quad-has-attr? (box) 'foo))
(check-true (quad-has-attr? (box '(foo bar)) 'foo))
(check-equal? (quad-attr-set (box '(foo bar)) 'foo 'zam) (box '(foo zam)))
(check-equal? (quad-attr-set (box '()) 'foo 'zam) (box '(foo zam)))
(check-equal? (quad-attr-set* (box '()) 'foo 'zam 'bar 'boo) (box '(foo zam bar boo)))
(check-equal? (quad-attr-set* (box '(foo bar)) 'foo 'zam 'bar 'boo) (box '(foo zam bar boo)))
(check-equal? (quad-attr-remove (box '(foo bar zim zam)) 'foo) (box '(zim zam)))
(check-equal? (quad-attr-remove (box '()) 'zim) (box))
(check-equal? (quad-attr-remove* (box '(foo bar zim zam ding dong)) 'foo 'ding) (box '(zim zam)))
(check-equal? (quad-attr-remove* (box '()) 'zim) (box))
(check-true (quad-ends-with? (box '() "foo") "foo"))
(check-false (quad-ends-with? (box '() "foo") "food"))
(check-false (quad-ends-with? (box '() (box '() "foo")) "food"))
(check-true (quad-ends-with? (box '() (box '() "foo")) "foo"))
(check-true (quad-ends-with? (box '() (box '() "foo")) "o"))
(check-true (quad-ends-with? (box '() (box '() (box '() (box '() (box '() "foo-"))))) "-"))
(check-equal? (quad-append (box '() "foo") "bar") (box '() "foo" "bar"))
(check-equal? (quad-append (box '() "foo") (box '() "bar")) (box '() "foo" (box '() "bar")))
(check-equal? (quad-last-char (box '() (box '() "foo") "food")) "d")
(check-equal? (quad-last-char (box '() (box '() "foo"))) "o")
(check-equal? (quad-last-char (box '() "foo")) "o")
(check-false (quad-last-char (box)))
(check-equal? (quad-first-char (box '() (box '() "foo") "bar")) "f")
(check-equal? (quad-first-char (box '() (box '() "foo") "bar")) "f")
(check-equal? (quad-first-char (box '() "foo")) "f")
(check-false (quad-first-char (box)))
(check-equal? (quad->string (box '(width 100) "foo")) "foo")
(check-equal? (quad->string (box '(width 100) "foo" (box '(width 100) "bar"))) "foobar")
(check-equal? (quad->string (box '(width 100) "foo" (box '(width 100) "bar") "ino")) "foobarino")
(check-equal? (quad->string (box '(width 100))) "")
(check-false (whitespace? (~a #\u00A0)))
(check-true (whitespace/nbsp? (~a #\u00A0)))
(check-true (whitespace/nbsp? (word '() (~a #\u00A0))))
(check-false (whitespace? (format " ~a " #\u00A0)))
(check-true (whitespace/nbsp? (format " ~a " #\u00A0)))
(define funny-unicode-spaces (map ~a (list #\u2000 #\u2007 #\u2009 #\u200a #\u202f)))
(check-true (andmap whitespace? funny-unicode-spaces))
(check-true (andmap whitespace/nbsp? funny-unicode-spaces))
(let ([world:minimum-last-line-chars 0])
(check-equal? (map (compose1 quad-list last quad-list) (make-pieces (split-quad (block '() "Foo-dog and " (box) " mas\u00adsachu.")))) '(("o") ("g") ("d") () ("s") ("."))))

@ -1,235 +0,0 @@
The schoolmaster was leaving the village, and everybody seemed sorry.
The miller at Cresscombe lent him the small white tilted cart and
horse to carry his goods to the city of his destination, about twenty
miles off, such a vehicle proving of quite sufficient size for the
departing teachers effects. For the schoolhouse had been partly
furnished by the managers, and the only cumbersome article possessed
by the master, in addition to the packing-case of books, was a
cottage piano that he had bought at an auction during the year in
which he thought of learning instrumental music. But the enthusiasm
having waned he had never acquired any skill in playing, and the
purchased article had been a perpetual trouble to him ever since in
moving house. Jude went out, and, feeling more than ever his existence to be an
undemanded one, he lay down upon his back on a heap of litter near
the pig-sty. The fog had by this time become more translucent, and
the position of the sun could be seen through it. He pulled his
straw hat over his face, and peered through the interstices of the
plaiting at the white brightness, vaguely reflecting. Growing up
brought responsibilities, he found. Events did not rhyme quite as
he had thought. Natures logic was too horrid for him to care for.
That mercy towards one set of creatures was cruelty towards another
sickened his sense of harmony. As you got older, and felt yourself
to be at the centre of your time, and not at a point in its
circumference, as you had felt when you were little, you were seized
with a sort of shuddering, he perceived. All around you there seemed
to be something glaring, garish, rattling, and the noises and glares
hit upon the little cell called your life, and shook it, and warped
it. The schoolmaster was leaving the village, and everybody seemed sorry.
The miller at Cresscombe lent him the small white tilted cart and
horse to carry his goods to the city of his destination, about twenty
miles off, such a vehicle proving of quite sufficient size for the
departing teachers effects. For the schoolhouse had been partly
furnished by the managers, and the only cumbersome article possessed
by the master, in addition to the packing-case of books, was a
cottage piano that he had bought at an auction during the year in
which he thought of learning instrumental music. But the enthusiasm
having waned he had never acquired any skill in playing, and the
purchased article had been a perpetual trouble to him ever since in
moving house. Jude went out, and, feeling more than ever his existence to be an
undemanded one, he lay down upon his back on a heap of litter near
the pig-sty. The fog had by this time become more translucent, and
the position of the sun could be seen through it. He pulled his
straw hat over his face, and peered through the interstices of the
plaiting at the white brightness, vaguely reflecting. Growing up
brought responsibilities, he found. Events did not rhyme quite as
he had thought. Natures logic was too horrid for him to care for.
That mercy towards one set of creatures was cruelty towards another
sickened his sense of harmony. As you got older, and felt yourself
to be at the centre of your time, and not at a point in its
circumference, as you had felt when you were little, you were seized
with a sort of shuddering, he perceived. All around you there seemed
to be something glaring, garish, rattling, and the noises and glares
hit upon the little cell called your life, and shook it, and warped
it. The schoolmaster was leaving the village, and everybody seemed sorry.
The miller at Cresscombe lent him the small white tilted cart and
horse to carry his goods to the city of his destination, about twenty
miles off, such a vehicle proving of quite sufficient size for the
departing teachers effects. For the schoolhouse had been partly
furnished by the managers, and the only cumbersome article possessed
by the master, in addition to the packing-case of books, was a
cottage piano that he had bought at an auction during the year in
which he thought of learning instrumental music. But the enthusiasm
having waned he had never acquired any skill in playing, and the
purchased article had been a perpetual trouble to him ever since in
moving house. Jude went out, and, feeling more than ever his existence to be an
undemanded one, he lay down upon his back on a heap of litter near
the pig-sty. The fog had by this time become more translucent, and
the position of the sun could be seen through it. He pulled his
straw hat over his face, and peered through the interstices of the
plaiting at the white brightness, vaguely reflecting. Growing up
brought responsibilities, he found. Events did not rhyme quite as
he had thought. Natures logic was too horrid for him to care for.
That mercy towards one set of creatures was cruelty towards another
sickened his sense of harmony. As you got older, and felt yourself
to be at the centre of your time, and not at a point in its
circumference, as you had felt when you were little, you were seized
with a sort of shuddering, he perceived. All around you there seemed
to be something glaring, garish, rattling, and the noises and glares
hit upon the little cell called your life, and shook it, and warped
it. The schoolmaster was leaving the village, and everybody seemed sorry.
The miller at Cresscombe lent him the small white tilted cart and
horse to carry his goods to the city of his destination, about twenty
miles off, such a vehicle proving of quite sufficient size for the
departing teachers effects. For the schoolhouse had been partly
furnished by the managers, and the only cumbersome article possessed
by the master, in addition to the packing-case of books, was a
cottage piano that he had bought at an auction during the year in
which he thought of learning instrumental music. But the enthusiasm
having waned he had never acquired any skill in playing, and the
purchased article had been a perpetual trouble to him ever since in
moving house. Jude went out, and, feeling more than ever his existence to be an
undemanded one, he lay down upon his back on a heap of litter near
the pig-sty. The fog had by this time become more translucent, and
the position of the sun could be seen through it. He pulled his
straw hat over his face, and peered through the interstices of the
plaiting at the white brightness, vaguely reflecting. Growing up
brought responsibilities, he found. Events did not rhyme quite as
he had thought. Natures logic was too horrid for him to care for.
That mercy towards one set of creatures was cruelty towards another
sickened his sense of harmony. As you got older, and felt yourself
to be at the centre of your time, and not at a point in its
circumference, as you had felt when you were little, you were seized
with a sort of shuddering, he perceived. All around you there seemed
to be something glaring, garish, rattling, and the noises and glares
hit upon the little cell called your life, and shook it, and warped
it. The schoolmaster was leaving the village, and everybody seemed sorry.
The miller at Cresscombe lent him the small white tilted cart and
horse to carry his goods to the city of his destination, about twenty
miles off, such a vehicle proving of quite sufficient size for the
departing teachers effects. For the schoolhouse had been partly
furnished by the managers, and the only cumbersome article possessed
by the master, in addition to the packing-case of books, was a
cottage piano that he had bought at an auction during the year in
which he thought of learning instrumental music. But the enthusiasm
having waned he had never acquired any skill in playing, and the
purchased article had been a perpetual trouble to him ever since in
moving house. Jude went out, and, feeling more than ever his existence to be an
undemanded one, he lay down upon his back on a heap of litter near
the pig-sty. The fog had by this time become more translucent, and
the position of the sun could be seen through it. He pulled his
straw hat over his face, and peered through the interstices of the
plaiting at the white brightness, vaguely reflecting. Growing up
brought responsibilities, he found. Events did not rhyme quite as
he had thought. Natures logic was too horrid for him to care for.
That mercy towards one set of creatures was cruelty towards another
sickened his sense of harmony. As you got older, and felt yourself
to be at the centre of your time, and not at a point in its
circumference, as you had felt when you were little, you were seized
with a sort of shuddering, he perceived. All around you there seemed
to be something glaring, garish, rattling, and the noises and glares
hit upon the little cell called your life, and shook it, and warped
it. The schoolmaster was leaving the village, and everybody seemed sorry.
The miller at Cresscombe lent him the small white tilted cart and
horse to carry his goods to the city of his destination, about twenty
miles off, such a vehicle proving of quite sufficient size for the
departing teachers effects. For the schoolhouse had been partly
furnished by the managers, and the only cumbersome article possessed
by the master, in addition to the packing-case of books, was a
cottage piano that he had bought at an auction during the year in
which he thought of learning instrumental music. But the enthusiasm
having waned he had never acquired any skill in playing, and the
purchased article had been a perpetual trouble to him ever since in
moving house. Jude went out, and, feeling more than ever his existence to be an
undemanded one, he lay down upon his back on a heap of litter near
the pig-sty. The fog had by this time become more translucent, and
the position of the sun could be seen through it. He pulled his
straw hat over his face, and peered through the interstices of the
plaiting at the white brightness, vaguely reflecting. Growing up
brought responsibilities, he found. Events did not rhyme quite as
he had thought. Natures logic was too horrid for him to care for.
That mercy towards one set of creatures was cruelty towards another
sickened his sense of harmony. As you got older, and felt yourself
to be at the centre of your time, and not at a point in its
circumference, as you had felt when you were little, you were seized
with a sort of shuddering, he perceived. All around you there seemed
to be something glaring, garish, rattling, and the noises and glares
hit upon the little cell called your life, and shook it, and warped
it. The schoolmaster was leaving the village, and everybody seemed sorry.
The miller at Cresscombe lent him the small white tilted cart and
horse to carry his goods to the city of his destination, about twenty
miles off, such a vehicle proving of quite sufficient size for the
departing teachers effects. For the schoolhouse had been partly
furnished by the managers, and the only cumbersome article possessed
by the master, in addition to the packing-case of books, was a
cottage piano that he had bought at an auction during the year in
which he thought of learning instrumental music. But the enthusiasm
having waned he had never acquired any skill in playing, and the
purchased article had been a perpetual trouble to him ever since in
moving house. Jude went out, and, feeling more than ever his existence to be an
undemanded one, he lay down upon his back on a heap of litter near
the pig-sty. The fog had by this time become more translucent, and
the position of the sun could be seen through it. He pulled his
straw hat over his face, and peered through the interstices of the
plaiting at the white brightness, vaguely reflecting. Growing up
brought responsibilities, he found. Events did not rhyme quite as
he had thought. Natures logic was too horrid for him to care for.
That mercy towards one set of creatures was cruelty towards another
sickened his sense of harmony. As you got older, and felt yourself
to be at the centre of your time, and not at a point in its
circumference, as you had felt when you were little, you were seized
with a sort of shuddering, he perceived. All around you there seemed
to be something glaring, garish, rattling, and the noises and glares
hit upon the little cell called your life, and shook it, and warped
it. The schoolmaster was leaving the village, and everybody seemed sorry.
The miller at Cresscombe lent him the small white tilted cart and
horse to carry his goods to the city of his destination, about twenty
miles off, such a vehicle proving of quite sufficient size for the
departing teachers effects. For the schoolhouse had been partly
furnished by the managers, and the only cumbersome article possessed
by the master, in addition to the packing-case of books, was a
cottage piano that he had bought at an auction during the year in
which he thought of learning instrumental music. But the enthusiasm
having waned he had never acquired any skill in playing, and the
purchased article had been a perpetual trouble to him ever since in
moving house. Jude went out, and, feeling more than ever his existence to be an
undemanded one, he lay down upon his back on a heap of litter near
the pig-sty. The fog had by this time become more translucent, and
the position of the sun could be seen through it. He pulled his
straw hat over his face, and peered through the interstices of the
plaiting at the white brightness, vaguely reflecting. Growing up
brought responsibilities, he found. Events did not rhyme quite as
he had thought. Natures logic was too horrid for him to care for.
That mercy towards one set of creatures was cruelty towards another
sickened his sense of harmony. As you got older, and felt yourself
to be at the centre of your time, and not at a point in its
circumference, as you had felt when you were little, you were seized
with a sort of shuddering, he perceived. All around you there seemed
to be something glaring, garish, rattling, and the noises and glares
hit upon the little cell called your life, and shook it, and warped
it. The schoolmaster was leaving the village, and everybody seemed sorry.
The miller at Cresscombe lent him the small white tilted cart and
horse to carry his goods to the city of his destination, about twenty
miles off, such a vehicle proving of quite sufficient size for the
departing teachers effects. For the schoolhouse had been partly
furnished by the managers, and the only cumbersome article possessed
by the master, in addition to the packing-case of books, was a
cottage piano that he had bought at an auction during the year in
which he thought of learning instrumental music. But the enthusiasm
having waned he had never acquired any skill in playing, and the
purchased article had been a perpetual trouble to him ever since in
moving house. Jude went out, and, feeling more than ever his existence to be an
undemanded one, he lay down upon his back on a heap of litter near
the pig-sty. The fog had by this time become more translucent, and
the position of the sun could be seen through it. He pulled his
straw hat over his face, and peered through the interstices of the
plaiting at the white brightness, vaguely reflecting. Growing up
brought responsibilities, he found. Events did not rhyme quite as
he had thought. Natures logic was too horrid for him to care for.
That mercy towards one set of creatures was cruelty towards another
sickened his sense of harmony. As you got older, and felt yourself
to be at the centre of your time, and not at a point in its
circumference, as you had felt when you were little, you were seized
with a sort of shuddering, he perceived. All around you there seemed
to be something glaring, garish, rattling, and the noises and glares
hit upon the little cell called your life, and shook it, and warped
it.

@ -1,429 +0,0 @@
The schoolmaster was leaving the village, and everybody seemed sorry.
The miller at Cresscombe lent him the small white tilted cart and
horse to carry his goods to the city of his destination, about twenty
miles off, such a vehicle proving of quite sufficient size for the
departing teachers effects. For the schoolhouse had been partly
furnished by the managers, and the only cumbersome article possessed
by the master, in addition to the packing-case of books, was a
cottage piano that he had bought at an auction during the year in
which he thought of learning instrumental music. But the enthusiasm
having waned he had never acquired any skill in playing, and the
purchased article had been a perpetual trouble to him ever since in
moving house.
The rector had gone away for the day, being a man who disliked the
sight of changes. He did not mean to return till the evening, when
the new school-teacher would have arrived and settled in, and
everything would be smooth again.
The blacksmith, the farm bailiff, and the schoolmaster himself were
standing in perplexed attitudes in the parlour before the instrument.
The master had remarked that even if he got it into the cart he
should not know what to do with it on his arrival at Christminster,
the city he was bound for, since he was only going into temporary
lodgings just at first.
A little boy of eleven, who had been thoughtfully assisting in the
packing, joined the group of men, and as they rubbed their chins he
spoke up, blushing at the sound of his own voice: “Aunt have got a
great fuel-house, and it could be put there, perhaps, till youve
found a place to settle in, sir.”
“A proper good notion,” said the blacksmith.
It was decided that a deputation should wait on the boys aunt—an
old maiden resident—and ask her if she would house the piano till
Mr. Phillotson should send for it. The smith and the bailiff started
to see about the practicability of the suggested shelter, and the boy
and the schoolmaster were left standing alone.
“Sorry I am going, Jude?” asked the latter kindly.
Tears rose into the boys eyes, for he was not among the regular day
scholars, who came unromantically close to the schoolmasters life,
but one who had attended the night school only during the present
teachers term of office. The regular scholars, if the truth must
be told, stood at the present moment afar off, like certain historic
disciples, indisposed to any enthusiastic volunteering of aid.
The boy awkwardly opened the book he held in his hand, which Mr.
Phillotson had bestowed on him as a parting gift, and admitted that
he was sorry.
“So am I,” said Mr. Phillotson.
“Why do you go, sir?” asked the boy.
“Ah—that would be a long story. You wouldnt understand my reasons,
Jude. You will, perhaps, when you are older.”
“I think I should now, sir.”
“Well—dont speak of this everywhere. You know what a university
is, and a university degree? It is the necessary hallmark of a man
who wants to do anything in teaching. My scheme, or dream, is to be
a university graduate, and then to be ordained. By going to live at
Christminster, or near it, I shall be at headquarters, so to speak,
and if my scheme is practicable at all, I consider that being on the
spot will afford me a better chance of carrying it out than I should
have elsewhere.”
The smith and his companion returned. Old Miss Fawleys fuel-house
was dry, and eminently practicable; and she seemed willing to give
the instrument standing-room there. It was accordingly left in
the school till the evening, when more hands would be available for
removing it; and the schoolmaster gave a final glance round.
The boy Jude assisted in loading some small articles, and at nine
oclock Mr. Phillotson mounted beside his box of books and other
impedimenta, and bade his friends good-bye.
“I shant forget you, Jude,” he said, smiling, as the cart moved off.
“Be a good boy, remember; and be kind to animals and birds, and read
all you can. And if ever you come to Christminster remember you hunt
me out for old acquaintance sake.”
The cart creaked across the green, and disappeared round the corner
by the rectory-house. The boy returned to the draw-well at the edge
of the greensward, where he had left his buckets when he went to help
his patron and teacher in the loading. There was a quiver in his lip
now and after opening the well-cover to begin lowering the bucket he
paused and leant with his forehead and arms against the framework,
his face wearing the fixity of a thoughtful childs who has felt the
pricks of life somewhat before his time. The well into which he was
looking was as ancient as the village itself, and from his present
position appeared as a long circular perspective ending in a shining
disk of quivering water at a distance of a hundred feet down.
There was a lining of green moss near the top, and nearer still the
harts-tongue fern.
He said to himself, in the melodramatic tones of a whimsical boy,
that the schoolmaster had drawn at that well scores of times on a
morning like this, and would never draw there any more. “Ive seen
him look down into it, when he was tired with his drawing, just as I
do now, and when he rested a bit before carrying the buckets home!
But he was too clever to bide here any longer—a small sleepy place
like this!”
A tear rolled from his eye into the depths of the well. The morning
was a little foggy, and the boys breathing unfurled itself as
a thicker fog upon the still and heavy air. His thoughts were
interrupted by a sudden outcry:
“Bring on that water, will ye, you idle young harlican!”
It came from an old woman who had emerged from her door towards the
garden gate of a green-thatched cottage not far off. The boy quickly
waved a signal of assent, drew the water with what was a great effort
for one of his stature, landed and emptied the big bucket into his
own pair of smaller ones, and pausing a moment for breath, started
with them across the patch of clammy greensward whereon the well
stood—nearly in the centre of the little village, or rather hamlet
of Marygreen.
It was as old-fashioned as it was small, and it rested in the lap of
an undulating upland adjoining the North Wessex downs. Old as it
was, however, the well-shaft was probably the only relic of the local
history that remained absolutely unchanged. Many of the thatched
and dormered dwelling-houses had been pulled down of late years, and
many trees felled on the green. Above all, the original church,
hump-backed, wood-turreted, and quaintly hipped, had been taken
down, and either cracked up into heaps of road-metal in the lane, or
utilized as pig-sty walls, garden seats, guard-stones to fences, and
rockeries in the flower-beds of the neighbourhood. In place of it
a tall new building of modern Gothic design, unfamiliar to English
eyes, had been erected on a new piece of ground by a certain
obliterator of historic records who had run down from London and back
in a day. The site whereon so long had stood the ancient temple to
the Christian divinities was not even recorded on the green and level
grass-plot that had immemorially been the churchyard, the obliterated
graves being commemorated by eighteen-penny cast-iron crosses
warranted to last five years.
II
Slender as was Jude Fawleys frame he bore the two brimming
house-buckets of water to the cottage without resting. Over the door
was a little rectangular piece of blue board, on which was painted
in yellow letters, “Drusilla Fawley, Baker.” Within the little lead
panes of the window—this being one of the few old houses left—were
five bottles of sweets, and three buns on a plate of the willow
pattern.
While emptying the buckets at the back of the house he could hear an
animated conversation in progress within-doors between his great-aunt,
the Drusilla of the sign-board, and some other villagers. Having
seen the school-master depart, they were summing up particulars of
the event, and indulging in predictions of his future.
“And whos he?” asked one, comparatively a stranger, when the boy
entered.
“Well ye med ask it, Mrs. Williams. Hes my great-nephew—come since
you was last this way.” The old inhabitant who answered was a tall,
gaunt woman, who spoke tragically on the most trivial subject, and
gave a phrase of her conversation to each auditor in turn. “He come
from Mellstock, down in South Wessex, about a year ago—worse luck
for n, Belinda” (turning to the right) “where his father was living,
and was took wi the shakings for death, and died in two days, as you
know, Caroline” (turning to the left). “It would ha been a blessing
if Goddy-mighty had took thee too, wi thy mother and father, poor
useless boy! But Ive got him here to stay with me till I can see
whats to be done with un, though I am obliged to let him earn any
penny he can. Just now hes a-scaring of birds for Farmer Troutham.
It keeps him out of mischty. Why do ye turn away, Jude?” she
continued, as the boy, feeling the impact of their glances like slaps
upon his face, moved aside.
The local washerwoman replied that it was perhaps a very good plan of
Miss or Mrs. Fawleys (as they called her indifferently) to have him
with her—”to kip ee company in your loneliness, fetch water, shet
the winder-shetters o nights, and help in the bit o baking.”
Miss Fawley doubted it.... “Why didnt ye get the schoolmaster to
take ee to Christminster wi un, and make a scholar of ee,” she
continued, in frowning pleasantry. “Im sure he couldnt ha took a
better one. The boy is crazy for books, that he is. It runs in our
family rather. His cousin Sue is just the same—so Ive heard; but
I have not seen the child for years, though she was born in this
place, within these four walls, as it happened. My niece and her
husband, after they were married, didn get a house of their own for
some year or more; and then they only had one till—Well, I wont go
into that. Jude, my child, dont you ever marry. Tisnt for the
Fawleys to take that step any more. She, their only one, was like
a child o my own, Belinda, till the split come! Ah, that a little
maid should know such changes!”
Jude, finding the general attention again centering on himself, went
out to the bakehouse, where he ate the cake provided for his
breakfast. The end of his spare time had now arrived, and emerging
from the garden by getting over the hedge at the back he pursued a
path northward, till he came to a wide and lonely depression in the
general level of the upland, which was sown as a corn-field. This
vast concave was the scene of his labours for Mr Troutham the farmer,
and he descended into the midst of it.
The brown surface of the field went right up towards the sky all
round, where it was lost by degrees in the mist that shut out the
actual verge and accentuated the solitude. The only marks on the
uniformity of the scene were a rick of last years produce standing
in the midst of the arable, the rooks that rose at his approach, and
the path athwart the fallow by which he had come, trodden now by he
hardly knew whom, though once by many of his own dead family.
“How ugly it is here!” he murmured.
The fresh harrow-lines seemed to stretch like the channellings in
a piece of new corduroy, lending a meanly utilitarian air to the
expanse, taking away its gradations, and depriving it of all history
beyond that of the few recent months, though to every clod and stone
there really attached associations enough and to spare—echoes of
songs from ancient harvest-days, of spoken words, and of sturdy
deeds. Every inch of ground had been the site, first or last,
of energy, gaiety, horse-play, bickerings, weariness. Groups of
gleaners had squatted in the sun on every square yard. Love-matches
that had populated the adjoining hamlet had been made up there
between reaping and carrying. Under the hedge which divided the
field from a distant plantation girls had given themselves to lovers
who would not turn their heads to look at them by the next harvest;
and in that ancient cornfield many a man had made love-promises to
a woman at whose voice he had trembled by the next seed-time after
fulfilling them in the church adjoining. But this neither Jude nor
the rooks around him considered. For them it was a lonely place,
possessing, in the one view, only the quality of a work-ground, and
in the other that of a granary good to feed in.
The boy stood under the rick before mentioned, and every few seconds
used his clacker or rattle briskly. At each clack the rooks left off
pecking, and rose and went away on their leisurely wings, burnished
like tassets of mail, afterwards wheeling back and regarding him
warily, and descending to feed at a more respectful distance.
He sounded the clacker till his arm ached, and at length his heart
grew sympathetic with the birds thwarted desires. They seemed, like
himself, to be living in a world which did not want them. Why should
he frighten them away? They took upon more and more the aspect of
gentle friends and pensioners—the only friends he could claim as
being in the least degree interested in him, for his aunt had often
told him that she was not. He ceased his rattling, and they alighted
anew.
“Poor little dears!” said Jude, aloud. “You SHALL have some dinner—
you shall. There is enough for us all. Farmer Troutham can afford
to let you have some. Eat, then my dear little birdies, and make a
good meal!”
They stayed and ate, inky spots on the nut-brown soil, and Jude
enjoyed their appetite. A magic thread of fellow-feeling united his
own life with theirs. Puny and sorry as those lives were, they much
resembled his own.
His clacker he had by this time thrown away from him, as being a mean
and sordid instrument, offensive both to the birds and to himself
as their friend. All at once he became conscious of a smart blow
upon his buttocks, followed by a loud clack, which announced to his
surprised senses that the clacker had been the instrument of offence
used. The birds and Jude started up simultaneously, and the dazed
eyes of the latter beheld the farmer in person, the great Troutham
himself, his red face glaring down upon Judes cowering frame, the
clacker swinging in his hand.
“So its Eat my dear birdies, is it, young man? Eat, dear
birdies, indeed! Ill tickle your breeches, and see if you say,
Eat, dear birdies, again in a hurry! And youve been idling at the
schoolmasters too, instead of coming here, hant ye, hey? Thats
how you earn your sixpence a day for keeping the rooks off my corn!”
Whilst saluting Judes ears with this impassioned rhetoric, Troutham
had seized his left hand with his own left, and swinging his slim
frame round him at arms-length, again struck Jude on the hind parts
with the flat side of Judes own rattle, till the field echoed with
the blows, which were delivered once or twice at each revolution.
“Dont ee, sir—please dont ee!” cried the whirling child, as
helpless under the centrifugal tendency of his person as a hooked
fish swinging to land, and beholding the hill, the rick, the
plantation, the path, and the rooks going round and round him in an
amazing circular race. “I—I sir—only meant that—there was a good
crop in the ground—I saw em sow it—and the rooks could have a
little bit for dinner—and you wouldnt miss it, sir—and Mr.
Phillotson said I was to be kind to em—oh, oh, oh!”
This truthful explanation seemed to exasperate the farmer even more
than if Jude had stoutly denied saying anything at all, and he still
smacked the whirling urchin, the clacks of the instrument continuing
to resound all across the field and as far as the ears of distant
workers—who gathered thereupon that Jude was pursuing his business
of clacking with great assiduity—and echoing from the brand-new
church tower just behind the mist, towards the building of which
structure the farmer had largely subscribed, to testify his love for
God and man.
Presently Troutham grew tired of his punitive task, and depositing
the quivering boy on his legs, took a sixpence from his pocket and
gave it him in payment for his days work, telling him to go home and
never let him see him in one of those fields again.
Jude leaped out of arms reach, and walked along the trackway
weeping—not from the pain, though that was keen enough; not from the
perception of the flaw in the terrestrial scheme, by which what was
good for Gods birds was bad for Gods gardener; but with the awful
sense that he had wholly disgraced himself before he had been a year
in the parish, and hence might be a burden to his great-aunt for
life.
With this shadow on his mind he did not care to show himself in the
village, and went homeward by a roundabout track behind a high hedge
and across a pasture. Here he beheld scores of coupled earthworms
lying half their length on the surface of the damp ground, as
they always did in such weather at that time of the year. It was
impossible to advance in regular steps without crushing some of them
at each tread.
Though Farmer Troutham had just hurt him, he was a boy who could not
himself bear to hurt anything. He had never brought home a nest of
young birds without lying awake in misery half the night after, and
often reinstating them and the nest in their original place the next
morning. He could scarcely bear to see trees cut down or lopped,
from a fancy that it hurt them; and late pruning, when the sap was up
and the tree bled profusely, had been a positive grief to him in his
infancy. This weakness of character, as it may be called, suggested
that he was the sort of man who was born to ache a good deal before
the fall of the curtain upon his unnecessary life should signify that
all was well with him again. He carefully picked his way on tiptoe
among the earthworms, without killing a single one.
On entering the cottage he found his aunt selling a penny loaf to a
little girl, and when the customer was gone she said, “Well, how do
you come to be back here in the middle of the morning like this?”
“Im turned away.”
“What?”
“Mr. Troutham have turned me away because I let the rooks have a few
peckings of corn. And theres my wages—the last I shall ever hae!”
He threw the sixpence tragically on the table.
“Ah!” said his aunt, suspending her breath. And she opened upon him
a lecture on how she would now have him all the spring upon her hands
doing nothing. “If you cant skeer birds, what can ye do? There!
dont ye look so deedy! Farmer Troutham is not so much better than
myself, come to that. But tis as Job said, Now they that are
younger than I have me in derision, whose fathers I would have
disdained to have set with the dogs of my flock. His father was my
fathers journeyman, anyhow, and I must have been a fool to let ee
go to work for n, which I shouldnt ha done but to keep ee out of
mischty.”
More angry with Jude for demeaning her by coming there than for
dereliction of duty, she rated him primarily from that point of view,
and only secondarily from a moral one.
“Not that you should have let the birds eat what Farmer Troutham
planted. Of course you was wrong in that. Jude, Jude, why didstnt
go off with that schoolmaster of thine to Christminster or somewhere?
But, oh no—poor ornary child—there never was any sprawl on thy
side of the family, and never will be!”
“Where is this beautiful city, Aunt—this place where Mr. Phillotson
is gone to?” asked the boy, after meditating in silence.
“Lord! you ought to know where the city of Christminster is. Near a
score of miles from here. It is a place much too good for you ever
to have much to do with, poor boy, Im a-thinking.”
“And will Mr. Phillotson always be there?”
“How can I tell?”
“Could I go to see him?”
“Lord, no! You didnt grow up hereabout, or you wouldnt ask such as
that. Weve never had anything to do with folk in Christminster, nor
folk in Christminster with we.”
Jude went out, and, feeling more than ever his existence to be an
undemanded one, he lay down upon his back on a heap of litter near
the pig-sty. The fog had by this time become more translucent, and
the position of the sun could be seen through it. He pulled his
straw hat over his face, and peered through the interstices of the
plaiting at the white brightness, vaguely reflecting. Growing up
brought responsibilities, he found. Events did not rhyme quite as
he had thought. Natures logic was too horrid for him to care for.
That mercy towards one set of creatures was cruelty towards another
sickened his sense of harmony. As you got older, and felt yourself
to be at the centre of your time, and not at a point in its
circumference, as you had felt when you were little, you were seized
with a sort of shuddering, he perceived. All around you there seemed
to be something glaring, garish, rattling, and the noises and glares
hit upon the little cell called your life, and shook it, and warped
it.
If he could only prevent himself growing up! He did not want to be a
man.
Then, like the natural boy, he forgot his despondency, and sprang up.
During the remainder of the morning he helped his aunt, and in the
afternoon, when there was nothing more to be done, he went into the
village. Here he asked a man whereabouts Christminster lay.
“Christminster? Oh, well, out by there yonder; though Ive never bin
there—not I. Ive never had any business at such a place.”
The man pointed north-eastward, in the very direction where lay that
field in which Jude had so disgraced himself. There was something
unpleasant about the coincidence for the moment, but the fearsomeness
of this fact rather increased his curiosity about the city. The
farmer had said he was never to be seen in that field again; yet
Christminster lay across it, and the path was a public one. So,
stealing out of the hamlet, he descended into the same hollow which
had witnessed his punishment in the morning, never swerving an inch
from the path, and climbing up the long and tedious ascent on the
other side till the track joined the highway by a little clump of
trees. Here the ploughed land ended, and all before him was bleak
open down.

@ -1,4 +0,0 @@
“I shant forget you, Jude,” he said, smiling, as the cart moved off.
“Be a good boy, remember; and be kind to animals and birds, and read
all you can. And if ever you come to Christminster remember you hunt
me out for old acquaintance sake.”

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,12 +0,0 @@
#lang racket/base
;; todo next
;; pagination: vertical measuring
;; adaptive linebreak using fu-formula
;; unified model of filling / positioning that works for every quad, recursively
;; how to handle constraint failure. At least a good error message.
;; imperative line break
;; why do certain MB fonts only appear in bold?
;; deal with separating / recombining footnote flow
;; deal with page number flags (for toc, index, etc)
;; disk cache of previously wrapped lines

@ -1,213 +0,0 @@
#lang racket/base
(require racket/list sugar racket/contract racket/function math/flonum)
(require "quads.rkt" "utils.rkt" "wrap.rkt" "measure.rkt" "world.rkt" "logger.rkt")
(provide typeset)
(require sugar/debug)
(define (input->nested-blocks i)
(define-syntax-rule (cons-reverse x y) (cons (reverse x) y))
(define-values (mps mcs bs b)
(for/fold ([multipages empty][multicolumns empty][blocks empty][block-acc empty])
([q (in-list (split-quad i))])
(cond
[(page-break? q) (values (cons-reverse (cons-reverse (cons-reverse block-acc blocks) multicolumns) multipages) empty empty empty)]
[(column-break? q) (values multipages (cons-reverse (cons-reverse block-acc blocks) multicolumns) empty empty)]
[(block-break? q) (values multipages multicolumns (cons-reverse block-acc blocks) empty)]
[else (values multipages multicolumns blocks (cons q block-acc))])))
(reverse (cons-reverse (cons-reverse (cons-reverse b bs) mcs) mps)))
(define (merge-adjacent-within q)
(quad (quad-name q) (quad-attrs q) (join-quads (quad-list q))))
(define (hyphenate-quad-except-last-word q)
(log-quad-debug "last word will not be hyphenated")
(define-values (first-quads last-quad) (split-last (quad-list q)))
(quad (quad-name q) (quad-attrs q) (snoc (map hyphenate-quad first-quads) last-quad)))
(define+provide (average-looseness lines)
(if (<= (length lines) 1)
0.0
(let ([lines-to-measure (drop-right lines 1)]) ; exclude last line from looseness calculation
(round-float (fl/ (fold-fl+ (map (λ(line) (quad-attr-ref line world:line-looseness-key 0.0)) lines-to-measure)) (fl- (fl (length lines)) 1.0))))))
(define (log-debug-lines lines)
(log-quad-debug "line report:")
(for/list ([(line idx) (in-indexed lines)])
(format "~a/~a: ~v ~a" idx
(length lines)
(quad->string line)
(quad-attr-ref line world:line-looseness-key))))
(require racket/trace)
(define (block->lines b)
(define quality (quad-attr-ref/parameter b world:quality-key))
(define (wrap-quads qs)
(define wrap-proc (cond
[(>= quality world:max-quality) wrap-best]
[(<= quality world:draft-quality) wrap-first]
[else wrap-adaptive]))
(wrap-proc qs))
(log-quad-debug "wrapping lines")
(log-quad-debug "quality = ~a" quality)
(log-quad-debug "looseness tolerance = ~a" world:line-looseness-tolerance)
(define wrapped-lines-without-hyphens (wrap-quads (quad-list b))) ; 100/150
(log-quad-debug* (log-debug-lines wrapped-lines-without-hyphens))
(define avg-looseness (average-looseness wrapped-lines-without-hyphens))
(define gets-hyphenation? (and world:use-hyphenation?
(fl> avg-looseness world:line-looseness-tolerance)))
(log-quad-debug "average looseness = ~a" avg-looseness)
(log-quad-debug (if gets-hyphenation? "hyphenating" "no hyphenation needed"))
(define wrapped-lines (if gets-hyphenation?
(wrap-quads (split-quad ((if world:allow-hyphenated-last-word-in-paragraph
hyphenate-quad
hyphenate-quad-except-last-word) (merge-adjacent-within b))))
wrapped-lines-without-hyphens))
(when gets-hyphenation? (log-quad-debug* (log-debug-lines wrapped-lines)))
(log-quad-debug "final looseness = ~a" (average-looseness wrapped-lines))
(map insert-spacers-in-line
(for/list ([line-idx (in-naturals)][line (in-list wrapped-lines)])
(quad-attr-set* line 'line-idx line-idx 'lines (length wrapped-lines)))))
(define+provide (number-pages ps)
(pages? . -> . pages?)
(for/list ([i (in-naturals)][p (in-list ps)])
(quad (quad-name p) (merge-attrs (quad-attrs p) `(page ,i)) (quad-list p))))
(define+provide (pages->doc ps)
(pages? . -> . doc?)
;; todo: resolve xrefs and other last-minute tasks
;; todo: generalize computation of widths and heights, recursively
(define (columns-mapper page)
(quad-map (compose1 add-vert-positions (curry quad-map (compose1 compute-line-height add-horiz-positions fill))) page))
(define mapped-pages (map columns-mapper (number-pages ps)))
(define doc (quads->doc mapped-pages))
doc)
(require racket/class csp)
(define+provide (lines->columns lines)
(lines? . -> . columns?)
(define prob (new problem%))
(define max-column-lines world:default-lines-per-column)
(define-values (columns ignored-return-value)
(for/fold ([columns null][lines-remaining lines])([col-idx (in-naturals)] #:break (empty? lines-remaining))
(log-quad-info "making column ~a" (add1 col-idx))
;; domain constraint is best way to simplify csp, because it limits the search space.
;; search from largest possible value to smallest.
;; largest possible is the minimum of the max column lines, or
;; the number of lines left (modulo minimum page lines) ...
(define viable-column-range
(range (min max-column-lines (max
(length lines-remaining)
(- (length lines-remaining) world:minimum-lines-per-column)))
;; ... and the smallest possible is 1, or the current minimum lines.
;; (sub1 insures that range is inclusive of last value.)
(sub1 (min 1 world:minimum-lines-per-column)) -1))
(log-quad-debug "viable number of lines for this column to start =\n~a" viable-column-range)
(send prob add-variable "column-lines" viable-column-range)
;; greediness constraint: leave enough lines for next page, or take all
(define (greediness-constraint pl)
(define leftover (- (length lines-remaining) pl))
(or (= leftover 0) (>= leftover world:minimum-lines-per-column)))
(send prob add-constraint greediness-constraint '("column-lines"))
(log-quad-debug "viable number of lines after greediness constraint =\n~a" (map (curryr hash-ref "column-lines") (send prob get-solutions)))
;; last lines constraint: don't take page that will end with too few lines of last paragraph.
(define (last-lines-constraint pl)
(define last-line-of-page (list-ref lines-remaining (sub1 pl)))
(define lines-in-this-paragraph (quad-attr-ref last-line-of-page world:total-lines-key))
(define line-index-of-last-line (quad-attr-ref last-line-of-page world:line-index-key))
(define (paragraph-too-short-to-meet-constraint?)
(< lines-in-this-paragraph world:min-last-lines))
(or (paragraph-too-short-to-meet-constraint?)
(>= (add1 line-index-of-last-line) world:min-last-lines)))
(send prob add-constraint last-lines-constraint '("column-lines"))
(log-quad-debug "viable number of lines after last-lines constraint =\n~a" (map (curryr hash-ref "column-lines") (send prob get-solutions)))
;; first lines constraint: don't take page that will leave too few lines at top of next page
(define (first-lines-constraint pl lines-remaining)
(define last-line-of-page (list-ref lines-remaining (sub1 pl)))
(define lines-in-this-paragraph (quad-attr-ref last-line-of-page world:total-lines-key))
(define line-index-of-last-line (quad-attr-ref last-line-of-page world:line-index-key))
(define lines-that-will-remain (- lines-in-this-paragraph (add1 line-index-of-last-line)))
(define (paragraph-too-short-to-meet-constraint?)
(< lines-in-this-paragraph world:min-first-lines))
(or (paragraph-too-short-to-meet-constraint?)
(= 0 lines-that-will-remain) ; ok to use all lines ...
(>= lines-that-will-remain world:min-first-lines))) ; but if any remain, must be minimum number.
(send prob add-constraint (curryr first-lines-constraint lines-remaining) '("column-lines"))
(log-quad-debug "viable number of lines after first-lines constraint =\n~a" (map (curryr hash-ref "column-lines") (send prob get-solutions)))
(define s (send prob get-solution))
(define how-many-lines-to-take (hash-ref s "column-lines"))
(define-values (lines-to-take lines-to-leave) (split-at lines-remaining how-many-lines-to-take))
(log-quad-debug "taking ~a lines for column ~a:" how-many-lines-to-take (add1 col-idx))
(map (λ(idx line) (log-quad-debug "~a:~a ~v" (add1 col-idx) (add1 idx) (quad->string line))) (range how-many-lines-to-take) lines-to-take)
(send prob reset)
(values (cons (quad-attr-set (quads->column lines-to-take) world:column-index-key col-idx) columns) lines-to-leave)))
(reverse columns))
(define (columns->pages cols)
(columns? . -> . pages?)
(define columns-per-page (quad-attr-ref/parameter (car cols) world:column-count-key))
(define column-gutter (quad-attr-ref/parameter (car cols) world:column-gutter-key))
;; don't use default value here. If the col doesn't have a measure key,
;; it deserves to be an error, because that means the line was composed incorrectly.
(when (not (quad-has-attr? (car cols) world:measure-key))
(error 'columns->pages "column attrs contain no measure key: ~a ~a" (quad-attrs (car cols)) (quad-car (car cols))))
(define column-width (quad-attr-ref (car cols) world:measure-key))
(define width-of-printed-area (+ (* columns-per-page column-width) (* (sub1 columns-per-page) column-gutter)))
(define result-pages
(map (λ(cols) (quads->page cols))
(for/list ([page-cols (in-list (slice-at cols columns-per-page))])
(define-values (last-x cols)
(for/fold ([current-x (/ (- (world:paper-width-default) width-of-printed-area) 2)][cols empty]) ([col (in-list page-cols)][idx (in-naturals)])
(values (+ current-x column-width column-gutter) (cons (quad-attr-set* col 'x current-x 'y world:top-page-margin world:column-index-key idx) cols))))
(reverse cols))))
result-pages)
(define current-eof (make-parameter (gensym)))
(define (eof? x) (equal? x (current-eof)))
(define (block-quads->lines qs)
(block->lines (quads->block qs)))
(define (typeset x)
(coerce/input? . -> . doc?)
(load-text-cache-file)
(define pages (append* (for/list ([multipage (in-list (input->nested-blocks x))])
(columns->pages (append* (for/list ([multicolumn (in-list multipage)])
(lines->columns (append* (for/list ([block-quads (in-list multicolumn)])
(block-quads->lines block-quads))))))))))
(define doc (pages->doc pages))
(update-text-cache-file)
doc)
(module+ main
(require "render.rkt" racket/class profile sugar/debug)
(require "samples.rkt")
(activate-logger quad-logger)
(parameterize ([world:quality-default world:draft-quality]
[world:paper-width-default 600]
[world:paper-height-default 700])
#;(define sample (block '(measure 54.0 leading 18.0) "\n" "\n" "Meg is an ally."))
(let ([toa (begin (time (typeset (dynamic-require "foo2.rkt" 'out))))]
[tob (typeset (block '(measure 54.0 leading 18.0) "Meg \nis an ally."))])
(report* toa tob (equal? toa tob))
(time (send (new pdf-renderer%) render-to-file toa "foo-a.pdf"))
(time (send (new pdf-renderer%) render-to-file tob "foo-b.pdf")))))

@ -1,268 +0,0 @@
#lang racket/base
(require sugar/list sugar/define)
(require (for-syntax racket/syntax racket/base) racket/string racket/contract racket/list sugar/debug racket/bool hyphenate racket/function math/flonum)
(require "quads.rkt" "world.rkt" "measure.rkt")
;; predicate for use below
(define (list-of-mergeable-attrs? xs)
(and (list? xs) (andmap (λ(x) (or (quad? x) (quad-attrs? x) (hashable-list? x))) xs)))
;; faster than (listof pair?)
(define (pairs? x) (and (list? x) (andmap pair? x)))
;; push together multiple attr sources into one list of pairs.
;; mostly a helper function for the two attr functions below.
(define+provide (join-attrs quads-or-attrs-or-lists)
(list-of-mergeable-attrs? . -> . pairs?)
(append-map hash->list (filter-not false? (map (λ(x)
(cond
[(quad? x) (quad-attrs x)]
[(quad-attrs? x) x]
[(hashable-list? x) (apply hash x)]
[else #f])) quads-or-attrs-or-lists))))
;; merge concatenates attributes, with later ones overriding earlier.
;; most of the work is done by join-attrs.
(define+provide (merge-attrs . quads-or-attrs-or-lists)
(() #:rest list-of-mergeable-attrs? . ->* . quad-attrs?)
(define all-attrs (join-attrs quads-or-attrs-or-lists))
(apply hash (flatten all-attrs)))
;; functionally update a quad attr. Similar to hash-set
(define+provide (quad-attr-set q k v)
(quad? symbol? any/c . -> . quad?)
(quad (quad-name q) (merge-attrs (quad-attrs q) (list k v)) (quad-list q)))
;; functionally update multiple quad attrs. Similar to hash-set*
(define+provide (quad-attr-set* q . kvs)
((quad?) #:rest hashable-list? . ->* . quad?)
(for/fold ([current-q q])([kv-list (in-list (slice-at kvs 2))])
(apply quad-attr-set current-q kv-list)))
;; functionally remove a quad attr. Similar to hash-remove
(define+provide (quad-attr-remove q k)
(quad? symbol? . -> . quad?)
(if (quad-attrs q)
(quad (quad-name q) (hash-remove (quad-attrs q) k) (quad-list q))
q))
;; functionally remove multiple quad attrs. Similar to hash-remove
(define+provide (quad-attr-remove* q . ks)
((quad?) #:rest (λ(ks) (and (list? ks) (andmap symbol? ks))) . ->* . quad?)
(for/fold ([current-q q])([k (in-list ks)])
(quad-attr-remove current-q k)))
(define+provide (quad-map proc q)
(procedure? quad? . -> . quad?)
(quad (quad-name q) (quad-attrs q) (map proc (quad-list q))))
;; flatten merges attributes, but applies special logic suitable to flattening
;; for instance, resolving x and y coordinates.
(define+provide (flatten-attrs . quads-or-attrs-or-falses)
(() #:rest (listof (or/c quad? quad-attrs?)) . ->* . quad-attrs?)
(define all-attrs (join-attrs quads-or-attrs-or-falses))
(define-values (x-attrs y-attrs other-attrs-reversed)
(for/fold ([xas null][yas null][oas null])([attr (in-list all-attrs)])
(cond
[(equal? (car attr) world:x-position-key) (values (cons attr xas) yas oas)]
[(equal? (car attr) world:y-position-key) (values xas (cons attr yas) oas)]
[else (values xas yas (cons attr oas))])))
(define (make-cartesian-attr key attrs) (if (empty? attrs) empty (cons key (apply + (map cdr attrs)))))
(define-values (x-attr y-attr) (apply values (map make-cartesian-attr (list world:x-position-key world:y-position-key) (list x-attrs y-attrs))))
(apply hash (flatten (list* x-attr y-attr (reverse other-attrs-reversed)))))
;; pushes attributes down from parent quads to children,
;; resulting in a flat list of quads.
(provide flatten-quad)
(define (flatten-quad q)
; (quad? . -> . quads?)
(flatten
(let loop ([x q][parent #f])
(cond
[(quad? x)
(let ([x-with-parent-attrs (quad (quad-name x)
(flatten-attrs parent x) ; child positioned last so it overrides parent attributes
(quad-list x))])
(if (empty? (quad-list x))
x-with-parent-attrs ; no subelements, so stop here
(map (λ(xi) (loop xi x-with-parent-attrs)) (quad-list x))))] ; replace quad with its elements
[(string? x) (quad (quad-name parent) (quad-attrs parent) (list x))]))))
(require sugar/debug)
;; flatten quad as above,
;; then dissolve it into individual character quads while copying attributes
(define+provide (split-quad q)
;(quad? . -> . quads?)
(letrec ([do-explode (λ(x [parent #f])
(cond
[(quad? x)
(if (empty? (quad-list x))
x ; no subelements, so stop here
(map (λ(xi) (do-explode xi x)) (quad-list x)))] ; replace quad with its elements, exploded
;; todo: figure out why newlines foul up the input stream. Does it suffice to ignore them?
[else (map (λ(xc) (quad 'word (quad-attrs parent) (list xc))) (regexp-match* #px"[^\r\n]" x))]))])
(flatten (map do-explode (flatten-quad q)))))
;; merge chars into words (and boxes), leave the rest
;; if two quads are mergeable types, and have the same attributes,
;; they get merged.
;; input is often large, so macro allows us to avoid allocation
(provide join-quads)
(define-syntax-rule (join-quads qs-in)
;((quads?)(quads?) . ->* . quads?)
(let ([make-matcher (λ (base-q)
(λ(q)
(and (member (quad-name q) world:mergeable-quad-types)
(not (whitespace/nbsp? q))
;; if key doesn't exist, it is compared against the default value.
;; this way, a nonexistent value will test true against a default value.
(andmap (λ(key default) (equal? (quad-attr-ref base-q key default) (quad-attr-ref q key default)))
(list world:font-name-key
world:font-size-key
world:font-weight-key
world:font-style-key)
(list (world:font-name-default)
(world:font-size-default)
(world:font-weight-default)
(world:font-style-default))))))])
(let loop ([qs qs-in][acc null])
(if (null? qs)
(reverse acc)
(let* ([base-q (first qs)]
[mergeable-and-matches-base? (make-matcher base-q)]) ; make a new predicate function for this quad
(cond
[(mergeable-and-matches-base? base-q)
;; take as many quads that match, using the predicate function
(define-values (matching-qs other-qs) (splitf-at (cdr qs) mergeable-and-matches-base?))
(define new-word (word (quad-attrs base-q) (string-append* (append-map quad-list (cons base-q matching-qs)))))
(loop other-qs (cons new-word acc))]
;; otherwise move on to the next in line
[else (loop (cdr qs) (cons base-q acc))]))))))
;; the last char of a quad
(define+provide (quad-last-char q)
(quad? . -> . (or/c #f string?))
(define split-qs (split-quad q)) ; split makes it simple, but is it too expensive?
(if (or (empty? split-qs) (empty? (quad-list (last split-qs))))
#f
(car (quad-list (last split-qs)))))
;; the first char of a quad
(define+provide (quad-first-char q)
(quad? . -> . (or/c #f string?))
(define split-qs (split-quad q)) ; explosion makes it simple, but is it too expensive?
(if (or (empty? split-qs) (empty? (quad-list (first split-qs))))
#f
(car (quad-list (first split-qs)))))
;; propagate x and y adjustments throughout the tree,
;; using parent x and y to adjust children, and so on.
(define+provide (compute-absolute-positions i [parent-x 0][parent-y 0])
((quad?) (integer? integer?) . ->* . quad?)
(cond
[(quad? i)
(define adjusted-x (round-float (+ (quad-attr-ref i world:x-position-key 0) parent-x)))
(define adjusted-y (round-float (+ (quad-attr-ref i world:y-position-key 0) parent-y)))
(quad (quad-name i) (merge-attrs i (list world:x-position-key adjusted-x world:y-position-key adjusted-y)) (map (λ(ii) (compute-absolute-positions ii adjusted-x adjusted-y)) (quad-list i)))]
[else i]))
;; simple assert. should get moved to sugar/debug
(provide assert)
(define-syntax-rule (assert pred expr)
(let ([result expr])
(if (pred result)
result
(error 'assert-failure (format "\n~a\nevaluates to:\n~a\nwhich is not:\n~a" 'expr result 'pred)))))
;; peeks at arguments and times execution
(provide snoop)
(define-syntax (snoop stx)
(syntax-case stx ()
[(_ proc arg ... . rest)
(with-syntax ()
#'(begin
(displayln (format "Evaluating ~s" '(proc arg ... . rest)))
(let ([start (current-milliseconds)]
[result (proc arg ... . rest)]
[end (current-milliseconds)])
(displayln (format "Evaluation of ~s took ~a ms\nResult ~a" '(proc arg ... . rest) (- end start) result))
result)))]))
;; find total pages in doc by searching on page count key.
(define+provide (pages-in-doc doc)
(doc? . -> . integer?)
(add1 (apply max (map (curryr quad-attr-ref world:page-key 0) (quad-list doc)))))
;; todo: how to guarantee line has leading key?
(define+provide (compute-line-height line)
(line? . -> . line?)
(quad-attr-set line world:height-key (quad-attr-ref/parameter line world:leading-key)))
(define (fixed-height? q) (quad-has-attr? q world:height-key))
(define+provide (quad-height q)
(quad? . -> . number?)
(quad-attr-ref q world:height-key 0))
;; use heights to compute vertical positions
(define+provide (add-vert-positions starting-quad)
(quad? . -> . quad?)
(define-values (new-quads final-height)
(for/fold ([new-quads empty][height-so-far 0])([q (in-list (quad-list starting-quad))])
(values (cons (quad-attr-set q world:y-position-key height-so-far) new-quads)
(round-float (+ height-so-far (quad-height q))))))
(quad (quad-name starting-quad) (quad-attrs starting-quad) (reverse new-quads)))
;; recursively hyphenate strings in a quad
(define+provide (hyphenate-quad x)
(quad? . -> . quad?)
(cond
[(quad? x) (quad-map hyphenate-quad x)]
[(string? x) (hyphenate x
#:min-length 6
#:min-left-length 3
#:min-right-length 3)]
[else x]))
;; just because it comes up a lot
(provide split-last)
(define-syntax-rule (split-last xs)
(let-values ([(first-list last-list) (split-at-right xs 1)])
(values first-list (car last-list))))
;; like cons, but joins a list to an atom
(provide snoc)
(define-syntax-rule (snoc xs x)
(append xs (list x)))
;; folded flonum operators
;; (for use with multiple args, standard flonum ops have arity = 2)
(define-syntax (define-folded-op stx)
(syntax-case stx ()
[(_ op starting-val)
(with-syntax ([fold-op (format-id stx "fold-~a" #'op)]
[ops (format-id stx "~as" #'op)])
#'(begin
(provide fold-op ops)
(define-syntax-rule (ops x (... ...))
(fold-op (list x (... ...))))
(define-syntax-rule (fold-op xs)
(foldl op starting-val xs))))]))
(define-folded-op fl+ 0.0)
(define-folded-op fl- 0.0)
(define-folded-op fl* 1.0)
(define-folded-op fl/ 1.0)

@ -1,99 +0,0 @@
#lang racket/base
(require (for-syntax racket/base racket/syntax))
(provide (prefix-out world: (all-defined-out)))
(define-syntax-rule (define-parameter name val)
(define name (make-parameter val)))
(define-syntax (define-key-and-parameter stx)
(syntax-case stx ()
[(_ name keyname val)
(with-syntax ([name-key (format-id #'name "~a-key" #'name)]
[name-default (format-id #'name "~a-default" #'name)])
#'(begin
(define name-key keyname)
(define-parameter name-default val)))]))
(define-key-and-parameter measure 'measure 300)
(define-key-and-parameter font-size 'size 13)
(define-key-and-parameter font-name 'font "Triplicate T4")
(define-key-and-parameter font-weight 'weight 'normal)
(define-key-and-parameter font-style 'style 'normal)
(define-key-and-parameter font-color 'color "black")
(define-key-and-parameter font-background 'background "none")
(define-key-and-parameter column-count 'column-count 2)
(define-key-and-parameter column-gutter 'column-gutter 30)
(define max-quality 100)
(define adaptive-quality 50)
(define draft-quality 20)
(define-key-and-parameter quality 'quality max-quality)
(define-key-and-parameter horiz-alignment 'x-align 'left)
(define-key-and-parameter leading 'leading (floor (* (font-size-default) 1.4)))
(define-key-and-parameter paper-width 'paper-width (* 8.5 72))
(define-key-and-parameter paper-height 'paper-height (* 11 72))
(define line-looseness-key 'looseness)
(define width-key 'width)
(define horiz-alignment-last-line-key 'x-align-last-line)
(define word-break-key 'word-break)
(define no-break-key 'nb)
(define before-break-key 'bb)
(define ascent-key 'ascent)
(define height-key 'height)
(define unbreakable-key 'no-break)
(define line-index-key 'line-idx)
(define total-lines-key 'lines)
(define page-index-key 'page-idx)
(define column-index-key 'column-idx)
(define x-position-key 'x)
(define y-position-key 'y)
(define page-key 'page)
(define soft-hyphen #\u00AD)
(define hyphens-and-dashes (list "-" "" "" (format "~a" soft-hyphen)))
(define spaces '(" "))
(define empty-string '"")
(define mergeable-quad-types '(char run word))
(define-parameter default-word-break-list '(nb "" bb "-"))
(define-parameter optical-overhang 0.8)
(define line-looseness-tolerance 0.05) ; 0.04 seems to be the magic point that avoids a lot of hyphenation
(define hyphen-limit 1) ; does not work with first-fit wrapping
(define minimum-last-line-chars 5)
(define allow-hyphenated-last-word-in-paragraph #t)
(define allowed-overfull-ratio 1.015)
(define last-line-can-be-short #t)
(define use-optical-kerns? #t)
(define use-hyphenation? #t)
(define new-line-penalty 5000)
(define hyphen-penalty 5000)
(define hanging-chars '("." "-" "," "" "" "" "" "'" "\"" ")" "(" "[" "]" "{" "}" ":" ";"))
(define minimum-lines-per-column 4)
(define min-first-lines 2)
(define min-last-lines 2)
(define default-lines-per-column 36)
(define top-page-margin 100)
(define-parameter logging-level 'debug)

@ -1,569 +0,0 @@
#lang racket/base
(require (for-syntax racket/base racket/syntax))
(require sugar/define sugar/list sugar/debug racket/list racket/function math/flonum racket/vector math/statistics)
(require "ocm.rkt" "quads.rkt" "utils.rkt" "measure.rkt" "world.rkt" "logger.rkt" )
;; predicate for the soft hyphen
(define+provide (soft-hyphen? x)
#;(string? . -> . boolean?)
(equal? (format "~a" world:soft-hyphen) x))
;; visible characters that also mark possible breakpoints
(define+provide (visible-breakable? x)
#;(string? . -> . boolean?)
(and (member x world:hyphens-and-dashes) #t))
;; invisible characters that denote possible breakpoints
(define+provide (invisible-breakable? x)
#;(string? . -> . boolean?)
(and (member x (cons world:empty-string world:spaces)) #t))
;; union of visible & invisible
(define+provide (breakable? x)
#;(any/c . -> . boolean?)
(cond
[(string? x) (or (visible-breakable? x) (invisible-breakable? x))]
[(word? x) (breakable? (word-string x))]
[else #f]))
;; used by insert-spacers to determine which characters
;; can be surrounded by stretchy spacers
(define+provide (takes-justification-space? x)
#;(any/c . -> . boolean?)
(whitespace/nbsp? x))
;; test if a quad can be a word break:
;; either it's an explicit word break,
;; or it's breakable (and can be converted to a word break)
(define+provide (possible-word-break-quad? q)
#;(quad? . -> . boolean?)
(or (word-break? q) (breakable? q)))
;; convert a possible word break into an actual one
(define+provide (convert-to-word-break q)
#;(possible-word-break-quad? . -> . word-break?)
(cond
[(word-break? q) q]
[(word? q)
(define str (word-string q)) ; str will be one character long, because we've exploded our input
(apply word-break
(merge-attrs q ; take q's attributes for formatting purposes
(cond
;; a space is ordinarily visible, but disappears at the end of a line
[(equal? str " ") (list world:no-break-key " " world:before-break-key "")]
;; soft hyphen is ordinarily invisible, but appears at the end of a line
[(soft-hyphen? str) (list world:no-break-key "" world:before-break-key "-")]
;; a visible breakable character is always visible
[(visible-breakable? str) (list world:no-break-key str world:before-break-key str)]
[else (world:default-word-break-list)])) (quad-list q))]))
(define (make-unbreakable q)
(quad-attr-set q world:unbreakable-key #t))
;; take list of atomic quads and gather them into pieces
;; a piece is an indivisible chunk of a line.
;; meaning, a line can wrap at a piece boundary, but not elsewhere.
;; hyphenation produces more, smaller pieces, which means more linebreak opportunities
;; but this also makes wrapping slower.
(define+provide (make-pieces qs)
#;(quads? . -> . pieces?)
(define-values (breakable-items items-to-make-unbreakable) (split-at-right qs (min world:minimum-last-line-chars (length qs))))
(define unbreak-qs (append breakable-items (map make-unbreakable items-to-make-unbreakable)))
(define lists-of-quads (slicef-after unbreak-qs (λ(q) (and (possible-word-break-quad? q) (not (quad-attr-ref q world:unbreakable-key #f))))))
(define-values (first-lists-of-quads last-list-of-quads) (split-last lists-of-quads))
(define (make-first-pieces qs)
(let-values ([(first-qs last-q) (split-last qs)])
(apply piece (list world:word-break-key (convert-to-word-break last-q)) first-qs)))
(append (map make-first-pieces first-lists-of-quads)
(list (apply piece #f last-list-of-quads))))
;; extract font attributes from quad, or get default values
(provide font-attributes-with-defaults)
(define-syntax-rule (font-attributes-with-defaults q)
(list
(let ([size (quad-attr-ref/parameter q world:font-size-key)])
(if (exact-integer? size) (fl size) size))
(quad-attr-ref/parameter q world:font-name-key)
(quad-attr-ref/parameter q world:font-weight-key)
(quad-attr-ref/parameter q world:font-style-key)))
;; get the width of a quad.
;; Try the attr first, and if it's not available, compute the width.
;; comes in fast or slow versions.
;; not designed to update the source quad.
(define+provide (quad-width q)
#;(quad? . -> . flonum?)
(cond
[(quad-has-attr? q world:width-key) (fl (quad-attr-ref q world:width-key))]
[(ormap (λ(pred) (pred q)) (list char? run? word? word-break?))
(apply measure-text (word-string q)
(font-attributes-with-defaults q))]
[(line? q) (fold-fl+ (map quad-width (quad-list q)))]
[else 0.0]))
;; get the ascent (distance from top of text to baseline)
;; used by renderer to align text runs baseline-to-baseline.
;; consult the attrs, and if not available, compute it.
;; not designed to update the source quad.
(define+provide (ascent q)
#;(quad? . -> . flonum?)
(or (quad-attr-ref q world:ascent-key #f)
(cond
[(ormap (λ(pred) (pred q)) (list char? run? word? word-break?))
(apply measure-ascent (word-string q) (font-attributes-with-defaults q))]
[else 0.0])))
;; convert a piece into its final form, which depends on location.
;; if a piece appears at the end of a line, it is rendered in "before break" mode.
;; if a piece appears elsewhere in a line, it is rendered in "no break" mode.
;; this allows the appearance of a piece to change depending on whether it's at the end.
;; and thus give correct behavior to trailing word spaces, soft hyphens, etc.
(define+provide (render-piece p [before-break? #f])
#;((piece?) (boolean?) . ->* . piece?)
;; a piece doesn't necessarily have a word-break item in it.
;; only needs it if the appearance of the piece changes based on location.
;; so words are likely to have a word-break item; boxes not.
;; the word break item contains the different characters needed to finish the piece.
(define the-word-break (quad-attr-ref p world:word-break-key #f))
(let ([p (quad-attr-remove p world:word-break-key)]) ; so it doesn't propagate into subquads
(if the-word-break
(quad (quad-name p) (quad-attrs p)
(append (quad-list p) (let ([rendered-wb ((if before-break?
word-break->before-break
word-break->no-break) the-word-break)])
(if (> (string-length (word-string rendered-wb)) 0) ; if rendered-wb is "", don't append it
(list rendered-wb)
empty))))
p)))
;; shorthand
(define+provide (render-piece-before-break p)
(render-piece p #t))
;; helper macro to convert quad into word-break.
;; look up the break character and convert the quad based on what is found.
(define-syntax-rule (render-word-break wb key)
(let ([break-char (quad-attr-ref wb key)])
(quad (if (whitespace? break-char) 'word-break 'word)
(hash-remove (hash-remove (quad-attrs wb) world:no-break-key) world:before-break-key) (list (quad-attr-ref wb key)))))
;; uses macro above in no-break mode.
(define (word-break->no-break wb)
(render-word-break wb world:no-break-key))
;; uses macro above in before-break mode.
(define (word-break->before-break wb)
(render-word-break wb world:before-break-key))
;; is this the last line? compare current line-idx to total lines
(define+provide (last-line? line)
#;(line? . -> . boolean?)
(define line-idx (quad-attr-ref line world:line-index-key #f))
(define lines (quad-attr-ref line world:total-lines-key #f))
(and line-idx lines (= (add1 line-idx) lines)))
;; optical kerns are automatically inserted at the beginning and end of a line
;; (by the pieces->line function)
;; but may also be found elsewhere, imperatively (e.g., before an indent)
;; they allow certain characters to hang over the line margin.
;; optical kerns aren't considered when the line is being composed,
;; rather they are an adjustment added to a composed line.
;; the optical kern doesn't have left- or right-handed versions.
;; it just looks at quads on both sides and kerns them if appropriate.
;; in practice, only one will likely be used.
(define+provide (render-optical-kerns exploded-line-quads)
#;(quads? . -> . quads?)
(define (overhang-width q)
(if (and (word? q) (member (word-string q) world:hanging-chars))
(fl*s -1.0 (world:optical-overhang) (apply measure-text (word-string q) (font-attributes-with-defaults q)))
0.0))
(cond
[(not (empty? exploded-line-quads))
;; after exploding, each quad will have a string with one character.
(for/list ([(q-left q q-right) (apply in-parallel (shifts exploded-line-quads '(1 0 -1)))])
(if (optical-kern? q)
(quad-attr-set q world:width-key (fl+ (overhang-width q-left) (overhang-width q-right)))
q))]
[else exploded-line-quads]))
(define-syntax (when/splice stx)
(syntax-case stx ()
[(_ test body)
#'(if test (list body) '())]))
;; ultimately every line is filled to fit the whole measure.
;; spacers are used to soak up extra space left over in a line.
;; depending on where the spacers are inserted, different formatting effects are achieved.
;; e.g., left / right / centered / justified.
(define+provide (insert-spacers-in-line line [alignment-override #f])
#;((line?) ((or/c #f symbol?)) . ->* . line?)
;; important principle: avoid peeking into quad-list to get attributes.
;; because non-attributed quads may be added.
;; here, we know that common attributes are hoisted into the line.
;; so rely on line attributes to get horiz alignment.
(define key-to-use (if (and (last-line? line) (quad-has-attr? line world:horiz-alignment-last-line-key))
world:horiz-alignment-last-line-key
world:horiz-alignment-key))
(define horiz-alignment (or alignment-override (quad-attr-ref line key-to-use (world:horiz-alignment-default))))
(define default-spacer (spacer))
(define-values (before middle after) (case horiz-alignment
[(left) (values #f #f default-spacer)]
[(right) (values default-spacer #f #f)]
[(center) (values default-spacer #f default-spacer)]
[(justified justify) (values #f default-spacer #f)]
[else (values #f #f #f)]))
(define (copy-with-attrs q attr-source)
(define keys-to-ignore '(width)) ; width will be determined during fill routine
(define filtered-hash (and (quad-attrs attr-source)
(foldl (λ(k ht) (hash-remove ht k)) (quad-attrs attr-source) keys-to-ignore)))
(quad (quad-name q) (merge-attrs filtered-hash q) (quad-list q)))
(define result
(quad (quad-name line) (quad-attrs line) (flatten (let ([qs (quad-list line)])
`(,@(when/splice before (copy-with-attrs before (first qs)))
,@(map (λ(q) (if (and middle (takes-justification-space? q))
(let ([interleaver (copy-with-attrs middle q)])
(list interleaver q interleaver))
q)) qs)
,@(when/splice after (copy-with-attrs after (last qs))))))))
result)
;; installs the width in the quad.
;; this becomes the value reported by quad-width.
(define (embed-width q w)
(quad-attr-set q world:width-key w))
;; installs the ascent in the quad.
(define (record-ascent q)
(quad-attr-set q world:ascent-key (ascent q)))
;; helper function: doesn't need contract because it's already covered by the callers
(define (render-pieces ps)
(define-values (initial-ps last-p) (split-last ps))
(snoc (map render-piece initial-ps) (render-piece-before-break last-p)))
(define (calc-looseness total-width measure)
(round-float (fl/ (fl- measure total-width) measure)))
;; compose pieces into a finished line.
;; take the contents of the rendered pieces and merge them.
;; compute looseness for line as a whole.
;; also add ascent to each component quad, which can be different depending on font & size.
(define+provide (pieces->line ps measure-quad-proc)
#;(pieces? procedure? . -> . line?)
;; handle optical kerns here to avoid resplitting and rejoining later.
(define rendered-pieces (render-pieces ps))
(define split-pieces (map quad-list rendered-pieces))
(define line-quads (append* split-pieces))
(define line-quads-maybe-with-opticals
(if world:use-optical-kerns?
(render-optical-kerns
(let ([my-ok (list (optical-kern (quad-attrs (car line-quads))))]) ; take attrs from line, incl measure
(append my-ok line-quads my-ok)))
line-quads))
(define merged-quads (join-quads line-quads-maybe-with-opticals))
(define merged-quad-widths (map measure-quad-proc merged-quads)) ; 10% of function time
(log-quad-debug "making pieces into line = ~v" (apply string-append (map quad->string merged-quads)))
;; if measure key isn't present, allow an error, because that's weird
(when (not (quad-has-attr? (first line-quads) world:measure-key))
(error 'pieces->line "quad has no measure key: ~a" (first line-quads)))
(define measure (fl (quad-attr-ref (first merged-quads) world:measure-key)))
(define looseness (calc-looseness (fold-fl+ merged-quad-widths) measure))
;; quads->line function hoists common attributes into the line
(let* ([new-line-quads (map embed-width merged-quads merged-quad-widths)] ; 15% of time
[new-line-quads (map record-ascent new-line-quads)] ; 35% of time
[new-line (quads->line new-line-quads)]
[new-line (quad-attr-set new-line world:line-looseness-key looseness)])
new-line))
;; a faster line-measuring function used by the wrapping function to test lines.
(define+provide (measure-potential-line ps)
;(pieces? . -> . flonum?)
(for*/sum ([rendered-piece (in-list (render-pieces ps))]
[piece-quad (in-list (quad-list rendered-piece))])
(quad-width piece-quad)))
(define (vector-break-at vec bps)
(define-values (vecs _) ;; loop backward
(for/fold ([vecs empty][end (vector-length vec)])([start (in-list (reverse (cons 0 bps)))])
(if (= start end)
(values vecs start)
(values (cons (vector-copy vec start end) vecs) start))))
vecs)
(define-syntax-rule (report-time0 name expr)
(let ([op (open-output-string)])
(parameterize ([current-output-port op])
(define result (time expr))
(report ((dynamic-require string-trim 'racket/string) (get-output-string op)) name)
(values result))))
(define-syntax-rule (report-time name expr)
expr)
;; makes a wrap function by combining component functions.
(define+provide (make-wrap-proc
#:make-pieces-proc make-pieces-proc
#:measure-quad-proc measure-quad-proc
#:compose-line-proc compose-line-proc
#:find-breakpoints-proc find-breakpoints-proc)
(λ(qs [measure #f])
(let* ([measure (fl+ (fl (or measure (quad-attr-ref/parameter (car qs) world:measure-key))) 0.0)]
[qs (if (quad-has-attr? (car qs) world:measure-key)
qs
(map (curryr quad-attr-set world:measure-key measure) qs))])
(log-quad-debug "wrapping on measure = ~a" measure)
(define pieces (make-pieces-proc qs)) ; 5%
(define bps (report-time 'find-bps (find-breakpoints-proc (list->vector pieces) measure))) ; 50%
(define broken-pieces (break-at pieces bps)) ; 5%
; (report (add1 (length bps)) 'lines-in-paragraph)
(report-time 'compose-lines (map (λ(bp) (compose-line-proc bp measure-quad-proc)) broken-pieces))))) ; 50%
(define width? flonum?)
(define measure? flonum?)
(define (breakpoints? x) (and (list? x) (andmap integer? x)))
(define (install-measurement-keys p)
(define basic-width (round-float (apply + (map quad-width (quad-list p)))))
(define p-word-break (quad-attr-ref p world:word-break-key #f))
(define before-break-width (fl+ basic-width (if p-word-break
(quad-width (word (quad-attrs p-word-break) (quad-attr-ref p-word-break world:before-break-key)))
0.0)))
(define no-break-width (fl+ basic-width (if p-word-break
(quad-width (word (quad-attrs p-word-break) (quad-attr-ref p-word-break world:no-break-key)))
0.0)))
(quad-attr-set* p 'bb-width before-break-width 'nb-width no-break-width))
(define (make-piece-vectors pieces)
(define pieces-measured
(report-time 'make-wrap-vector (for/list ([p (in-vector pieces)])
(define wb (quad-attr-ref p world:word-break-key #f))
(vector
(fold-fl+ (for/list ([q (in-list (quad-list p))])
(define str (quad->string q))
(if (equal? str "")
(fl (quad-attr-ref q world:width-key 0.0))
(apply measure-text (quad->string q) (font-attributes-with-defaults q)))))
(if wb (apply measure-text (quad-attr-ref wb world:no-break-key) (font-attributes-with-defaults wb)) 0.0)
(if wb (apply measure-text (quad-attr-ref wb world:before-break-key) (font-attributes-with-defaults wb)) 0.0)))))
(values
(for/flvector ([p (in-list pieces-measured)]) (fl+ (vector-ref p 0) (vector-ref p 1))) ; first = word length, second = nb length
(for/flvector ([p (in-list pieces-measured)]) (fl+ (vector-ref p 0) (vector-ref p 2))))) ; first = word length, third = bb length
(define-syntax-rule (make-trial-line pieces-rendered-widths pieces-rendered-before-break-widths i j)
(let ([flvec (flvector-copy pieces-rendered-widths i j)])
(flvector-set! flvec (sub1 (flvector-length flvec)) (flvector-ref pieces-rendered-before-break-widths (sub1 j)))
flvec))
(define-syntax-rule (get-line-width line)
(round-float (fold-fl+ (flvector->list line))))
;; top-level adaptive wrap proc.
;; first-fit and best-fit are variants.
(define+provide (adaptive-fit-proc pieces measure [use-first? #t] [use-best? #t])
;((pieces? . -> . width?) . -> . (pieces? measure? . -> . breakpoints?))
;; this is the winning performance strategy: extract the numbers first, then just wrap on those.
;; todo: how to avoid re-measuring pieces later?
;; todo: how to retain information about words per line and hyphen at end?
(define-values (pieces-rendered-widths pieces-rendered-before-break-widths)
(make-piece-vectors pieces))
(define pieces-with-word-space (vector-map (λ(piece) (and (quad-has-attr? piece world:word-break-key) (equal? (quad-attr-ref (quad-attr-ref piece world:word-break-key) 'nb) " "))) pieces))
(define (make-first-fit-bps-and-widths)
(define-values (folded-bps folded-widths)
(for/fold ([bps '(0)][line-widths empty])([j-1 (in-range (vector-length pieces))])
(define line-width (get-line-width (make-trial-line pieces-rendered-widths
pieces-rendered-before-break-widths
(car bps) (add1 j-1))))
(if (fl> line-width (fl* world:allowed-overfull-ratio measure))
(values (cons j-1 bps) (cons line-width line-widths))
(values bps line-widths))))
(values (cdr (reverse folded-bps)) (reverse folded-widths)))
(define (fu-formula)
(define line-count (length trial-line-widths))
(cond
[(<= line-count 2) 1.0] ; signals that first-fit is always OK with 1 or 2 lines
[else ; only measure middle lines. we know bps has at least 2 bps
(define looseness-stddev (stddev (map (curryr calc-looseness measure) (drop-right (drop trial-line-widths 1) 1))))
(define piece-count (flvector-length pieces-rendered-widths))
(define pieces-per-line (fl/ (fl piece-count) (sub1 (fl line-count)))) ; todo: more accurate to count only pieces in middle
(fl+s 2.2 (fllog (flabs looseness-stddev)) (fl* 0.09 pieces-per-line))])) ; the FU FORMULA
;; only buy first-fit-bps if use-first? is true.
(define-values (first-fit-bps trial-line-widths) (if use-first? (make-first-fit-bps-and-widths) (values (void) (void))))
(cond
;; possible outcomes at this branch:
;; adaptive wrap: use-first and use-best are true, so first-fit-bps will exist, and fu-formula will be used.
;; first-fit wrap: use-first is true but not use-best. So first-fit-bps will be returned regardless.
;; best-fit wrap: use-first is false but use-best is true. So first-fit-bps will be skipped, and move on to best-fit.
[(and use-first? (if use-best? (fl> (fu-formula) 0.0) #t))
(log-quad-debug "first-fit breakpoints = ~a" first-fit-bps)
first-fit-bps]
[else
(define $penalty vector) ; don't use struct for penalty, because of read/write overhead
(define ($penalty-width x) (vector-ref x 1))
(define ($penalty-hyphens x) (vector-ref x 0))
(define ($penalty->value v) ($penalty-width v))
(define initial-value ($penalty 0 0.0))
(log-quad-debug "~a pieces to wrap = ~v" (vector-length pieces) (vector-map quad->string pieces))
(define (penalty i j)
(cond
[(or (>= i j) ; implies negative or zero length line
(> j (vector-length pieces))) ; exceeds available pieces
($penalty 0 (fl* -1.0 (fl i)))] ; ocm out of bounds signal
[else
(define penalty-up-to-i (ocm-min-value ocm i))
(define last-piece-to-test (vector-ref pieces (sub1 j)))
(define new-hyphen?
(and (quad-has-attr? last-piece-to-test world:word-break-key)
(equal? (quad-attr-ref (quad-attr-ref last-piece-to-test world:word-break-key) world:before-break-key) "-")))
(define cumulative-hyphens (if (not new-hyphen?)
0
(add1 ($penalty-hyphens penalty-up-to-i))))
($penalty
cumulative-hyphens
(round-float
(fl+s
(if (> cumulative-hyphens world:hyphen-limit)
(fl world:hyphen-penalty)
0.0)
(fl world:new-line-penalty)
($penalty->value penalty-up-to-i)
(let ([line-width (get-line-width (make-trial-line pieces-rendered-widths pieces-rendered-before-break-widths i j))])
(cond
;; overfull line: huge penalty prevents break; multiplier is essential for monotonicity.
;; multiply by -1 because line-width is longer than measure, thus diff is negative
[(fl> line-width (fl* world:allowed-overfull-ratio measure))
(fl* (fl- line-width measure) (flexpt 10.0 7.0))]
;; standard penalty, optionally also applied to last line (by changing operator)
[((if world:last-line-can-be-short < <=) j (vector-length pieces))
(define words (fl (vector-count identity (vector-copy pieces-with-word-space i (sub1 j)))))
(fl/ (flexpt (fl- measure line-width) 2.0) (flmax 1.0 words))]
;; only option left is (= j (vector-length pieces)), meaning we're on the last line.
;; 0 penalty means any length is ok.
;[(< (length pieces-to-test) (world:minimum-last-line-pieces)) 50000]
[else 0.0])))))]))
(define ocm (make-ocm penalty initial-value $penalty->value))
;; starting from last position, ask ocm for position of row minimum (= new-pos)
;; collect this value, and use it as the input next time
;; until you reach first position.
(define first-position 0)
(define last-position (vector-length pieces))
(define result (let loop ([pos last-position][acc null])
(let ([next-pos (ocm-min-index ocm pos)]) ; first look ahead ...
(if (= next-pos first-position) ; therefore we're done
acc
(loop next-pos (cons next-pos acc))))))
(log-quad-debug "best-fit breakpoints = ~a" result)
result]))
;; wrap proc based on greedy proc
(define+provide wrap-first (make-wrap-proc
#:make-pieces-proc make-pieces
#:measure-quad-proc quad-width
#:compose-line-proc pieces->line
#:find-breakpoints-proc (curryr adaptive-fit-proc #t #f)))
;; wrap proc based on penalty function
(define+provide wrap-best (make-wrap-proc
#:make-pieces-proc make-pieces
#:measure-quad-proc quad-width
#:compose-line-proc pieces->line
#:find-breakpoints-proc (curryr adaptive-fit-proc #f #t)))
(define+provide wrap-adaptive (make-wrap-proc
#:make-pieces-proc make-pieces
#:measure-quad-proc quad-width
#:compose-line-proc pieces->line
#:find-breakpoints-proc adaptive-fit-proc))
(define (fixed-width? q) (quad-has-attr? q world:width-key))
;; build quad out to a given width by distributing excess into spacers
;; todo: adjust this to work recursively, so that fill operation cascades down
(define+provide (fill starting-quad [target-width? #f])
#;((quad?) ((or/c #f flonum?)) . ->* . quad?)
(define target-width (fl (or target-width? (quad-attr-ref starting-quad world:measure-key))))
(define subquads (quad-list starting-quad))
(define-values (flexible-subquads fixed-subquads) (partition spacer? subquads)) ; only puts fill into spacers.
(define width-used (fold-fl+ (map quad-width fixed-subquads)))
(define width-remaining (round-float (fl- target-width width-used)))
(cond
;; check for zero condition because we want to divide by this number
;; if there's no spacers, put one in
;; todo: go in two rounds, once for word spacers, and once for line spacers?
;; or separate the line alignment & word-spacing properties?
[(fl= 0.0 (fl (length flexible-subquads))) (fill (insert-spacers-in-line starting-quad (world:horiz-alignment-default)) target-width)]
[else (define width-per-flexible-quad (round-float (fl/ width-remaining (fl (length flexible-subquads)))))
(define new-quad-list (map (λ(q) (if (spacer? q)
(quad-attr-set q world:width-key width-per-flexible-quad)
q)) subquads))
(quad (quad-name starting-quad) (quad-attrs (quad-attr-set starting-quad world:width-key target-width)) new-quad-list)]))
;; add x positions to a list of fixed-width quads
;; todo: adjust this to work recursively, so that positioning operation cascades down
(define+provide (add-horiz-positions starting-quad)
#;((and/c quad? fixed-width?) . -> . quad?)
(define-values (new-quads final-width)
(for/fold ([new-quads empty][width-so-far 0.0])([q (in-list (quad-list starting-quad))])
(values (cons (quad-attr-set q world:x-position-key width-so-far) new-quads) (round-float (fl+ (quad-width q) width-so-far)))))
(quad (quad-name starting-quad) (quad-attrs starting-quad) (reverse new-quads)))
(module+ main
(define eqs (split-quad (block '(x-align center font "Equity Text B" size 10) "Foo-d" (word '(size 13) "og ") "and " (box) " Zu" (word-break '(nb "c" bb "k-")) "kerman's. Instead of a circle, the result is a picture of the code that, if it were used as an expression, would produce a circle. In other words, code is not a function, but instead a new syntactic form for creating pictures; the bit between the opening parenthesis with code is not an expression, but instead manipulated by the code syntactic form. This helps explain what we meant in the previous section when we said that racket provides require and the function-calling syntax. Libraries are not restricted to exporting values, such as functions; they can also define new syntactic forms. In this sense, Racket isnt exactly a language at all; its more of an idea for how to structure a language so that you can extend it or create entirely " (word '(font "Courier" size 5) "lang."))))
(define megs (split-quad (block '(size 10 font "Courier") "Meg is an ally.")))
(activate-logger quad-logger)
(define measure 40.0)
(map quad->string (wrap-first megs measure))
(map quad->string (wrap-best megs measure))
(set! measure 200.0)
(time (map quad->string (wrap-first eqs measure)))
(time (map quad->string (wrap-best eqs measure)))
#|
(define trials 1)
(time-repeat trials (let () (wrap-first megs 36) (void)))
(time-repeat trials (let ([measure 36]) (wrap-best megs measure) (void)))
(time-repeat trials (let () (wrap-first eqs 54) (void)))
(time-repeat trials (let ([measure 54]) (wrap-best eqs measure) (void)))
(time-repeat trials (let ([measure 54]) (wrap-adaptive eqs measure) (void)))
|#
)

@ -1,5 +0,0 @@
#lang racket/base
(provide (all-defined-out))
(define current-default-attrs (make-parameter (make-hasheq)))
(define current-line-width (make-parameter 1))

@ -1,4 +0,0 @@
#lang quad/text pdf 360 300
@quad[(make-attrs #:font "Charter.ttf")]{Not that you need to be a programmer to start using Pollen. On the contrary, the Pollen language is markup-based, so you can write & edit text naturally. But when you want to automate repetitive tasks, add cross-references, or pull in data from other sources, you can access a full programming language from within the text.
Not that you need to be a programmer to start using Pollen. On the contrary, the Pollen language is markup-based, so you can write & edit text naturally. But when you want to automate repetitive tasks, add cross-references, or pull in data from other sources, you can access a full programming language from within the text.}

@ -1,127 +0,0 @@
#lang racket/base
(provide (all-defined-out))
(require racket/string racket/list (for-syntax racket/base racket/syntax))
(struct $quad (attrs dim val) #:transparent #:mutable)
(struct $black $quad () #:transparent)
(struct $space $quad () #:transparent)
(struct $hyphen $black () #:transparent) ; hyphen should be treated as black in measure & render ops
(struct $shy $quad () #:transparent)
(struct $shim $quad () #:transparent)
(struct $eof $quad () #:transparent)
(define (quad-printable? x) (or ($black? x) ($space? x) ($hyphen? x)))
(define quad? $quad?)
(define quad-attrs $quad-attrs)
(define quad-val $quad-val)
(define (quad-attrs? x) (list? x))
#|
Attrs needed to specify rendered appearance:
(font) family
(font) style
(font) size
color
background
position
measure (line width)
|#
(define default-attrs (hasheq 'size 10 'font "sc.otf")) ; Source Code Pro, 12 pt, chars are 6pt wide
(define (munge-whitespace str)
;; reduce multiple whitespace to single
;; trim remaining (? maybe not)
(regexp-replace* #px"\\s+" str " "))
(define (merge-strings xs)
;; merge consecutive strings
;; todo: only trim remove space between string and a hard break.
;; space between a string and a subquad is ok
(let loop ([xs xs])
(cond
[(empty? xs) empty]
[else
(define-values (strs rest) (splitf-at xs string?))
(define-values (nonstrs restrest) (splitf-at rest (λ(x) (not (string? x)))))
(append (if (empty? strs)
empty
(list (munge-whitespace (string-append* strs)))) nonstrs (loop restrest))])))
(struct $attrs (size font) #:transparent)
(define (make-attrs #:size [size #f]
#:font [font #f])
(hasheq 'size size 'font font))
(define (make-empty-attrs) (make-attrs))
(define (quad attr . xs)
;; squeeze excess whitespace out of quad args
;; todo: find way to do this with less allocation
($quad (or attr (make-attrs)) 0 (merge-strings xs)))
(define (quad-dim q)
($quad-dim q))
(define (quad-font q)
(hash-ref (quad-attrs q) 'font))
(define (quad-font-size q)
(hash-ref (quad-attrs q) 'size))
(define (quad-dim-set! q val)
(set-$quad-dim! q val))
(define (override-with dest source)
;; replace missing values in dest with values from source
(for/hasheq ([k (in-hash-keys source)])
(values k (or (hash-ref dest k) (hash-ref source k)))))
(require (for-syntax sugar/debug))
(define-syntax-rule (define-break name)
(define (name) ($shim (make-attrs) 'name #f)))
(define-break page-break)
(define-break column-break)
(define-break block-break)
(define-break line-break)
(define-syntax (define-case-macro stx)
(syntax-case stx ()
[(_ ID PRED)
#'(define-syntax (ID stx)
(syntax-case stx ()
[(_ test-val
[(match-val0 . match-vals) . result] (... ...)
[else . else-result])
#'(cond
[(PRED test-val '(match-val0 . match-vals)) . result] (... ...)
[else . else-result])]
[(_ test-val
match-clause (... ...))
#'(ID test-val
match-clause (... ...)
[else (error 'ID "no match")])]))]))
;; like case but strictly uses `eq?` comparison (as opposed to `equal?`)
(define-case-macro caseq memq)
;; `eqv?` is OK for chars (same as `char=?`)
(define-case-macro casev memv)
(module+ test
(require rackunit)
(define q (quad #f "bar"))
(check-true (quad? q))
(check-false (quad? 42))
(check-equal? (quad-attrs q) (make-attrs))
(check-equal? (quad-val q) '("bar"))
#;(check-equal? (merge-strings '(50 " foo " " bar " 42 " zam")) '(50 "foo bar" 42 "zam")))

@ -1,101 +0,0 @@
#lang quad/dev
(require racket/class racket/contract racket/string sugar/debug sugar/cache racket/list racket/file racket/draw data/gvector)
(provide (all-defined-out))
(define (world:paper-width-default) 612)
(define (world:paper-height-default) 792)
(define renderable-quads '(word box))
(define (render-pdf [qs #f] [path-string "test.pdf"])
(send* (current-ps-setup) (set-margin 0 0) (set-scaling 1.0 1.0))
(define dc-output-port (open-output-bytes))
(define dc (new pdf-dc% [interactive #f][use-paper-bbox #f][as-eps #f]
[output dc-output-port]
[width (world:paper-width-default)][height (world:paper-height-default)]))
(send dc start-doc "boing")
(send dc set-pen "black" 1 'solid)
(send dc set-brush "black" 'transparent) ; no fill by default
#;(for ([q (in-vector qs)] #:when (member (quad-name q) renderable-quads))
(define p (quad-attr-ref q world:page-key))
(gvector-set! page-quad-vector p (cons q (gvector-ref page-quad-vector p null))))
#;(for/list ([pq (in-gvector page-quad-vector)])
(send dc start-page)
(map/send render-element (filter-not whitespace/nbsp? elements))
(send dc end-page))
(define (print-status)
(send dc draw-text (format "quad pdf test @ ~a" (current-milliseconds)) 0 0))
(send dc set-font (make-font #:face "Source Code Pro" #:size 10))
(send dc start-page)
(print-status)
(define default-x 40)
(define default-y 40)
(when qs
(for/fold ([page-pos 0]
[x-pos default-x]
[y-pos default-y])
([q (in-vector qs)])
(let ([font-attr (hash-ref (quad-attrs q) 'font #f)])
(when font-attr
(send dc set-font (make-font #:face (string-trim font-attr ".ttf") #:size 10))))
(cond
[(eq? (quad-dim q) 'page-break)
(send dc end-page)
(send dc start-page)
(print-status)
(values page-pos default-x default-y)]
[(eq? (quad-dim q) 'line-break)
(values page-pos default-x (+ y-pos 12))]
[(eq? (quad-dim q) 'column-break)
(values page-pos x-pos y-pos)] ; ignore for now
[(quad-printable? q)
(send dc draw-text (format "~a" (quad-val q)) x-pos y-pos)
(values page-pos (+ x-pos (quad-dim q)) y-pos)]
[else (values page-pos x-pos y-pos)])))
(send dc end-page)
(send dc end-doc)
(define result-bytes (get-output-bytes dc-output-port))
(display-to-file result-bytes path-string #:exists 'replace #:mode 'binary))
#;(define (render-element q)
(cond
[(word? q) (render-word q)]
[else q]))
(define/caching (make-font/caching font size style weight)
(make-font #:face font #:size size #:style style #:weight weight))
#;(define (render-word w)
(define word-font (quad-attr-ref/parameter w world:font-name-key))
(define word-size (quad-attr-ref/parameter w world:font-size-key))
(define word-style (quad-attr-ref/parameter w world:font-style-key))
(define word-weight (quad-attr-ref/parameter w world:font-weight-key))
(define word-color (quad-attr-ref/parameter w world:font-color-key))
(define word-background (quad-attr-ref/parameter w world:font-background-key))
(send dc set-font (make-font/caching word-font word-size word-style word-weight))
(send dc set-text-foreground (send the-color-database find-color word-color))
(define background-color (send the-color-database find-color word-background))
(if background-color ; all invalid color-string values will return #f
(send* dc (set-text-mode 'solid) (set-text-background background-color))
(send dc set-text-mode 'transparent))
(define word-text (quad-car w))
(send dc draw-text word-text (quad-attr-ref w world:x-position-key)
;; we want to align by baseline rather than top of box
;; thus, subtract ascent from y to put baseline at the y coordinate
(- (quad-attr-ref w world:y-position-key) (quad-attr-ref w world:ascent-key 0)) #t))
(module+ test
(render-pdf))

@ -1,30 +0,0 @@
#lang quad/dev
(require racket/format)
(provide (all-defined-out))
(define (debug-render qs)
(define line-counter #f)
(define (line-counter-increment!) (set! line-counter (add1 line-counter)))
(define (line-counter-reset!) (set! line-counter 1))
(line-counter-reset!)
(printf " ")
(for ([i (in-range 1 71)])
(printf (cond
[(zero? (modulo i 10)) "|"]
[(zero? (modulo i 5)) "'"]
[else "·"])))
(define (print-line-counter)
(printf "\n~a "(~r line-counter #:min-width 2 #:pad-string " " #:base 10)))
(print-line-counter)
(for ([q (in-vector qs)])
(define qd (quad-dim q))
(cond
[(symbol? qd) ; symbol indicates a break
(caseq qd
[(line-break) (line-counter-increment!)]
[(column-break) (line-counter-reset!) (printf "\n--col--")]
[(page-break) (printf "\n\n==page==\n")])
(print-line-counter)]
[(quad-printable? q) (printf "~a" (quad-val q))]
[else (void)]))
(printf "\n\n"))

Binary file not shown.

@ -1,35 +0,0 @@
#lang racket/base
#|
Same semantics as `#lang quad`,
but substitutes a Scribble-style text-based reader
|#
(module reader syntax/module-reader
quad/main
#:read quad-read
#:read-syntax quad-read-syntax
#:whole-body-readers? #t ;; need this to make at-reader work
(require scribble/reader racket/list)
(require sugar/debug)
(define (quad-read p)
(syntax->datum (quad-read-syntax (object-name p) p)))
(define quad-command-char #\@)
(define (quad-read-syntax path-string p)
(define quad-at-reader (make-at-reader
#:command-char quad-command-char
#:syntax? #t
#:inside? #t))
(define source-stx (quad-at-reader path-string p))
(define source-stx-list (syntax->list source-stx))
(define config-line (car source-stx-list))
;; we dump all whitespace lines in plain-text mode, as they have no semantic purpose
;; the at-reader will kindly separate these all-whitespace lines into their own list elements
(define source-stx-no-interline-whitespace
(filter-not (λ(stx)
(define datum (syntax->datum stx))
(and (string? datum) (regexp-match #px"^\\s+$" datum))) (cdr source-stx-list)))
(datum->syntax source-stx (cons config-line source-stx-no-interline-whitespace) source-stx)))

@ -1,77 +0,0 @@
#lang quad/dev
(provide (all-defined-out))
(require "measure.rkt")
;; track this k outside of for/fold loop to keep it independent.
;; otherwise, every time k is invoked, the loop k will also change.
;; (mutated data is not reset by a continuation, but loop vars are)
(define last-breakpoint-k raise-overflow-error)
(define (set-breakpoint-k-here!)
(let/cc k (set! last-breakpoint-k k) #f))
(define (already-breakpoint-type? q type)
(eq? (quad-dim q) type))
(define char-width 6)
(define line-width (* 60 char-width)) ; 50 chars, each 6 pts wide
(define line-height 12)
(define col-height (* 6 line-height)) ; 3 rows, each 12 pts high
(define page-width (* 3 line-width)) ; meaning, two columns
;; posn-page : horiz position of column within page
;; posn-col : vert position of line within column
;; posn-line : horiz position of char within line
(struct posn (page col line) #:transparent)
(define (make-posn [page 0] [col 0] [line 0]) (posn page col line))
(define page-start-position (make-posn))
(define (fit qs [line-width line-width] [col-height col-height])
(define (handle-break val [current-posn #f])
(caseq val ; test in order of frequency
[(line-break) (make-posn (posn-page current-posn) (+ (posn-col current-posn) line-height))]
[(column-break) (make-posn (+ (posn-page current-posn) line-width))]
[(page-break) page-start-position]
[else current-posn]))
(for/fold ([current-posn page-start-position])
([q (in-vector qs)])
(unless (quad-dim q) (measure! q))
(cond
;; shim may contain an imperative break.
[($shim? q) (handle-break (quad-dim q) current-posn)]
;; test for overset (before a new bp-k gets set).
;; send break type back through continuation
;; we do a combined test to find out the "biggest" break that is needed
;; order connotes precedence
[(or
;; test page-horiz with >= because one column impliedly exists at the start
(and (>= (posn-page current-posn) page-width) 'page-break)
;; test tp-vert with >= because one column impliedly exists at the start
(and (>= (posn-col current-posn) col-height) 'column-break)
;; test tp-horiz with > because no characters exist in the line at the start
(and (> (posn-line current-posn) line-width) 'line-break)) => last-breakpoint-k]
;; set a new bp-k, or resume after invoking a bp-k
;; bp-k has to be in conditional so it triggers side effect but also forces next branch
[(and ($space? q) (set-breakpoint-k-here!))
=> ; grabs the value of the condition: the arg passed to breakpoint-k
(λ (breakpoint-k-result)
(when (already-breakpoint-type? q breakpoint-k-result)
;; it means we're caught in an overflow loop, so
(raise-overflow-error))
;; convert the white, thereby consuming it. todo: don't consume hyphens
(quad-dim-set! q breakpoint-k-result)
(handle-break breakpoint-k-result current-posn))]
[else (posn (posn-page current-posn) (posn-col current-posn) (+ (posn-line current-posn) (quad-dim q)))]))
qs)
(module+ test
(require "atomize.rkt" "render.rkt")
;; todo: preserve space between black quads
(define q (quad #f "One morning " (quad #f "and himself")))
(time (debug-render (fit (atomize q)))))

@ -0,0 +1,3 @@
#lang quad/typewriter
line line line line

@ -0,0 +1,47 @@
#lang debug br/quicklang
(require racket/promise "quad.rkt" "atomize.rkt" "break.rkt")
(provide (rename-out [mb #%module-begin]))
(define optional-break? (λ (q) (and (quad? q) (memv (car (qe q)) '(#\space)))))
(struct $slug $quad () #:transparent)
(define (slug . xs) ($slug #f xs))
(define (lbs xs size [debug #f])
(insert-breaks xs size debug
#:break-val 'lb
#:optional-break-proc optional-break?
#:size-proc (λ (q) (let ([val (hash-ref (qa q) 'size (λ ()
(if (memv (car (qe q)) '(#\space))
(delay (values 0 1 0))
(delay (values 1 1 1)))))])
(if (promise? val) (force val) (val))))
#:finish-segment-proc (λ (pcs) (list ($slug #f pcs)))))
(define (pbs xs size [debug #f])
(insert-breaks xs size debug
#:break-val 'pb
#:optional-break-proc (λ (x) (eq? x 'lb))
#:size-proc (λ (q) (case q
[(lb) (values 0 0 0)]
[else (values 1 1 1)]))))
(define (typeset args)
(pbs (lbs (atomize (apply quad #f args)) 5) 2))
(define-syntax-rule (mb lang-line-config-arg . args)
(#%module-begin
(typeset (list . args))))
(module reader syntax/module-reader
quad/typewriter
#:read quad-read
#:read-syntax quad-read-syntax
#:whole-body-readers? #t ;; need this to make at-reader work
(require scribble/reader)
(define (quad-read p) (syntax->datum (quad-read-syntax (object-name p) p)))
(define (quad-read-syntax path-string p)
(define quad-at-reader (make-at-reader
#:syntax? #t
#:inside? #t))
(quad-at-reader path-string p)))
Loading…
Cancel
Save