/*
 *  $Id: createc.c 29611 2026-03-17 10:31:06Z yeti-dn $
 *  Copyright (C) 2004 Rok Zitko.
 *  E-mail: rok.zitko@ijs.si.
 *  Copyright (C) 2009-2025 David Necas (Yeti).
 *  E-mail: yeti@gwyddion.net.
 *
 *  Based on nanoscope.c, Copyright (C) 2004 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* TODO:
 * - constant height or current
*/

/**
 * [FILE-MAGIC-FREEDESKTOP]
 * <mime-type type="application/x-createc-spm">
 *   <comment>Createc SPM data</comment>
 *   <magic priority="80">
 *     <match type="string" offset="0" value="[Parameter]\r\nDAC-Type"/>
 *     <match type="string" offset="0" value="[Paramet32]\r\nDAC-Type"/>
 *     <match type="string" offset="0" value="[Paramco32]\r\nDAC-Type"/>
 *   </magic>
 * </mime-type>
 **/

/**
 * [FILE-MAGIC-FILEMAGIC]
 * # Createc SPM.
 * 0 string [Parameter]\x0d\x0aDAC-Type Createc SPM data, 16bit int data
 * 0 string [Paramet32]\x0d\x0aDAC-Type Createc SPM data, 32bit float data
 * 0 string [Paramco32]\x0d\x0aDAC-Type Createc SPM data, 32bit float data, compressed
 **/

/**
 * [FILE-MAGIC-USERGUIDE]
 * Createc DAT
 * .dat
 * Read
 **/

#include "config.h"
#include <glib/gi18n-lib.h>
#include <string.h>
#include <stdlib.h>

#include <gwy.h>
#include "gwyzlib.h"
#include "err.h"

#define Angstrom (1e-10)

enum {
    HEADER_SIZE = 16384,
};

typedef enum {
    CREATEC_NONE,
    CREATEC_1,
    CREATEC_2,
    CREATEC_2Z,
} CreatecVersion;

typedef enum {
    CHANNEL_TOPOGRAPHY = 1u << 0,
    CHANNEL_CURRENT    = 1u << 1,
    CHANNEL_ADC1       = 1u << 2,
    CHANNEL_ADC2       = 1u << 3,
    CHANNEL_ADC3       = 1u << 4,
    CHANNEL_DI_DV      = 1u << 5,
    CHANNEL_D2I_DV     = 1u << 6,
    CHANNEL_DF         = 1u << 7,
    CHANNEL_DAMPING    = 1u << 8,
    CHANNEL_AMPLITUDE  = 1u << 9,
    CHANNEL_CP         = 1u << 10,
    CHANNEL_AUX1       = 1u << 11,
} CreatecChannelFlags;

static gboolean       module_register     (void);
static gint           detect_file         (const GwyFileDetectInfo *fileinfo,
                                           gboolean only_name);
static GwyFile*       load_file           (const gchar *filename,
                                           GwyRunModeFlags mode,
                                           GError **error);
static CreatecVersion createc_get_version (const gchar *buffer,
                                           gsize size);
static GwyField*      hash_to_field       (GHashTable *hash,
                                           gint version,
                                           guint channelbit,
                                           const gchar *buffer,
                                           gsize size,
                                           gsize *offset,
                                           GError **error);
static gsize          first_channel_offset(gint version);
static guint          channel_bpp         (gint version);
static void           read_binary_data    (gint n,
                                           gdouble *data,
                                           const gchar *buffer,
                                           gint bpp);
static GwyDict*  get_metadata        (GHashTable *hash);

static const GwyEnum versions[] = {
    { "[Parameter]", CREATEC_1,  },
    { "[Paramet32]", CREATEC_2,  },
    { "[Paramco32]", CREATEC_2Z, },
};

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Imports Createc data files."),
    "Rok Zitko <rok.zitko@ijs.si>",
    "2.0",
    "Rok Zitko, David Nečas (Yeti)",
    "2004",
};

GWY_MODULE_QUERY2(module_info, createc)

static gboolean
module_register(void)
{
    gwy_file_func_register("createc",
                           N_("Createc files (.dat)"),
                           detect_file, load_file, NULL, NULL);

    return TRUE;
}

static gint
detect_file(const GwyFileDetectInfo *fileinfo, gboolean only_name)
{
    if (only_name)
        return g_str_has_suffix(fileinfo->name_lowercase, ".dat") ? 10 : 0;

    if (createc_get_version(fileinfo->head, fileinfo->buffer_len))
        return 100;

    return 0;
}

