colo-icc.lua / last modification: 2020-01-30 14:16
if not modules then modules = { } end modules ['colo-icc'] = {
    version   = 1.000,
    comment   = "companion to colo-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

local char, byte, gsub, match, format, strip = string.char, string.byte, string.gsub, string.match, string.format, string.strip
local readstring, readnumber = io.readstring, io.readnumber
local band = bit32.band
local next = next

local colors = attributes and attributes.colors or { } -- when used in mtxrun

local report_colors = logs.reporter("colors","icc")

local R, Cs, lpegmatch = lpeg.R, lpeg.Cs, lpeg.match

local invalid = R(char(0)..char(31))
local cleaned = invalid^0 * Cs((1-invalid)^0)

function colors.iccprofile(filename,verbose)
    local fullname = resolvers.findfile(filename,"icc") or ""
    if fullname == "" then
        local locate = resolvers.finders.byscheme -- not in mtxrun
        if locate then
            fullname = locate("loc",filename)
        end
    end
    if fullname == "" then
        report_colors("profile %a cannot be found",filename)
        return nil, false
    end
    local f = io.open(fullname,"rb")
    if not f then
        report_colors("profile %a cannot be loaded",fullname)
        return nil, false
    end
    local header =  {
        size               = readnumber(f,4),
        cmmtype            = readnumber(f,4),
        version            = readnumber(f,4),
        deviceclass        = strip(readstring(f,4)),
        colorspace         = strip(readstring(f,4)),
        connectionspace    = strip(readstring(f,4)),
        datetime           = {
            year    = readnumber(f,2),
            month   = readnumber(f,2),
            day     = readnumber(f,2),
            hour    = readnumber(f,2),
            minutes = readnumber(f,2),
            seconds = readnumber(f,2),
        },
        filesignature      = strip(readstring(f,4)),
        platformsignature  = strip(readstring(f,4)),
        options            = readnumber(f,4),
        devicemanufacturer = strip(readstring(f,4)),
        devicemodel        = strip(readstring(f,4)),
        deviceattributes   = readnumber(f,4),
        renderingintent    = readnumber(f,4),
        illuminantxyz      = {
            x = readnumber(f,4),
            y = readnumber(f,4),
            z = readnumber(f,4),
        },
        profilecreator     = readnumber(f,4),
        id                 = strip(readstring(f,16)),
    }
    local tags = { }
    for i=1,readnumber(f,128,4) do
        tags[readstring(f,4)] = {
            offset = readnumber(f,4),
            length = readnumber(f,4),
        }
    end
    local o = header.options
    header.options =
        o == 0 and "embedded"  or
        o == 1 and "dependent" or "unknown"
    local d = header.deviceattributes
    header.deviceattributes = {
        [band(d,1) ~= 0 and "transparency" or "reflective"] = true,
        [band(d,2) ~= 0 and "mate"         or "glossy"    ] = true,
        [band(d,3) ~= 0 and "negative"     or "positive"  ] = true,
        [band(d,4) ~= 0 and "bw"           or "color"     ] = true,
    }
    local r = header.renderingintent
    header.renderingintent =
        r == 0 and "perceptual" or
        r == 1 and "relative"   or
        r == 2 and "saturation" or
        r == 3 and "absolute"   or "unknown"
    for tag, spec in next, tags do
        if tag then
            local offset, length = spec.offset, spec.length
            local variant = readstring(f,offset,4)
            if variant == "text" or variant == "desc" then
                local str = readstring(f,length-4)
                tags[tag] = {
                    data    = str,
                    cleaned = lpegmatch(cleaned,str),
                }
            else
                if verbose then
                    report_colors("ignoring tag %a or type %a in profile %a",tag,variant,fullname)
                end
                tags[tag] = nil
            end
        end
    end
    f:close()
    local profile = {
        filename = filename,
        fullname = fullname,
        header   = header,
        tags     = tags,
    }
    report_colors("profile %a loaded",fullname)
    return profile, true
end