• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wavedrom / bitfield / 4380761793

pending completion
4380761793

push

github

GitHub
Merge pull request #54 from amiq-eda/meta_bit_option

90 of 222 branches covered (40.54%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

157 of 263 relevant lines covered (59.7%)

12.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

50.72
/lib/render.js
1
'use strict';
2

3
const tspan = require('tspan');
1✔
4

5
// ----- ✂ ------------------------------------------------------------
6

7
const round = Math.round;
1✔
8

9
const getSVG = (w, h) => ['svg', {
2✔
10
  xmlns: 'http://www.w3.org/2000/svg',
11
  // TODO link ns?
12
  width: w,
13
  height: h,
14
  viewBox: [0, 0, w, h].join(' ')
15
}];
16

17
const tt = (x, y, obj) => Object.assign(
38✔
18
  {transform: 'translate(' + x + (y ? (',' + y) : '') + ')'},
38✔
19
  (typeof obj === 'object') ? obj : {}
38✔
20
);
21

22
const colors = { // TODO compare with WaveDrom
1✔
23
  2: 0,
24
  3: 80,
25
  4: 170,
26
  5: 45,
27
  6: 126,
28
  7: 215
29
};
30

31
const typeStyle = t => (colors[t] !== undefined)
3✔
32
  ? ';fill:hsl(' + colors[t] + ',100%,50%)'
33
  : '';
34

35
const norm = (obj, other) => Object.assign(
129✔
36
  Object
37
    .keys(obj)
38
    .reduce((prev, key) => {
39
      const val = Number(obj[key]);
513✔
40
      const valInt = isNaN(val) ? 0 : Math.round(val);
513!
41
      if (valInt !== 0) { prev[key] = valInt; }
513✔
42
      return prev;
513✔
43
    }, {}),
44
  other
45
);
46

47
const trimText = (text, availableSpace, charWidth) => {
1✔
48
  if (!(typeof text === 'string' || text instanceof String))
×
49
    return text;
×
50

51
  const textWidth = text.length * charWidth;
×
52
  if (textWidth <= availableSpace)
×
53
    return text;
×
54

55
  var end = text.length - ((textWidth - availableSpace) / charWidth) - 3;
×
56
  if (end > 0)
×
57
    return text.substring(0, round(end)) + '...';
×
58
  return text.substring(0, 1) + '...';
×
59
};
60

61
const text = (body, x, y, rotate) => {
1✔
62
  const props = {y: 6};
28✔
63
  if (rotate !== undefined) {
28✔
64
    props.transform = 'rotate(' + rotate + ')';
1✔
65
  }
66
  return ['g', tt(round(x), round(y)), ['text', props].concat(tspan.parse(body))];
28✔
67
};
68

69
const hline = (len, x, y) => ['line', norm({x1: x, x2: x + len, y1: y, y2: y})];
4✔
70
const vline = (len, x, y) => ['line', norm({x1: x, x2: x, y1: y, y2: y + len})];
122✔
71

72
const getLabel = (val, x, y, step, len, rotate) => {
1✔
73
  if (typeof val !== 'number') {
10✔
74
    return text(val, x, y, rotate);
9✔
75
  }
76
  const res = ['g', {}];
1✔
77
  for (let i = 0; i < len; i++) {
1✔
78
    res.push(text(
3✔
79
      (val >> i) & 1,
80
      x + step * (len / 2 - i - 0.5),
81
      y
82
    ));
83
  }
84
  return res;
1✔
85
};
86

87
const getAttr = (e, opt, step, lsbm, msbm) => {
1✔
88
  const x = opt.vflip
4!
89
    ? step * ((msbm + lsbm) / 2)
90
    : step * (opt.mod - ((msbm + lsbm) / 2) - 1);
91

92
  if (!Array.isArray(e.attr)) {
4!
93
    return getLabel(e.attr, x, 0, step, e.bits);
4✔
94
  }
95
  return e.attr.reduce((prev, a, i) =>
×
96
    (a === undefined || a === null)
×
97
      ? prev
98
      : prev.concat([getLabel(a, x, opt.fontsize * i, step, e.bits)]),
99
  ['g', {}]);
100
};
101

102
const labelArr = (desc, opt) => {
1✔
103
  const {margin, hspace, vspace, mod, index, fontsize, vflip, trim, compact, offset} = opt;
2✔
104
  const width = hspace - margin.left - margin.right - 1;
2✔
105
  const height = vspace - margin.top - margin.bottom;
2✔
106
  const step = width / mod;
2✔
107
  const blanks = ['g'];
2✔
108
  const bits = ['g', tt(round(step / 2), -round(0.5 * fontsize + 4))];
2✔
109
  const names = ['g', tt(round(step / 2), round(0.5 * height + 0.4 * fontsize - 6))];
2✔
110
  const attrs = ['g', tt(round(step / 2), round(height + 0.7 * fontsize - 2))];
2✔
111
  desc.map(e => {
2✔
112
    let lsbm = 0;
8✔
113
    let msbm = mod - 1;
8✔
114
    let lsb = index * mod;
8✔
115
    let msb = (index + 1) * mod - 1;
8✔
116
    if (((e.lsb / mod) >> 0) === index) {
8!
117
      lsbm = e.lsbm;
8✔
118
      lsb = e.lsb;
8✔
119
      if (((e.msb / mod) >> 0) === index) {
8!
120
        msb = e.msb;
8✔
121
        msbm = e.msbm;
8✔
122
      }
123
    } else {
124
      if (((e.msb / mod) >> 0) === index) {
×
125
        msb = e.msb;
×
126
        msbm = e.msbm;
×
127
      } else if (!(lsb > e.lsb && msb < e.msb)) {
×
128
        return;
×
129
      }
130
    }
131
    if (!compact) {
8!
132
      bits.push(text(lsb + offset, step * (vflip ? lsbm : (mod - lsbm - 1))));
8!
133
      if (lsbm !== msbm) {
8!
134
        bits.push(text(msb + offset, step * (vflip ? msbm : (mod - msbm - 1))));
8!
135
      }
136
    }
137
    if (e.name !== undefined) {
8✔
138
      names.push(getLabel(
6✔
139
        trim ? trimText(e.name, step * e.bits, trim) : e.name,
6!
140
        step * (vflip
6!
141
          ? ((msbm + lsbm) / 2)
142
          : (mod - ((msbm + lsbm) / 2) - 1)
143
        ),
144
        0,
145
        step,
146
        e.bits,
147
        e.rotate
148
      ));
149
    }
150

151
    if ((e.name === undefined) || (e.type !== undefined)) {
8✔
152
      if (!(opt.compact && e.type === undefined)) {
3!
153
        blanks.push(['rect', Object.assign({}, norm({
3✔
154
          x: step * (vflip ? lsbm : (mod - msbm - 1)),
3!
155
          width: step * (msbm - lsbm + 1),
156
          height: height
157
        }, {
158
          field: e.name,
159
          style: 'fill-opacity:0.1' + typeStyle(e.type)
160
        }), 
161
        e.rect !== undefined ? e.rect : {}
3!
162
        )]);
163
      }
164
    }
165
    if (e.attr !== undefined) {
8✔
166
      attrs.push(getAttr(e, opt, step, lsbm, msbm));
4✔
167
    }
168
  });
169
  return ['g', blanks, bits, names, attrs];
2✔
170
};
171

172
const getLabelMask = (desc, mod) => {
1✔
173
  const mask = [];
×
174
  let idx = 0;
×
175
  desc.map(e => {
×
176
    mask[idx % mod] = true;
×
177
    idx += e.bits;
×
178
    mask[(idx - 1) % mod] = true;
×
179
  });
180
  return mask;
×
181
};
182

183
const getLegendItems = (opt) => {
1✔
184
  const {hspace, margin, fontsize, legend} = opt;
×
185
  const width = hspace - margin.left - margin.right - 1;
×
186
  const items = ['g', tt(margin.left, -10)];
×
187
  const legendSquarePadding = 36;
×
188
  const legendNamePadding = 24;
×
189

190
  let x = width / 2 - Object.keys(legend).length / 2 * (legendSquarePadding + legendNamePadding);
×
191
  for(const key in legend) {
×
192
    const value = legend[key];
×
193

194
    items.push(['rect', norm({
×
195
      x: x,
196
      width: 12,
197
      height: 12
198
    }, {
199
      style: 'fill-opacity:0.15; stroke: #000; stroke-width: 1.2;' + typeStyle(value)
200
    })]);
201

202
    x += legendSquarePadding;
×
203
    items.push(text(
×
204
      key,
205
      x,
206
      0.1 * fontsize + 4
207
    ));
208
    x += legendNamePadding;
×
209
  }
210

211
  return items;
×
212
};
213

214
const compactLabels = (desc, opt) => {
1✔
215
  const {hspace, margin, mod, fontsize, vflip, legend, offset} = opt;
×
216
  const width = hspace - margin.left - margin.right - 1;
×
217
  const step = width / mod;
×
218
  const labels = ['g', tt(margin.left, legend ? 0 : -3)];
×
219

220
  const mask = getLabelMask(desc, mod);
×
221

222
  for (let i = 0; i < mod; i++) {
×
223
    const idx = vflip ? i : (mod - i - 1);
×
224
    if (mask[idx]) {
×
225
      labels.push(text(
×
226
        idx + offset,
227
        step * (i + .5),
228
        0.5 * fontsize + 4
229
      ));
230
    }
231
  }
232

233
  return labels;
×
234
};
235

236
const skipDrawingEmptySpace = (desc, opt, laneIndex, laneLength, globalIndex) => {
1✔
237
  if (!opt.compact)
×
238
    return false;
×
239

240
  const isEmptyBitfield = (bitfield) => bitfield.name === undefined && bitfield.type === undefined;
×
241
  const bitfieldIndex = desc.findIndex((e) => isEmptyBitfield(e) && globalIndex >= e.lsb && globalIndex <= e.msb + 1);
×
242

243
  if (bitfieldIndex === -1) {
×
244
    return false;
×
245
  }
246

247
  if (globalIndex > desc[bitfieldIndex].lsb && globalIndex < desc[bitfieldIndex].msb + 1) {
×
248
    return true;
×
249
  }
250

251
  if (globalIndex == desc[bitfieldIndex].lsb && (laneIndex === 0 || bitfieldIndex > 0 && isEmptyBitfield(desc[bitfieldIndex - 1]))) {
×
252
    return true;
×
253
  }
254

255
  if (globalIndex == desc[bitfieldIndex].msb + 1 && (laneIndex === laneLength || bitfieldIndex < desc.length - 1 && isEmptyBitfield(desc[bitfieldIndex + 1]))) {
×
256
    return true;
×
257
  }
258

259
  return false;
×
260
};
261

262
const cage = (desc, opt) => {
1✔
263
  const {hspace, vspace, mod, margin, index, vflip} = opt;
2✔
264
  const width = hspace - margin.left - margin.right - 1;
2✔
265
  const height = vspace - margin.top - margin.bottom;
2✔
266
  const res = ['g',
2✔
267
    {
268
      stroke: 'black',
269
      'stroke-width': 1,
270
      'stroke-linecap': 'round'
271
    }
272
  ];
273
  if (opt.sparse) {
2!
274
    const skipEdge = opt.uneven && (opt.bits % 2 === 1) && (index === (opt.lanes - 1));
×
275
    if (skipEdge) {
×
276
      if (vflip) {
×
277
        res.push(
×
278
          hline(width - (width / mod), 0, 0),
279
          hline(width - (width / mod), 0, height)
280
        );
281
      } else {
282
        res.push(
×
283
          hline(width - (width / mod), width / mod, 0),
284
          hline(width - (width / mod), width / mod, height)
285
        );
286
      }
287
    } else if (!opt.compact) {
×
288
      res.push(
×
289
        hline(width, 0, 0),
290
        hline(width, 0, height),
291
        vline(height, (vflip ? width : 0), 0)
×
292
      );
293
    }
294
  } else {
295
    res.push(
2✔
296
      hline(width, 0, 0),
297
      vline(height, (vflip ? width : 0), 0),
2!
298
      hline(width, 0, height)
299
    );
300
  }
301

302
  let i = index * mod;
2✔
303
  const delta = vflip ? 1 : -1;
2!
304
  let j = vflip ? 0 : mod;
2!
305

306
  if (opt.sparse) {
2!
307
    for (let k = 0; k <= mod; k++) {
×
308
      if (skipDrawingEmptySpace(desc, opt, k, mod, i)) {
×
309
        i++;
×
310
        j += delta;
×
311
        continue;
×
312
      }
313
      const xj = j * (width / mod);
×
314
      if ((k === 0) || (k === mod) || desc.some(e => (e.msb + 1 === i))) {
×
315
        res.push(vline(height, xj, 0));
×
316
      } else {
317
        res.push(
×
318
          vline((height >>> 3), xj, 0),
319
          vline(-(height >>> 3), xj, height)
320
        );
321
      }
322
      if (opt.compact && k !== 0 && !skipDrawingEmptySpace(desc, opt, k - 1, mod, i - 1)) {
×
323
        res.push(
×
324
          hline(width / mod, xj, 0),
325
          hline(width / mod, xj, height)
326
        );
327
      }
328
      i++;
×
329
      j += delta;
×
330
    }
331
  } else {
332
    for (let k = 0; k < mod; k++) {
2✔
333
      const xj = j * (width / mod);
64✔
334
      if ((k === 0) || desc.some(e => (e.lsb === i))) {
233✔
335
        res.push(vline(height, xj, 0));
8✔
336
      } else {
337
        res.push(
56✔
338
          vline((height >>> 3), xj, 0),
339
          vline(-(height >>> 3), xj, height)
340
        );
341
      }
342
      i++;
64✔
343
      j += delta;
64✔
344
    }
345
  }
346
  return res;
2✔
347
} /* eslint complexity: [1, 23] */;
348

349
const lane = (desc, opt) => {
1✔
350
  const {index, vspace, hspace, margin, hflip, lanes, compact, label} = opt;
2✔
351
  const height = vspace - margin.top - margin.bottom;
2✔
352
  const width = hspace - margin.left - margin.right - 1;
2✔
353

354
  let tx = margin.left;
2✔
355
  const idx = hflip ? index : (lanes - index - 1);
2!
356
  let ty = round(idx * vspace + margin.top);
2✔
357
  if (compact) {
2!
358
    ty = round(idx * height + margin.top);
×
359
  }
360
  const res = ['g',
2✔
361
    tt(tx, ty),
362
    cage(desc, opt),
363
    labelArr(desc, opt)
364
  ];
365

366
  if (label && label.left !== undefined) {
2!
367
    const lab = label.left;
×
368
    let txt = index;
×
369
    if (typeof lab === 'string') {
×
370
      txt = lab;
×
371
    } else
372
    if (typeof lab === 'number') {
×
373
      txt += lab;
×
374
    } else
375
    if (typeof lab === 'object') {
×
376
      txt = lab[index] || txt;
×
377
    }
378
    res.push(['g', {'text-anchor': 'end'},
×
379
      text(txt, -4, round(height / 2))
380
    ]);
381
  }
382

383
  if (label && label.right !== undefined) {
2!
384
    const lab = label.right;
×
385
    let txt = index;
×
386
    if (typeof lab === 'string') {
×
387
      txt = lab;
×
388
    } else
389
    if (typeof lab === 'number') {
×
390
      txt += lab;
×
391
    } else
392
    if (typeof lab === 'object') {
×
393
      txt = lab[index] || txt;
×
394
    }
395
    res.push(['g', {'text-anchor': 'start'},
×
396
      text(txt, width + 4, round(height / 2))
397
    ]);
398
  }
399

400
  return res;
2✔
401
};
402

403
// Maximum number of attributes across all fields
404
const getMaxAttributes = desc =>
1✔
405
  desc.reduce((prev, field) =>
2✔
406
    Math.max(
8✔
407
      prev,
408
      (field.attr === undefined)
8✔
409
        ? 0
410
        : Array.isArray(field.attr)
4!
411
          ? field.attr.length
412
          : 1
413
    ),
414
  0);
415

416
const getTotalBits = desc =>
1✔
417
  desc.reduce((prev, field) => prev + ((field.bits === undefined) ? 0 : field.bits), 0);
8!
418

419
const isIntGTorDefault = opt => row => {
2✔
420
  const [key, min, def] = row;
8✔
421
  const val = Math.round(opt[key]);
8✔
422
  opt[key] = (typeof val === 'number' && val >= min) ? val : def;
8✔
423
};
424

425
const optDefaults = opt => {
1✔
426
  opt = (typeof opt === 'object') ? opt : {};
2✔
427

428
  [ // key         min default
2✔
429
    // ['vspace', 20, 60],
430
    ['hspace', 40, 800],
431
    ['lanes', 1, 1],
432
    ['bits', 1, undefined],
433
    ['fontsize', 6, 14]
434
  ].map(isIntGTorDefault(opt));
435

436
  opt.fontfamily = opt.fontfamily || 'sans-serif';
2✔
437
  opt.fontweight = opt.fontweight || 'normal';
2✔
438
  opt.compact = opt.compact || false;
2✔
439
  opt.hflip = opt.hflip || false;
2✔
440
  opt.uneven = opt.uneven || false;
2✔
441
  opt.margin = opt.margin || {};
2✔
442
  opt.offset = opt.offset || 0;
2✔
443

444
  return opt;
2✔
445
};
446

447
const render = (desc, opt) => {
1✔
448
  opt = optDefaults(opt);
2✔
449

450
  const maxAttributes = getMaxAttributes(desc);
2✔
451

452
  opt.vspace = opt.vspace || ((maxAttributes + 4) * opt.fontsize);
2✔
453

454
  if (opt.bits === undefined) {
2!
455
    opt.bits = getTotalBits(desc);
2✔
456
  }
457

458
  const {hspace, vspace, lanes, margin, compact, fontsize, bits, label, legend} = opt;
2✔
459

460
  if (margin.right === undefined) {
2!
461
    if (label && label.right !== undefined) {
2!
462
      margin.right = round(.1 * hspace);
×
463
    } else {
464
      margin.right = 4;
2✔
465
    }
466
  }
467

468
  if (margin.left === undefined) {
2!
469
    if (label && label.left !== undefined) {
2!
470
      margin.left = round(.1 * hspace);
×
471
    } else {
472
      margin.left = 4; // margin.right;
2✔
473
    }
474
  }
475
  if (margin.top === undefined) {
2!
476
    margin.top = 1.5 * fontsize;
2✔
477
    if (margin.bottom === undefined) {
2!
478
      margin.bottom = fontsize * (maxAttributes) + 4;
2✔
479
    }
480
  } else {
481
    if (margin.bottom === undefined) {
×
482
      margin.bottom = 4;
×
483
    }
484
  }
485

486
  const width = hspace;
2✔
487
  let height = vspace * lanes;
2✔
488
  if (compact) {
2!
489
    height -= (lanes - 1) * (margin.top + margin.bottom);
×
490
  }
491

492
  if (legend) {
2!
493
    height += 12;
×
494
  }
495

496
  const res = ['g',
2✔
497
    tt(0.5, legend ? 12.5 : 0.5, {
2!
498
      'text-anchor': 'middle',
499
      'font-size': opt.fontsize,
500
      'font-family': opt.fontfamily,
501
      'font-weight': opt.fontweight
502
    })
503
  ];
504

505
  let lsb = 0;
2✔
506
  const mod = Math.ceil(bits * 1.0 / lanes);
2✔
507
  opt.mod = mod | 0;
2✔
508

509
  desc.map(e => {
2✔
510
    e.lsb = lsb;
8✔
511
    e.lsbm = lsb % mod;
8✔
512
    lsb += e.bits;
8✔
513
    e.msb = lsb - 1;
8✔
514
    e.msbm = e.msb % mod;
8✔
515
  });
516

517
  for (let i = 0; i < lanes; i++) {
2✔
518
    opt.index = i;
2✔
519
    res.push(lane(desc, opt));
2✔
520
  }
521
  if (compact) {
2!
522
    res.push(compactLabels(desc, opt));
×
523
  }
524

525
  if (legend) {
2!
526
    res.push(getLegendItems(opt));
×
527
  }
528

529
  return getSVG(width, height).concat([res]);
2✔
530
};
531

532
// ----- ✂ ------------------------------------------------------------
533

534
module.exports = render;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc