D cabal.sandbox.config => cabal.sandbox.config +0 -27
@@ 1,27 0,0 @@
--- This is a Cabal package environment file.
--- THIS FILE IS AUTO-GENERATED. DO NOT EDIT DIRECTLY.
--- Please create a 'cabal.config' file in the same directory
--- if you want to change the default settings for this sandbox.
-
-
-local-repo: /home/alcinnz/argonaut/typograffiti2/.cabal-sandbox/packages
-logs-dir: /home/alcinnz/argonaut/typograffiti2/.cabal-sandbox/logs
-world-file: /home/alcinnz/argonaut/typograffiti2/.cabal-sandbox/world
-user-install: False
-package-db: /home/alcinnz/argonaut/typograffiti2/.cabal-sandbox/x86_64-linux-ghc-8.6.5-packages.conf.d
-build-summary: /home/alcinnz/argonaut/typograffiti2/.cabal-sandbox/logs/build.log
-
-install-dirs
-  prefix: /home/alcinnz/argonaut/typograffiti2/.cabal-sandbox
-  bindir: $prefix/bin
-  libdir: $prefix/lib
-  libsubdir: $abi/$libname
-  dynlibdir: $libdir/$abi
-  libexecdir: $prefix/libexec
-  libexecsubdir: $abi/$pkgid
-  datadir: $prefix/share
-  datasubdir: $abi/$pkgid
-  docdir: $datadir/doc/$abi/$pkgid
-  htmldir: $docdir/html
-  haddockdir: $htmldir
-  sysconfdir: $prefix/etc
 
