From c9c6663626bf0ae07553cd856d9be141881df9f4 Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Wed, 5 Jul 2017 22:49:23 -0700 Subject: [PATCH] shaping up --- pitfall/fontkit/default-shaper.rkt | 6 +- pitfall/fontkit/gpos-processor.rkt | 2 +- pitfall/fontkit/layout-engine.rkt | 45 +++++++++------ pitfall/fontkit/ot-layout-engine.rkt | 40 ++++++++++++- pitfall/fontkit/shapers.rkt | 5 ++ pitfall/fontkit/shaping-plan.rkt | 82 +++++++++++++++++++++++++++ pitfall/pitfall/test/test15rkt.pdf | Bin 0 -> 5993 bytes 7 files changed, 157 insertions(+), 23 deletions(-) diff --git a/pitfall/fontkit/default-shaper.rkt b/pitfall/fontkit/default-shaper.rkt index f850f08a..e32f4aff 100644 --- a/pitfall/fontkit/default-shaper.rkt +++ b/pitfall/fontkit/default-shaper.rkt @@ -15,6 +15,7 @@ (define-subclass object% (DefaultShaper) (define/public (plan plan_ glyphs features) + (report*/file plan_ glyphs features) ;; Plan the features we want to apply (planPreprocessing plan_) (planFeatures plan_) @@ -36,7 +37,10 @@ (void)) (define/public (planPostprocessing plan userFeatures) - (send plan add (append COMMON_FEATURES HORIZONTAL_FEATURES userFeatures))) + (when userFeatures + (unless (and (list? userFeatures) (andmap symbol? userFeatures)) + (raise-argument-error 'DefaultShaper:planPostprocessing "list of features" userFeatures))) + (send plan add (append COMMON_FEATURES HORIZONTAL_FEATURES (or userFeatures empty)))) (define/public (assignFeatures plan glyphs) ;; todo: Enable contextual fractions diff --git a/pitfall/fontkit/gpos-processor.rkt b/pitfall/fontkit/gpos-processor.rkt index 446b02ec..ef04d77e 100644 --- a/pitfall/fontkit/gpos-processor.rkt +++ b/pitfall/fontkit/gpos-processor.rkt @@ -7,5 +7,5 @@ https://github.com/mbutterick/fontkit/blob/master/src/opentype/GPOSProcessor.js |# (define-subclass OTProcessor (GPOSProcessor) - + ) \ No newline at end of file diff --git a/pitfall/fontkit/layout-engine.rkt b/pitfall/fontkit/layout-engine.rkt index 0a40e559..5efe3b28 100644 --- a/pitfall/fontkit/layout-engine.rkt +++ b/pitfall/fontkit/layout-engine.rkt @@ -52,8 +52,11 @@ https://github.com/mbutterick/fontkit/blob/master/src/layout/LayoutEngine.js (send (· this engine) setup glyphs features script language)) ;; Substitute and position the glyphs - (set! glyphs (send this substitute glyphs features script language)) + ;; todo: glyph substitution + #;(set! glyphs (send this substitute glyphs features script language)) + (report/file 'ready-position) (define positions (send this position glyphs features script language)) + (report/file 'fired-position) ;; Let the layout engine clean up any state it might have (when (and (· this engine) #;(·? this engine cleanup)) @@ -69,25 +72,31 @@ https://github.com/mbutterick/fontkit/blob/master/src/layout/LayoutEngine.js glyphs) -(define (position this glyphs features script language) +(define/contract (position this glyphs features script language) ((listof Glyph?) (option/c list?) (option/c symbol?) (option/c symbol?) . ->m . (listof GlyphPosition?)) - (define positions (for/list ([glyph (in-list glyphs)]) - (make-object GlyphPosition (· glyph advanceWidth)))) - #| + (make-object GlyphPosition (· glyph advanceWidth)))) + ;; Call the advanced layout engine. Returns the features applied. (define positioned (and (· this engine) #;(· this engine position) (send (· this engine) position glyphs positions features script language))) ;; if there is no GPOS table, use unicode properties to position marks. - ;; todo: implement unicodelayoutengine - + ;; todo: unicode layout + #;(unless positioned + (unless (· this unicodeLayoutEngine) + (set! unicodeLayoutEngine (+UnicodeLayoutEngine (· this font)))) + (send unicodeLayoutEngine positionGlyphs glyphs positions)) ;; if kerning is not supported by GPOS, do kerning with the TrueType/AAT kern table - ;; todo: implement kerning -|# + ;; todo: old style kern table + #;(when (and (or (not positioned) (not (· positioned kern))) (· this font kern)) + (unless kernProcessor + (set! kernProcessor (+KernProcessor (· this font)))) + (send kernProcessor process glyphs positions)) + positions ) @@ -97,15 +106,15 @@ https://github.com/mbutterick/fontkit/blob/master/src/layout/LayoutEngine.js (define space (send (· this font) glyphForCodePoint #x20)) (define-values (new-glyphs new-positions) (for/lists (ngs nps) - ([glyph (in-list (· glyphRun glyphs))] - [pos (in-list (· glyphRun positions))]) - (cond - [(send this isDefaultIgnorable (car (· glyph codePoints))) - (define new-pos pos) - (set-field! xAdvance new-pos 0) - (set-field! yAdvance new-pos 0) - (values space new-pos)] - [else (values glyph pos)]))) + ([glyph (in-list (· glyphRun glyphs))] + [pos (in-list (· glyphRun positions))]) + (cond + [(send this isDefaultIgnorable (car (· glyph codePoints))) + (define new-pos pos) + (set-field! xAdvance new-pos 0) + (set-field! yAdvance new-pos 0) + (values space new-pos)] + [else (values glyph pos)]))) (set-field! glyphs glyphRun new-glyphs) (set-field! positions glyphRun new-positions)) diff --git a/pitfall/fontkit/ot-layout-engine.rkt b/pitfall/fontkit/ot-layout-engine.rkt index 0574a37a..76ca8089 100644 --- a/pitfall/fontkit/ot-layout-engine.rkt +++ b/pitfall/fontkit/ot-layout-engine.rkt @@ -21,7 +21,7 @@ https://github.com/mbutterick/fontkit/blob/master/src/opentype/OTLayoutEngine.js (report* 'dingdong!-starting-gpos) (when (· font has-gpos-table?) - (set-field! GPOSProcessor this (+GPOSProcessor font (· font GPOS)))) + (set-field! GPOSProcessor this (+GPOSProcessor font (· font GPOS)))) (define/public (setup glyphs features script language) @@ -32,5 +32,39 @@ https://github.com/mbutterick/fontkit/blob/master/src/opentype/OTLayoutEngine.js ;; Choose a shaper based on the script, and setup a shaping plan. ;; This determines which features to apply to which glyphs. (set! shaper (Shapers-choose script)) - #;(set! plan (+ShapingPlan (· this font) script language)) - (send (· this shaper) plan (· this plan) (· this glyphInfos) features))) \ No newline at end of file + (set! plan (+ShapingPlan (· this font) script language)) + (report/file shaper) + (send (make-object shaper) plan (· this plan) (· this glyphInfos) features)) + + (define/public (position glyphs positions . _) + (report*/file glyphs positions shaper) + (define static-shaper (make-object shaper)) + (when (eq? (· static-shaper zeroMarkWidths) 'BEFORE_GPOS) + (zeroMarkAdvances positions)) + + (when GPOSProcessor + (report/file GPOSProcessor) + (send (· this plan) process GPOSProcessor glyphInfos positions)) + + (when (eq? (· static-shaper zeroMarkWidths) 'AFTER_GPOS) + (zeroMarkAdvances positions)) + + ;; Reverse the glyphs and positions if the script is right-to-left + (when (eq? (· this plan direction) 'rtl) + (set! glyphs (reverse glyphs)) + (set! positions (reverse positions))) + + (report/file (and GPOSProcessor (· GPOSProcessor features)))) + + + (define/public (zeroMarkAdvances positions) + (set! positions + (for/list ([glyphInfo (in-list glyphInfos)] + [position (in-list positions)]) + (when (· glyphInfo isMark) + (dict-set*! position + 'xAdvance 0 + 'yAdvance 0)) + position))) + + ) \ No newline at end of file diff --git a/pitfall/fontkit/shapers.rkt b/pitfall/fontkit/shapers.rkt index eedaaa26..9f4e8cc8 100644 --- a/pitfall/fontkit/shapers.rkt +++ b/pitfall/fontkit/shapers.rkt @@ -2,6 +2,11 @@ (require "default-shaper.rkt") (provide (all-defined-out)) +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/opentype/shapers/index.js +|# + ;; todo: alternative shapers (define SHAPERS (hasheq diff --git a/pitfall/fontkit/shaping-plan.rkt b/pitfall/fontkit/shaping-plan.rkt index fadd1001..cfca3e26 100644 --- a/pitfall/fontkit/shaping-plan.rkt +++ b/pitfall/fontkit/shaping-plan.rkt @@ -1,9 +1,91 @@ #lang fontkit/racket +(require (prefix-in Script- "script.rkt")) (provide (all-defined-out)) +#| +approximates +https://github.com/mbutterick/fontkit/blob/master/src/opentype/ShapingPlan.js +|# + ; * ShapingPlans are used by the OpenType shapers to store which ; * features should by applied, and in what order to apply them. ; * The features are applied in groups called stages. A feature ; * can be applied globally to all glyphs, or locally to only ; * specific glyphs. +(define-subclass object% (ShapingPlan font script language) + (field [direction (Script-direction script)] + [stages empty] + [globalFeatures (mhasheq)] + [allFeatures (mhasheq)]) + + ;; Adds the given features to the last stage. + ;; Ignores features that have already been applied. + (define/public (_addFeatures features) + (report*/file 'stages-before stages) + (match-define (list head-stages ... last-stage) stages) + (set! stages + `(,@head-stages + ,(append last-stage + (for/list ([feature (in-list features)] + #:unless (dict-ref (· this allFeatures) feature #f)) + (dict-set! (· this allFeatures) feature #t) + feature)))) + (report*/file 'stages-after stages)) + + ;; Adds the given features to the global list + (define/public (_addGlobal features) + (for ([feature (in-list features)]) + (dict-set! (· this globalFeatures) feature #t))) + + ;; Add features to the last stage + (define/public (add arg [global #t]) + (when (zero? (length (· this stages))) + (push-end-field! stages this empty)) + + (when (string? arg) + (set! arg (list arg))) + + (cond + [(list? arg) + (_addFeatures arg) + (when global (_addGlobal arg))] + [(dict? arg) + (define features (append (or (· arg global) empty) + (or (· arg local) empty))) + (_addFeatures features) + (when (· arg global) + (_addGlobal (· arg global)))] + [else (raise-argument-error 'ShapingPlan:add "valid arg" arg)])) + + ;; Add a new stage + (define/public (addStage arg global) + (cond + [(procedure? arg) + (push-end-field! stages this arg) + (push-end-field! stages this empty)] + [else (push-end-field! stages this empty) + (add arg global)])) + + ;; Assigns the global features to the given glyphs + (define/public (assignGlobalFeatures glyphs) + (report*/file glyphs (· this globalFeatures)) + (for* ([glyph (in-list glyphs)] + [feature (in-dict-keys (· this globalFeatures))]) + (dict-set! (· glyph features) feature #t))) + + ;; Executes the planned stages using the given OTProcessor + (define/public (process processor glyphs positions) + (report*/file 'shaping-plan-process processor) + (send processor selectScript (· this script) (· this language)) + + (report/file stages) + (for ([stage (in-list stages)]) + (cond + [(and (procedure? stage) (not positions)) + (stage (· this font) glyphs positions)] + [(> (length stage) 0) + (report*/file 'shaping-plan:applying-features processor) + (send processor applyFeatures stage glyphs positions)])))) + + diff --git a/pitfall/pitfall/test/test15rkt.pdf b/pitfall/pitfall/test/test15rkt.pdf index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..74b79659b78ceae3b109dde31c85cd10e994d32c 100644 GIT binary patch literal 5993 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 literal 0 HcmV?d00001