#define createc_atof(x) g_ascii_strtod(x, NULL)

/* Macros to extract integer/double variables in hash_to_field() */
/* Any missing keyword/value pair is fatal, so we return a NULL pointer. */
#define HASH_GET(key, var, typeconv, err) \
    if (!(s = g_hash_table_lookup(hash, key))) { \
        err_MISSING_FIELD(err, key); \
        goto fail; \
    } \
    var = typeconv(s)

/* Support for alternative keywords in some (apparently newer) versions of dat files */
#define HASH_GET2(key1, key2, var, typeconv, err) \
    if (!(s = g_hash_table_lookup(hash, key1))) { \
      if (!(s = g_hash_table_lookup(hash, key2))) { \
          g_set_error(err, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA, \
                      _("Neither `%s' nor `%s' header field found."), \
                      key1, key2); \
        goto fail; \
      } \
    } \
    var = typeconv(s)

#define HASH_INT(key, var, err)    HASH_GET(key, var, atoi, err)
#define HASH_DOUBLE(key, var, err) HASH_GET(key, var, createc_atof, err)
#define HASH_STRING(key, var, err) HASH_GET(key, var, /* */, err)

#define HASH_INT2(key1, key2, var, err)    HASH_GET2(key1, key2, var, atoi, err)
#define HASH_DOUBLE2(key1, key2, var, err) HASH_GET2(key1, key2, var, createc_atof, err)
#define HASH_STRING2(key1, key2, var, err) HASH_GET2(key1, key2, var, /* */, err)

static GwyFile*
load_file(const gchar *filename,
          G_GNUC_UNUSED GwyRunModeFlags mode,
          GError **error)
{
    static const GwyEnum channel_names[] = {
        { "Topography", CHANNEL_TOPOGRAPHY, },
        { "Current",    CHANNEL_CURRENT,    },
        { "ADC1",       CHANNEL_ADC1,       },
        { "ADC2",       CHANNEL_ADC2,       },
        { "ADC3",       CHANNEL_ADC3,       },
        { "dI/dV",      CHANNEL_DI_DV,      },
        { "d2I/dV",     CHANNEL_D2I_DV,     },
        { "df",         CHANNEL_DF,         },
        { "Damping",    CHANNEL_DAMPING,    },
        { "Amplitude",  CHANNEL_AMPLITUDE,  },
        { "CP",         CHANNEL_CP,         },
        { "AUX1",       CHANNEL_AUX1,       },
        /* Fallback, usually right. */
        { "Voltage",    0,                  },
    };
    GwyFile *file = NULL;
    GwyDict *meta;
    const guchar *buffer = NULL, *imagedata;
    guchar *zbuffer = NULL;
    gchar *p, *head;
    gsize size = 0, datasize = 0, offset;
    guint len, nchannels, id, channelbit, channelselect;
    GError *err = NULL;
    const gchar *s; /* for HASH_GET macros */
    GwyTextHeaderParser parser;
    GHashTable *hash = NULL;
    GwyField *dfield;
    CreatecVersion version;
    const gchar *title;

    if (!gwy_map_file_contents(filename, &buffer, &size, &err)) {
        err_GET_FILE_CONTENTS(error, &err);
        return NULL;
    }
    if (!(version = createc_get_version(buffer, size))) {
        err_FILE_TYPE(error, "Createc");
        gwy_unmap_file_contents(buffer);
        return NULL;
    }

    head = g_memdup(buffer, HEADER_SIZE + 1);
    head[HEADER_SIZE] = '\0';
    len = strlen(gwy_enum_to_string(version, versions, G_N_ELEMENTS(versions)));
    for (p = head + len; g_ascii_isspace(*p); p++)
        ;

    /* Lots of lines contain just an equal sign, make them comments. */
    gwy_clear1(parser);
    parser.comment_prefix = "=";
    parser.key_value_separator = "=";
    hash = gwy_text_header_parse(p, &parser, NULL, NULL);

    offset = first_channel_offset(version);
    if (size < offset) {
        err_TOO_SHORT(error);
        goto fail;
    }

    HASH_INT2("Channels", "Channels / Channels", nchannels, error);
    gwy_debug("nchannels: %u", nchannels);

    if (version == CREATEC_2Z) {
        guint bpp = channel_bpp(version);
        guint xres, yres;
        gsize comp_size = size - offset;

        HASH_INT2("Num.X", "Num.X / Num.X", xres, error);
        HASH_INT2("Num.Y", "Num.Y / Num.Y", yres, error);

        datasize = nchannels*bpp*xres*yres + 4;
        if (!(zbuffer = gwyzlib_unpack_compressed_data(buffer + offset, &comp_size, NULL, &datasize, error)))
            goto fail;
        imagedata = zbuffer;
        offset = 4;   /* the usual 4 unused bytes */
    }
    else {
        imagedata = buffer;
        datasize = size;
    }

    channelbit = 1;
    channelselect = 0;
    if ((s = g_hash_table_lookup(hash, "Channelselectval / Channelselectval")))
        channelselect = atoi(s);

    for (id = 0; id < nchannels; id++) {
        while (channelselect && channelbit && !(channelbit & channelselect))
            channelbit <<= 1;
        if (!channelselect || !channelbit)
            channelbit = 1;

        dfield = hash_to_field(hash, version, channelbit, imagedata, datasize, &offset, error);
        if (!dfield)
            break;

        if (!file)
            file = gwy_file_new_in_construction();

        gwy_file_pass_image(file, id, dfield);
        title = gwy_enum_to_string(channelbit, channel_names, G_N_ELEMENTS(channel_names));
        if (strlen(title))
            gwy_file_set_title(file, GWY_FILE_IMAGE, id, title, FALSE);
        else
            gwy_image_title_fall_back(file, id);

        if ((meta = get_metadata(hash)))
            gwy_file_pass_meta(file, GWY_FILE_IMAGE, id, meta);

        gwy_log_add_import(file, GWY_FILE_IMAGE, id, NULL, filename);

        channelbit <<= 1;
    }

fail:
    /* Must not free earlier, it holds the hash's strings */
    g_free(head);
    g_free(zbuffer);
    if (hash)
        g_hash_table_destroy(hash);
    gwy_unmap_file_contents(buffer);

    return file;
}

