ntg-context - mailing list for ConTeXt users
 help / color / mirror / Atom feed
From: "Marcel Fabian Krüger" <tex@2krueger.de>
To: ConTeXt users list <ntg-context@ntg.nl>
Subject: CFF2 based variable fonts with SubRS
Date: Wed, 26 Aug 2020 11:01:37 +0200	[thread overview]
Message-ID: <20200826090137.oomejyu6us4kjvhd@yoga> (raw)

[-- Attachment #1: Type: text/plain, Size: 5941 bytes --]

Hi,

I was playing with the variable font support of ConTeXt's font loader
and noticed some issues.

  1. While parsing the LocalSubRS and GlobalSubRS Indices, ConTeXt tries
     to use 16bit count fields as in CFF(1) instead of 32bit fields as in
     CFF2, reducing the number of found subroutines by a factor of 65536.
     Effectivly this suppressed all subroutines in the fonts I tested.

  2. For fonts where the Top DICT does not contain an FDSelect entry,
     ConTeXt tries to read the Private dictionary offset from the Top
     dictionary as in name-keyed CFF1 fonts. This is not the expected
     behavior for CFF2, where the Top dictionary never contains the Privte
     offset.
     Instead, a missing FDSelect means that exactly one entry exists in
     FDArray which should be used for all CIDs. This caused problems
     when ConTeXt tried to find the Private directory in order to find
     local subroutines.

A test font demonstrating both issues is "SourceCode Variable"
https://github.com/adobe-fonts/source-code-pro/releases:

The document

\definefontfeature
  [light]
  [default]
  [axis={weight=200}]

\definefont
  [sourcelight]
  [file:SourceCodeVariable-Roman.otf*light]

\starttext
\sourcelight Hallo
\stoptext

generates

[...]
otf reader      > cff >     unknown local call 14, case 2 : [] n=0
otf reader      > cff >     unknown local call 11, case 2 : [] n=0
otf reader      > cff >     unknown local call 138, case 2 : [] n=0
otf reader      > cff >     unknown local call 314, case 2 : [300 -12] n=2
otf reader      > cff >     unknown local call 607, case 2 : [-99 -66 99 174] n=4
otf reader      > fatal error in file 'SourceCodeVariable-Roman.otf': ...e0dde776fb1556f32e/formats/luametatex/font-cff-macro.lua:1517: attempt to perform arithmetic on a nil value (local 'v')
stack traceback:
	...e0dde776fb1556f32e/formats/luametatex/font-otr-macro.lua:2339: in metamethod 'add'
	...e0dde776fb1556f32e/formats/luametatex/font-cff-macro.lua:1517: in local 'a'
	...e0dde776fb1556f32e/formats/luametatex/font-cff-macro.lua:1980: in upvalue 'process'
[...]


A possible fix for both issues would be


diff --git a/tex/context/base/mkiv/font-cff.lua b/tex/context/base/mkiv/font-cff.lua
index c2cf0e699..d00637b8e 100644
--- a/tex/context/base/mkiv/font-cff.lua
+++ b/tex/context/base/mkiv/font-cff.lua
@@ -2265,8 +2265,8 @@ do
 
 end
 
-local function readglobals(f,data)
-    local routines = readlengths(f)
+local function readglobals(f,data,version)
+    local routines = readlengths(f,version == "cff2")
     for i=1,#routines do
         routines[i] = readbytetable(f,routines[i])
     end
@@ -2324,14 +2324,14 @@ local function readprivates(f,data)
     end
 end
 
-local function readlocals(f,data,dictionary)
+local function readlocals(f,data,dictionary,version)
     local header  = data.header
     local private = dictionary.private
     if private then
         local subroutineoffset = private.data.subroutines
         if subroutineoffset ~= 0 then
             setposition(f,header.offset+private.offset+subroutineoffset)
-            local subroutines = readlengths(f)
+            local subroutines = readlengths(f,version=="cff2")
             for i=1,#subroutines do
                 subroutines[i] = readbytetable(f,subroutines[i])
             end
@@ -2394,7 +2394,7 @@ readers.parsecharstrings = parsecharstrings -- used in font-onr.lua (type 1)
 local function readnoselect(f,fontdata,data,glyphs,doshapes,version,streams)
     local dictionaries = data.dictionaries
     local dictionary   = dictionaries[1]
-    readglobals(f,data)
+    readglobals(f,data,version)
     readcharstrings(f,data,version)
     if version == "cff2" then
         dictionary.charset = nil
@@ -2402,9 +2402,19 @@ local function readnoselect(f,fontdata,data,glyphs,doshapes,version,streams)
         readencodings(f,data)
         readcharsets(f,data,dictionary)
     end
+    local cid          = dictionary.cid
+    local fdarray      = cid and cid.fdarray
+    if fdarray and not dictionary.private then
+        setposition(f,data.header.offset+fdarray)
+        local dictionaries = readlengths(f,version=="cff2")
+        assert(#dictionaries == 1)
+        dictionaries[1] = readstring(f,dictionaries[1])
+        parsedictionaries(data,dictionaries)
+        dictionary.private = dictionaries[1].private
+    end
     readprivates(f,data)
     parseprivates(data,data.dictionaries)
-    readlocals(f,data,dictionary)
+    readlocals(f,data,dictionary,version)
     startparsing(fontdata,data,streams)
     parsecharstrings(fontdata,data,glyphs,doshapes,version,streams)
     stopparsing(fontdata,data)
@@ -2416,7 +2426,7 @@ local function readfdselect(f,fontdata,data,glyphs,doshapes,version,streams)
     local dictionary   = dictionaries[1]
     local cid          = dictionary.cid
     local cidselect    = cid and cid.fdselect
-    readglobals(f,data)
+    readglobals(f,data,version)
     readcharstrings(f,data,version)
     if version ~= "cff2" then
         readencodings(f,data)
@@ -2462,7 +2472,7 @@ local function readfdselect(f,fontdata,data,glyphs,doshapes,version,streams)
         local cidarray = cid.fdarray
         if cidarray then
             setposition(f,header.offset+cidarray)
-            local dictionaries = readlengths(f)
+            local dictionaries = readlengths(f, version == "cff2")
             for i=1,#dictionaries do
                 dictionaries[i] = readstring(f,dictionaries[i])
             end
@@ -2470,7 +2480,7 @@ local function readfdselect(f,fontdata,data,glyphs,doshapes,version,streams)
             cid.dictionaries = dictionaries
             readcidprivates(f,data)
             for i=1,#dictionaries do
-                readlocals(f,data,dictionaries[i])
+                readlocals(f,data,dictionaries[i],version)
             end
             startparsing(fontdata,data,streams)
             for i=1,#charstrings do

----
Best regards,
Marcel

[-- Attachment #2: cff2_patch.diff --]
[-- Type: text/plain, Size: 3781 bytes --]

diff --git a/tex/context/base/mkiv/font-cff.lua b/tex/context/base/mkiv/font-cff.lua
index c2cf0e699..d00637b8e 100644
--- a/tex/context/base/mkiv/font-cff.lua
+++ b/tex/context/base/mkiv/font-cff.lua
@@ -2265,8 +2265,8 @@ do
 
 end
 
-local function readglobals(f,data)
-    local routines = readlengths(f)
+local function readglobals(f,data,version)
+    local routines = readlengths(f,version == "cff2")
     for i=1,#routines do
         routines[i] = readbytetable(f,routines[i])
     end
@@ -2324,14 +2324,14 @@ local function readprivates(f,data)
     end
 end
 
-local function readlocals(f,data,dictionary)
+local function readlocals(f,data,dictionary,version)
     local header  = data.header
     local private = dictionary.private
     if private then
         local subroutineoffset = private.data.subroutines
         if subroutineoffset ~= 0 then
             setposition(f,header.offset+private.offset+subroutineoffset)
-            local subroutines = readlengths(f)
+            local subroutines = readlengths(f,version=="cff2")
             for i=1,#subroutines do
                 subroutines[i] = readbytetable(f,subroutines[i])
             end
@@ -2394,7 +2394,7 @@ readers.parsecharstrings = parsecharstrings -- used in font-onr.lua (type 1)
 local function readnoselect(f,fontdata,data,glyphs,doshapes,version,streams)
     local dictionaries = data.dictionaries
     local dictionary   = dictionaries[1]
-    readglobals(f,data)
+    readglobals(f,data,version)
     readcharstrings(f,data,version)
     if version == "cff2" then
         dictionary.charset = nil
@@ -2402,9 +2402,19 @@ local function readnoselect(f,fontdata,data,glyphs,doshapes,version,streams)
         readencodings(f,data)
         readcharsets(f,data,dictionary)
     end
+    local cid          = dictionary.cid
+    local fdarray      = cid and cid.fdarray
+    if fdarray and not dictionary.private then
+        setposition(f,data.header.offset+fdarray)
+        local dictionaries = readlengths(f,version=="cff2")
+        assert(#dictionaries == 1)
+        dictionaries[1] = readstring(f,dictionaries[1])
+        parsedictionaries(data,dictionaries)
+        dictionary.private = dictionaries[1].private
+    end
     readprivates(f,data)
     parseprivates(data,data.dictionaries)
-    readlocals(f,data,dictionary)
+    readlocals(f,data,dictionary,version)
     startparsing(fontdata,data,streams)
     parsecharstrings(fontdata,data,glyphs,doshapes,version,streams)
     stopparsing(fontdata,data)
@@ -2416,7 +2426,7 @@ local function readfdselect(f,fontdata,data,glyphs,doshapes,version,streams)
     local dictionary   = dictionaries[1]
     local cid          = dictionary.cid
     local cidselect    = cid and cid.fdselect
-    readglobals(f,data)
+    readglobals(f,data,version)
     readcharstrings(f,data,version)
     if version ~= "cff2" then
         readencodings(f,data)
@@ -2462,7 +2472,7 @@ local function readfdselect(f,fontdata,data,glyphs,doshapes,version,streams)
         local cidarray = cid.fdarray
         if cidarray then
             setposition(f,header.offset+cidarray)
-            local dictionaries = readlengths(f)
+            local dictionaries = readlengths(f, version == "cff2")
             for i=1,#dictionaries do
                 dictionaries[i] = readstring(f,dictionaries[i])
             end
@@ -2470,7 +2480,7 @@ local function readfdselect(f,fontdata,data,glyphs,doshapes,version,streams)
             cid.dictionaries = dictionaries
             readcidprivates(f,data)
             for i=1,#dictionaries do
-                readlocals(f,data,dictionaries[i])
+                readlocals(f,data,dictionaries[i],version)
             end
             startparsing(fontdata,data,streams)
             for i=1,#charstrings do

[-- Attachment #3: Type: text/plain, Size: 493 bytes --]

___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the Wiki!

maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
webpage  : http://www.pragma-ade.nl / http://context.aanhet.net
archive  : https://bitbucket.org/phg/context-mirror/commits/
wiki     : http://contextgarden.net
___________________________________________________________________________________

             reply	other threads:[~2020-08-26  9:01 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-08-26  9:01 Marcel Fabian Krüger [this message]
2020-08-26 10:40 ` Hans Hagen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200826090137.oomejyu6us4kjvhd@yoga \
    --to=tex@2krueger.de \
    --cc=ntg-context@ntg.nl \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).