M src/Typograffiti.hs => src/Typograffiti.hs +3 -1
@@ 12,7 12,9 @@ module Typograffiti(
     makeDrawGlyphs, AllocatedRendering(..), Layout(..),
     SpatialTransform(..), TextTransform(..), move, scale, rotate, color, alpha,
     withFontStore, newFontStore, FontStore(..), Font(..),
-    SampleText (..), defaultSample, addSampleFeature,
+    SampleText (..), defaultSample, addSampleFeature, parseSampleFeature, parseSampleFeatures,
+        addFontVariant, parseFontVariant, parseFontVariants,
+        varItalic, varOptSize, varSlant, varWidth, varWeight,
     RichText (..), str, txt, ($$), style, apply, on, off, alternate,
         alt, case_, centerCJKPunct, capSpace, ctxtSwash, petiteCaps', smallCaps',
         expertJ, finGlyph, fract, fullWidth, hist, hkana, hlig, hojo, halfWidth,
 
A src/Typograffiti/Rich.hs => src/Typograffiti/Rich.hs +128 -0
@@ 0,0 1,128 @@
+module Typograffiti.Rich where
+import           Data.Text.Lazy     (Text, append, pack)
+import qualified Data.Text.Lazy as  Txt
+import           Data.Text.Glyphize (Feature(..), tag_from_string, parseFeature)
+import           Data.String        (IsString(..))
+import           Data.Word          (Word32)
+
+length' = toEnum . fromEnum . Txt.length
+
+data RichText = RichText {
+    text :: Text,
+    features :: [Feature]
+}
+
+instance IsString RichText where
+    fromString x = flip RichText [] $ pack x
+str :: String -> RichText
+str = fromString
+txt :: Text -> RichText
+txt = flip RichText []
+
+($$) :: RichText -> RichText -> RichText
+RichText ltext lfeat $$ RichText rtext rfeat = RichText {
+    text = append ltext rtext,
+    features = let n = length' ltext in lfeat ++ [
+        feat { featStart = start + n, featEnd = end + n }
+        | feat@Feature { featStart = start, featEnd = end } <- rfeat]
+  }
+
+style :: String -> Word32 -> RichText -> RichText
+style feat value (RichText text feats) = RichText {
+    text = text,
+    features = Feature (tag_from_string feat) value 0 (length' text) : feats
+  }
+apply :: String -> RichText -> RichText
+apply syntax rich | Just feat <- parseFeature syntax = rich {
+      features = feat { featStart = 0, featEnd = length' $ text rich } : features rich
+    }
+  | otherwise = rich
+
+alt, case_, centerCJKPunct, capSpace, ctxtSwash, petiteCaps', smallCaps', expertJ,
+    finGlyph, fract, fullWidth, hist, hkana, hlig, hojo, halfWidth, italic, justifyAlt,
+    jap78, jap83, jap90, jap04, kerning, lBounds, liningFig, localized, mathGreek,
+    altAnnotation, nlcKanji, oldFig, ordinals, ornament, propAltWidth, petiteCaps,
+    propKana, propFig, propWidth, quarterWidth, rBounds, ruby, styleAlt, sciInferior,
+    smallCaps, simpleCJ, subscript, superscript, swash, titling, traditionNameJ,
+    tabularFig, traditionCJ, thirdWidth, unicase, vAlt, vert, vHalfAlt, vKanaAlt,
+    vKerning, vPropAlt, vRotAlt, vrot, slash0 :: Word32 -> RichText -> RichText
+altFrac, ctxtAlt, ctxtLig, optLigs, lig, rand :: Bool -> RichText -> RichText
+alt         = style "aalt"
+altFrac True= style "afrc" 4
+altFrac False=style "afrc" 0
+ctxtAlt True= style "calt" 6
+ctxtAlt False=style "calt" 0
+case_       = style "case"
+ctxtLig True= style "clig" 8
+ctxtLig False=style "clig" 0
+centerCJKPunct = style "cpct"
+capSpace    = style "cpsp"
+ctxtSwash   = style "cswh"
+petiteCaps' = style "c2pc"
+smallCaps'  = style "c2sc"
+optLigs True= style "dlig" 4
+optLigs False=style "dlig" 0
+expertJ     = style "expt"
+finGlyph    = style "falt"
+fract       = style "frac"
+fullWidth   = style "fwid"
+hist        = style "hist"
+hkana       = style "hkna"
+hlig        = style "hlig"
+hojo        = style "hojo"
+halfWidth   = style "hwid"
+italic      = style "ital"
+justifyAlt  = style "jalt"
+jap78       = style "jp78"
+jap83       = style "jp83"
+jap90       = style "jp90"
+jap04       = style "jp04"
+kerning     = style "kern"
+lBounds     = style "lfbd"
+lig True    = style "liga" 4
+lig False   = style "liga" 0
+liningFig   = style "lnum"
+localized   = style "locl"
+mathGreek   = style "mgrk"
+altAnnotation=style "nalt"
+nlcKanji    = style "nlck"
+oldFig      = style "onum"
+ordinals    = style "ordn"
+ornament    = style "ornm"
+propAltWidth= style "palt"
+petiteCaps  = style "pcap"
+propKana    = style "pkna"
+propFig     = style "pnum"
+propWidth   = style "pwid"
+quarterWidth= style "qwid"
+rand True   = style "rand" 3
+rand False  = style "rand" 0
+rBounds     = style "rtbd"
+ruby        = style "ruby"
+styleAlt    = style "salt"
+sciInferior = style "sinf"
+smallCaps   = style "smcp"
+simpleCJ    = style "smpl"
+subscript   = style "subs"
+superscript = style "sups"
+swash       = style "swsh"
+titling     = style "titl"
+traditionNameJ = style "tnam"
+tabularFig  = style "tnum"
+traditionCJ = style "trad"
+thirdWidth  = style "twid"
+unicase     = style "unic"
+vAlt        = style "valt"
+vert        = style "vert"
+vHalfAlt    = style "vhal"
+vKanaAlt    = style "vkna"
+vKerning    = style "vkrn"
+vPropAlt    = style "vpal"
+vRotAlt     = style "vrt2"
+vrot        = style "vrtr"
+slash0      = style "zero"
+
+off, on, alternate :: Word32
+off = 0
+on = 1
+alternate = 3
 
M src/Typograffiti/Store.hs => src/Typograffiti/Store.hs +12 -5
@@ 33,6 33,8 @@ import qualified Data.Text.Glyphize     as HB
 import           Data.Text.Lazy         (Text, pack)
 import qualified Data.Text.Lazy         as Txt
 import           FreeType.Core.Base
+import           FreeType.Core.Types    (FT_Fixed)
+import           FreeType.Format.Multiple (ft_Set_Var_Design_Coordinates)
 
 import           Typograffiti.Atlas
 import           Typograffiti.Cache
@@ 57,7 59,7 @@ makeDrawTextCached :: (MonadIO m, MonadFail m, MonadError TypograffitiError m,
 makeDrawTextCached store filepath index fontsize SampleText {..} = do
     s <- liftIO $ atomically $ readTMVar $ fontMap store
     font <- case M.lookup (filepath, fontsize, index) s of
-        Nothing -> allocFont store filepath index fontsize
+        Nothing -> allocFont store filepath index fontsize fontOptions
         Just font -> return font
 
     let glyphs = map (codepoint . fst) $
@@ 74,8 76,9 @@ makeDrawTextCached store filepath index fontsize SampleText {..} = do
     return $ drawLinesWrapper tabwidth $ \RichText {..} -> drawGlyphs store atlas $
         shape (harfbuzz font) defaultBuffer { HB.text = text } []
 
-allocFont :: (MonadIO m) => FontStore n -> FilePath -> Int -> GlyphSize -> m Font
-allocFont FontStore {..} filepath index fontsize = liftIO $ do
+allocFont :: (MonadIO m) =>
+        FontStore n -> FilePath -> Int -> GlyphSize -> HB.FontOptions -> m Font
+allocFont FontStore {..} filepath index fontsize options = liftIO $ do
     font <- ft_New_Face lib filepath $ toEnum index
     case fontsize of
         PixelSize w h -> ft_Set_Pixel_Sizes font (toEnum $ x2 w) (toEnum $ x2 h)
@@ 84,7 87,8 @@ allocFont FontStore {..} filepath index fontsize = liftIO $ do
                                                     (toEnum dpix) (toEnum dpiy)
 
     bytes <- B.readFile filepath
-    let font' = HB.createFont $ HB.createFace bytes $ toEnum index
+    let font' = HB.createFontWithOptions options $ HB.createFace bytes $ toEnum index
+    liftIO $ ft_Set_Var_Design_Coordinates font $ map float2fixed $ HB.fontVarCoordsDesign font'
 
     atlases <- liftIO $ atomically $ newTMVar []
     let ret = Font font' font atlases
@@ 93,7 97,10 @@ allocFont FontStore {..} filepath index fontsize = liftIO $ do
         map <- takeTMVar fontMap
         putTMVar fontMap $ M.insert (filepath, fontsize, index) ret map
     return ret
-  where x2 = (*2)
+  where
+    x2 = (*2)
+    float2fixed :: Float -> FT_Fixed
+    float2fixed = toEnum . fromEnum . (*65536)
 
 allocAtlas' :: (MonadIO m, MonadFail m) =>
     TMVar [(IS.IntSet, Atlas)] -> FT_Face -> IS.IntSet -> m Atlas
 
M src/Typograffiti/Text.hs => src/Typograffiti/Text.hs +44 -5
@@ 29,9 29,13 @@ import qualified Data.Set               as S
 import qualified Data.IntSet            as IS
 import           Linear
 import qualified Data.ByteString        as B
-import           Data.Text.Glyphize     (defaultBuffer, Buffer(..), shape, GlyphInfo(..))
+import           Data.Text.Glyphize     (defaultBuffer, Buffer(..), shape, GlyphInfo(..),
+                                        parseFeature, parseVariation, Variation(..),
+                                        FontOptions(..), defaultFontOptions)
 import qualified Data.Text.Glyphize     as HB
 import           FreeType.Core.Base
+import           FreeType.Core.Types    (FT_Fixed)
+import           FreeType.Format.Multiple (ft_Set_Var_Design_Coordinates)
 import           Data.Text.Lazy         (Text, pack)
 import qualified Data.Text.Lazy         as Txt
 import           Data.Word              (Word32)
@@ 47,11 51,12 @@ data GlyphSize = CharSize Float Float Int Int
 data SampleText = SampleText {
     sampleFeatures :: [HB.Feature],
     sampleText :: Text,
-    tabwidth :: Int
+    tabwidth :: Int,
+    fontOptions :: FontOptions
 }
 
 defaultSample :: SampleText
-defaultSample = SampleText [] (pack $ map toEnum [32..126]) 4
+defaultSample = SampleText [] (pack $ map toEnum [32..126]) 4 defaultFontOptions
 addSampleFeature :: String -> Word32 -> SampleText -> SampleText
 addSampleFeature name value sample@SampleText {..} = sample {
         sampleFeatures =
@@ 62,6 67,35 @@ addSampleFeature name value sample@SampleText {..} = sample {
     i = w $ length sampleFeatures
     w :: Int -> Word
     w = toEnum
+parseSampleFeature :: String -> SampleText -> SampleText
+parseSampleFeature syntax sample | Just feat <- parseFeature syntax = sample {
+        sampleFeatures = feat : sampleFeatures sample
+    }
+  | otherwise = sample
+parseSampleFeatures :: [String] -> SampleText -> SampleText
+parseSampleFeatures = flip $ foldl $ flip parseSampleFeature
+addFontVariant :: String -> Float -> SampleText -> SampleText
+addFontVariant name val sampleText = sampleText {
+    fontOptions = (fontOptions sampleText) {
+        optionVariations = Variation (HB.tag_from_string name) val :
+            optionVariations (fontOptions sampleText)
+    }
+  }
+parseFontVariant :: String -> SampleText -> SampleText
+parseFontVariant syntax sample | Just var <- parseVariation syntax = sample {
+        fontOptions = (fontOptions sample) {
+            optionVariations = var : optionVariations (fontOptions sample)
+        }
+    }
+  | otherwise = sample
+parseFontVariants :: [String] -> SampleText -> SampleText
+parseFontVariants = flip $ foldl $ flip parseFontVariant
+
+varItalic = "ital"
+varOptSize = "opsz"
+varSlant = "slnt"
+varWidth = "wdth"
+varWeight = "wght"
 
 makeDrawText :: (MonadIO m, MonadFail m, MonadError TypograffitiError m,
     MonadIO n, MonadFail n, MonadError TypograffitiError n) =>
@@ 76,19 110,24 @@ makeDrawText lib filepath index fontsize SampleText {..} = do
                                                     (toEnum dpix) (toEnum dpiy)
 
     bytes <- liftIO $ B.readFile filepath
-    let font' = HB.createFont $ HB.createFace bytes $ toEnum index
+    let font' = HB.createFontWithOptions fontOptions $ HB.createFace bytes $ toEnum index
     let glyphs = map (codepoint . fst) $
             shape font' defaultBuffer {
                 HB.text = Txt.replicate (toEnum $ succ $ length sampleFeatures) sampleText
             } sampleFeatures
     let glyphs' = map toEnum $ IS.toList $ IS.fromList $ map fromEnum glyphs
+    -- FIXME expose this function...
+    liftIO $ ft_Set_Var_Design_Coordinates font $ map float2fixed $ HB.fontVarCoordsDesign font'
     atlas <- allocAtlas (glyphRetriever font) glyphs'
     liftIO $ ft_Done_Face font
 
     drawGlyphs <- makeDrawGlyphs
     return $ drawLinesWrapper tabwidth $ \RichText {..} ->
         drawGlyphs atlas $ shape font' defaultBuffer { HB.text = text } features
-  where x2 = (*2)
+  where
+    x2 = (*2)
+    float2fixed :: Float -> FT_Fixed
+    float2fixed = toEnum . fromEnum . (*65536)
 
 makeDrawText' a b c d =
     ft_With_FreeType $ \ft -> runExceptT $ makeDrawText ft a b c d
 
R typograffiti2.cabal => typograffiti.cabal +1 -1
@@ 35,7 35,7 @@ library
       Typograffiti.Rich
   build-depends:       base >=4.12 && <4.13, linear>=1.20, containers >= 0.6,
                         freetype2 >= 0.2, gl >= 0.8, mtl >= 2.2, stm >= 2.5, text,
-                        vector >= 0.12, harfbuzz-pure >= 0.0.7, bytestring >= 0.10
+                        vector >= 0.12, harfbuzz-pure >= 1.0.1, bytestring >= 0.10
   hs-source-dirs:      src
   default-language:    Haskell2010