static CreatecVersion
createc_get_version(const gchar *buffer,
                    gsize size)
{
    guint i, len;

    for (i = 0; i < G_N_ELEMENTS(versions); i++) {
        len = strlen(versions[i].name);
        if (size > len && memcmp(versions[i].name, buffer, len) == 0) {
            gwy_debug("header=%s, version=%u",
                      versions[i].name, versions[i].value);
            return versions[i].value;
        }
    }
    gwy_debug("header=%.*s, no version matched",
              (int)strlen(versions[0].name), buffer);
    return CREATEC_NONE;
}

static gsize
first_channel_offset(gint version)
{
    if (version == CREATEC_1)
        return 16384 + 2; /* header + 2 unused bytes */
    if (version == CREATEC_2)
        return 16384 + 4; /* header + 4 unused bytes */
    if (version == CREATEC_2Z)
        return 16384; /* header */
    g_return_val_if_reached(16384);
}

static guint
channel_bpp(gint version)
{
    if (version == CREATEC_1)
        return 2;
    if (version == CREATEC_2 || version == CREATEC_2Z)
        return 4;
    g_return_val_if_reached(4);
}

static GwyField*
hash_to_field(GHashTable *hash,
              gint version,
              guint channelbit,
              const gchar *buffer,
              gsize size,
              gsize *offset,
              GError **error)
{
    static const GwyEnum channel_units[] = {
        /* FIXME: I am just putting "V" everywhere I am not sure what the instrument actually does. */
        { "m",       CHANNEL_TOPOGRAPHY, },
        { "A",       CHANNEL_CURRENT,    },
        { "V",       CHANNEL_ADC1,       },
        { "V",       CHANNEL_ADC2,       },
        { "V",       CHANNEL_ADC3,       },
        { "A/V",     CHANNEL_DI_DV,      },
        { "A^2/V^2", CHANNEL_D2I_DV,     },
        { "V",       CHANNEL_DF,         },
        { "V",       CHANNEL_DAMPING,    },
        { "V",       CHANNEL_AMPLITUDE,  },
        { "V",       CHANNEL_CP,         },
        { "V",       CHANNEL_AUX1,       },
        /* Fallback, usually right. */
        { "V",       0,                  },
    };

    GwyField *dfield = NULL;
    const gchar *zunitstr;
    const gchar *s; /* for HASH_GET macros */
    guint xres, yres, bpp;
    gchar *imagedata = NULL;
    gdouble xreal, yreal, q = 1.0;
    gdouble *data;
    guint dacbits;
    gint ti1, ti2; /* temporary storage */
    gdouble td; /* temporary storage */

    if (!hash)
        return NULL;

    bpp = channel_bpp(version);
    HASH_INT("DAC-Type", dacbits, error);

    HASH_INT2("Num.X", "Num.X / Num.X", xres, error);
    HASH_INT2("Num.Y", "Num.Y / Num.Y", yres, error);
    if (err_DIMENSION(error, xres) || err_DIMENSION(error, yres))
        return NULL;

    if (err_SIZE_MISMATCH(error, *offset + bpp*xres*yres, size, FALSE))
        goto fail;

    HASH_INT2("Delta X", "Delta X / Delta X [Dac]", ti1, error);
    HASH_INT2("GainX", "GainX / GainX", ti2, error);
    HASH_DOUBLE("Xpiezoconst", td, error); /* lowcase p, why? */
    xreal = xres * ti1; /* dacs */
    xreal *= 20.0/gwy_powi(2.0, dacbits) * ti2; /* voltage per dac */
    xreal *= Angstrom * td; /* piezoconstant [A/V] */
    sanitise_real_size(&xreal, "x size");

    HASH_INT2("Delta Y", "Delta Y / Delta Y [Dac]", ti1, error);
    HASH_INT2("GainY", "GainY / GainY", ti2, error);
    HASH_DOUBLE("YPiezoconst", td, error); /* upcase P */
    yreal = yres * ti1;
    yreal *= 20.0/gwy_powi(2.0, dacbits) * ti2;
    yreal *= Angstrom * td;
    sanitise_real_size(&yreal, "y size");

    HASH_INT2("GainZ", "GainZ / GainZ", ti2, error);
    HASH_DOUBLE("ZPiezoconst", td, error); /* upcase P */
    q = 1.0; /* unity dac */
    q *= 20.0/gwy_powi(2.0, dacbits) * ti2; /* voltage per dac */
    q *= Angstrom * td; /* piezoconstant [A/V] */

    dfield = gwy_field_new(xres, yres, xreal, yreal, FALSE);
    data = gwy_field_get_data(dfield);
    read_binary_data(xres*yres, data, buffer + *offset, bpp);
    *offset += bpp*xres*yres;
    gwy_field_multiply(dfield, q);

    gwy_unit_set_from_string(gwy_field_get_unit_xy(dfield), "m");
    zunitstr = gwy_enum_to_string(channelbit, channel_units, G_N_ELEMENTS(channel_units));
    gwy_unit_set_from_string(gwy_field_get_unit_z(dfield), strlen(zunitstr) ? zunitstr : "V");

fail:
    g_free(imagedata);

    return dfield;
}

/* Macro for storing meta data */

#define HASH_STORE(key) \
    if ((val = g_hash_table_lookup(hash, key))) { \
        gwy_debug("key = %s, val = %s", key, val); \
        gwy_dict_set_string_by_name(meta, key, val); \
    }

static GwyDict*
get_metadata(GHashTable *hash)
{
    /* Relocation-less storage */
    static const gchar tobestored[] =
        "ActGainXYZ\0"
        "AproBurst\0"
        "AproPeriod\0"
        "BiasVoltage / BiasVolt.[mV]\0"
        "BiasVoltage\0"
        "Biasvolt[mV]\0"
        "CHModeBias[mV] / CHModeBias[mV]\0"
        "CHModeGainpreamp / CHModeGainpreamp\0"
        "CHModeZoff / CHModeZoff\0"
        "CP_V_Limit\0"
        "Chan(1,2,4) / Chan(1,2,4)\0"
        "Chan(1,2,4)\0"
        "Channels / Channels\0"
        "Channels\0"
        "Channelselectval / Channelselectval\0"
        "CurrentRC\0"
        "Current[A]\0"
        "D-DeltaX / D-DeltaX\0"
        "D-DeltaX\0"
        "DAC-Type\0"
        "DAC5[V]\0"
        "DSP_Clock\0"
        "DX_DIV_DDelta-X / DX/DDeltaX\0"
        "Dacto[A]xy\0"
        "Dacto[A]z\0"
        "Dactonmx\0"
        "Dactonmz\0"
        "Delay X+ / Delay X+\0"
        "Delay X+\0"
        "Delay X- / Delay X-\0"
        "Delay X-\0"
        "Delay Y / Delay Y\0"
        "Delay Y\0"
        "Delta X / Delta X [Dac]\0"
        "Delta X\0"
        "Delta Y / Delta Y [Dac]\0"
        "Delta Y\0"
        "DigZoomX\0"
        "DigZoomZ\0"
        "DispAutoXmax\0"
        "DispAutoXmin\0"
        "FBBackLogIset\0"
        "FBChannel\0"
        "FBIntegral\0"
        "FBIset\0"
        "FBLingain\0"
        "FBLogIset\0"
        "FBLog\0"
        "FBPropGain\0"
        "FBProp\0"
        "FBRC\0"
        "FBVoltGain\0"
        "FBVoltRC\0"
        "FFTPoints\0"
        "Frameimageoffset\0"
        "GainX / GainX\0"
        "GainX\0"
        "GainY / GainY\0"
        "GainY\0"
        "GainZ / GainZ\0"
        "GainZ\0"
        "Gainpreamp / GainPre 10^\0"
        "Gainpreamp\0"
        "HPIB_Address\0"
        "HP_Ch1\0"
        "HP_Ch2\0"
        "HP_Ch3\0"
        "HP_Ch4\0"
        "ImageAutoMode\0"
        "ImageAutoScale\0"
        "Imagebackoffset\0"
        "Imageframe\0"
        "Imagegrayfactor\0"
        "ImaxZret\0"
        "Imaxcurrent\0"
        "Imaxdelay\0"
        "Latm0Delay\0"
        "LatmResist\0"
        "LatmanVolt\0"
        "Latmanccdz\0"
        "Latmanddx\0"
        "Latmandelay\0"
        "Latmanextension\0"
        "Latmangain\0"
        "Latmanlgi\0"
        "Latmanmode\0"
        "Length x[A]\0"
        "Length y[A]\0"
        "LockinAmpl\0"
        "LockinChannel\0"
        "LockinFreq\0"
        "LockinMode\0"
        "LockinPhase2\0"
        "LockinPhase\0"
        "LockinRC\0"
        "LockinRefAmpl\0"
        "LockinRefPhase\0"
        "MVolt_1 / MVolt_1\0"
        "MVolt_10 / MVolt_10\0"
        "MVolt_2 / MVolt_2\0"
        "MVolt_3 / MVolt_3\0"
        "MVolt_4 / MVolt_4\0"
        "MVolt_5 / MVolt_5\0"
        "MVolt_6 / MVolt_6\0"
        "MVolt_7 / MVolt_7\0"
        "MVolt_8 / MVolt_8\0"
        "MVolt_9 / MVolt_9\0"
        "Mcurrent_1 / Mcurrent_1\0"
        "Mcurrent_10 / Mcurrent_10\0"
        "Mcurrent_2 / Mcurrent_2\0"
        "Mcurrent_3 / Mcurrent_3\0"
        "Mcurrent_4 / Mcurrent_4\0"
        "Mcurrent_5 / Mcurrent_5\0"
        "Mcurrent_6 / Mcurrent_6\0"
        "Mcurrent_7 / Mcurrent_7\0"
        "Mcurrent_8 / Mcurrent_8\0"
        "Mcurrent_9 / Mcurrent_9\0"
        "Num.X / Num.X\0"
        "Num.Y / Num.Y\0"
        "OrgPlanOff\0"
        "OrgPlanX\0"
        "OrgPlanY\0"
        "PLLAmplGain\0"
        "PLLAmplMin\0"
        "PLLAmplPostFilter\0"
        "PLLAmplitude\0"
        "PLLCenterFreq\0"
        "PLLControl\0"
        "PLLFreqOffMax\0"
        "PLLFreqPostFilter\0"
        "PLLGain\0"
        "PLLPhase\0"
        "PLLRC\0"
        "PLLScanDelay\0"
        "PLLScanInc\0"
        "PLLScanStartFreq\0"
        "PLLScanStopFreq\0"
        "PlanDx / PlanDx\0"
        "PlanDy / PlanDy\0"
        "Planavrgnr\0"
        "Plany2\0"
        "Preamptype / Preamptype\0"
        "RepeatRotinc / RepeatRotinc\0"
        "RepeatXoffset / RepeatXoffset\0"
        "RepeatYoffset / RepeatYoffset\0"
        "Repeatcounter / Repeatcounter\0"
        "Repeatinterval / Repeatinterval\0"
        "RotBurst\0"
        "RotCMode / RotCMode\0"
        "Rotation / Rotation\0"
        "Rotation\0"
        "Rotcount\0"
        "Rotinc\0"
        "Rotincquad\0"
        "RptBVoltinc / RptBVoltinc\0"
        "SBC_Clk[MHz]\0"
        "SRS_Frequency\0"
        "SRS_InpGain[V]\0"
        "SRS_InpTimeC[s]\0"
        "SRS_ModVoltage\0"
        "ScanYDirec / ScanYDirec\0"
        "ScanYMode / ScanYMode\0"
        "Scancoarse / Scancoarse\0"
        "Scancoarse\0"
        "Scandvinc[DAC]\0"
        "Scanmode / ScanXMode\0"
        "ScanmodeSine / ScanmodeSine\0"
        "Scanrotoffx / OffsetX\0"
        "Scanrotoffy / OffsetY\0"
        "Scantype / Scantype\0"
        "Scantype\0"
        "Sec/Image:\0"
        "Sec/line:\0"
        "SpecAvrgnr\0"
        "SpecFreq\0"
        "SpecXGrid\0"
        "SpecXYGridDelay\0"
        "SpecYGrid\0"
        "T-STM:\0"
        "T_ADC2[K]\0"
        "T_ADC3[K]\0"
        "TipForm_Volt\0"
        "TipForm_ZOffset\0"
        "TipForm_Z\0"
        "Tip_Delay\0"
        "Tip_Gain\0"
        "Tip_LatDelay\0"
        "Tip_Latddx\0"
        "Titel / Titel\0"
        "Titel\0"
        "Upcount\0"
        "Upinc\0"
        "UserPreampCode\0"
        "VFBMode / VFBMode\0"
        "VertAvrgdelay\0"
        "VertFBLogiset\0"
        "VertFBMode\0"
        "VertLatddx\0"
        "VertLatdelay\0"
        "VertLineCount\0"
        "VertRepeatCounter\0"
        "VertSpecAvrgnr\0"
        "VertSpecBack\0"
        "VertStartdly[ms]\0"
        "Vertchannelselectval\0"
        "Vertmandelay\0"
        "Vertmangain\0"
        "VerttreshImax\0"
        "VerttreshImin\0"
        "Vpoint0.V\0"
        "Vpoint0.t\0"
        "Vpoint1.V\0"
        "Vpoint1.t\0"
        "Vpoint2.V\0"
        "Vpoint2.t\0"
        "Vpoint3.V\0"
        "Vpoint3.t\0"
        "Vpoint4.V\0"
        "Vpoint4.t\0"
        "Vpoint5.V\0"
        "Vpoint5.t\0"
        "Vpoint6.V\0"
        "Vpoint6.t\0"
        "Vpoint7.V\0"
        "Vpoint7.t\0"
        "X-Puls-Count\0"
        "XYBurst\0"
        "Xpiezoconst\0"
        "Y-Puls-Count\0"
        "YPiezoconst\0"
        "Z-Puls-Count\0"
        "ZPiezoconst\0"
        "Zdrift\0"
        "Zoffset\0"
        "Zoom\0"
        "Zpoint0.t\0"
        "Zpoint0.z\0"
        "Zpoint1.t\0"
        "Zpoint1.z\0"
        "Zpoint2.t\0"
        "Zpoint2.z\0"
        "Zpoint3.t\0"
        "Zpoint3.z\0"
        "Zpoint4.t\0"
        "Zpoint4.z\0"
        "Zpoint5.t\0"
        "Zpoint5.z\0"
        "Zpoint6.t\0"
        "Zpoint6.z\0"
        "Zpoint7.t\0"
        "Zpoint7.z\0"
        "memo:0\0"
        "memo:1\0"
        "memo:2\0"
        "\0";
    GwyDict *meta;
    const gchar *ctr;
    gchar *val;

    meta = gwy_dict_new_in_construction();

    for (ctr = tobestored; *ctr; ctr += strlen(ctr) + 1)
        HASH_STORE(ctr);

    if (!gwy_dict_get_n_items(meta)) {
        g_object_unref(meta);
        meta = NULL;
    }

    return meta;
}

static void
read_binary_data(gint n,
                 gdouble *data,
                 const gchar *buffer,
                 gint bpp)
{
    if (bpp == 2)
        gwy_convert_raw_data(buffer, n, 1, GWY_RAW_DATA_SINT16, GWY_BYTE_ORDER_LITTLE_ENDIAN,
                             data, gwy_powi(1.0/256.0, bpp), 0.0);
    else if (bpp == 4)
        gwy_convert_raw_data(buffer, n, 1, GWY_RAW_DATA_FLOAT, GWY_BYTE_ORDER_LITTLE_ENDIAN, data, 1.0, 0.0);
    else {
        g_return_if_reached();
    }
}

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
