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

wavedrom / bitfield / 4380763497

pending completion
4380763497

push

github

GitHub
Merge pull request #53 from amiq-eda/fix_no_gap_for_single_bit

90 of 210 branches covered (42.86%)

Branch coverage included in aggregate %.

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

157 of 255 relevant lines covered (61.57%)

13.2 hits per line

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

52.92
/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 skipField = (desc, opt, globalIndex) => {
1✔
237
  if (!opt.compact) {
×
238
    return false;
×
239
  }
240

241
  const emptyField = (e) => e.name === undefined && e.type === undefined;
×
242
  if (desc.findIndex((e) => emptyField(e) && globalIndex > e.lsb && globalIndex <= e.msb + 1) !== -1) {
×
243
    return true;
×
244
  }
245

246
  return false;
×
247
};
248

249
const cage = (desc, opt) => {
1✔
250
  const {hspace, vspace, mod, margin, index, vflip} = opt;
2✔
251
  const width = hspace - margin.left - margin.right - 1;
2✔
252
  const height = vspace - margin.top - margin.bottom;
2✔
253
  const res = ['g',
2✔
254
    {
255
      stroke: 'black',
256
      'stroke-width': 1,
257
      'stroke-linecap': 'round'
258
    }
259
  ];
260
  if (opt.sparse) {
2!
261
    const skipEdge = opt.uneven && (opt.bits % 2 === 1) && (index === (opt.lanes - 1));
×
262
    if (skipEdge) {
×
263
      if (vflip) {
×
264
        res.push(
×
265
          hline(width - (width / mod), 0, 0),
266
          hline(width - (width / mod), 0, height)
267
        );
268
      } else {
269
        res.push(
×
270
          hline(width - (width / mod), width / mod, 0),
271
          hline(width - (width / mod), width / mod, height)
272
        );
273
      }
274
    } else if (!opt.compact) {
×
275
      res.push(
×
276
        hline(width, 0, 0),
277
        hline(width, 0, height),
278
        vline(height, (vflip ? width : 0), 0)
×
279
      );
280
    }
281
  } else {
282
    res.push(
2✔
283
      hline(width, 0, 0),
284
      vline(height, (vflip ? width : 0), 0),
2!
285
      hline(width, 0, height)
286
    );
287
  }
288

289
  let i = index * mod;
2✔
290
  const delta = vflip ? 1 : -1;
2!
291
  let j = vflip ? 0 : mod;
2!
292

293
  if (opt.sparse) {
2!
294
    for (let k = 0; k <= mod; k++) {
×
295

296
      const xj = j * (width / mod);
×
297

298
      if ((!skipField(desc, opt, i) && k !== 0) || (!skipField(desc, opt, i + 1) && k !== mod)) {
×
299
        if ((k === 0) || (k === mod) || desc.some(e => (e.msb + 1 === i))) {
×
300
          res.push(vline(height, xj, 0));
×
301
        } else {
302
          res.push(vline((height >>> 3), xj, 0));
×
303
          res.push(vline(-(height >>> 3), xj, height));
×
304
        }
305
      }
306

307
      if (opt.compact && k !== 0 && !skipField(desc, opt, i)) {
×
308
        res.push(hline(width / mod, xj, 0));
×
309
        res.push(hline(width / mod, xj, height));
×
310
      }
311
      i++;
×
312
      j += delta;
×
313
    }
314
  } else {
315
    for (let k = 0; k < mod; k++) {
2✔
316
      const xj = j * (width / mod);
64✔
317
      if ((k === 0) || desc.some(e => (e.lsb === i))) {
233✔
318
        res.push(vline(height, xj, 0));
8✔
319
      } else {
320
        res.push(
56✔
321
          vline((height >>> 3), xj, 0),
322
          vline(-(height >>> 3), xj, height)
323
        );
324
      }
325
      i++;
64✔
326
      j += delta;
64✔
327
    }
328
  }
329
  return res;
2✔
330
} /* eslint complexity: [1, 23] */;
331

332
const lane = (desc, opt) => {
1✔
333
  const {index, vspace, hspace, margin, hflip, lanes, compact, label} = opt;
2✔
334
  const height = vspace - margin.top - margin.bottom;
2✔
335
  const width = hspace - margin.left - margin.right - 1;
2✔
336

337
  let tx = margin.left;
2✔
338
  const idx = hflip ? index : (lanes - index - 1);
2!
339
  let ty = round(idx * vspace + margin.top);
2✔
340
  if (compact) {
2!
341
    ty = round(idx * height + margin.top);
×
342
  }
343
  const res = ['g',
2✔
344
    tt(tx, ty),
345
    cage(desc, opt),
346
    labelArr(desc, opt)
347
  ];
348

349
  if (label && label.left !== undefined) {
2!
350
    const lab = label.left;
×
351
    let txt = index;
×
352
    if (typeof lab === 'string') {
×
353
      txt = lab;
×
354
    } else
355
    if (typeof lab === 'number') {
×
356
      txt += lab;
×
357
    } else
358
    if (typeof lab === 'object') {
×
359
      txt = lab[index] || txt;
×
360
    }
361
    res.push(['g', {'text-anchor': 'end'},
×
362
      text(txt, -4, round(height / 2))
363
    ]);
364
  }
365

366
  if (label && label.right !== undefined) {
2!
367
    const lab = label.right;
×
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': 'start'},
×
379
      text(txt, width + 4, round(height / 2))
380
    ]);
381
  }
382

383
  return res;
2✔
384
};
385

386
// Maximum number of attributes across all fields
387
const getMaxAttributes = desc =>
1✔
388
  desc.reduce((prev, field) =>
2✔
389
    Math.max(
8✔
390
      prev,
391
      (field.attr === undefined)
8✔
392
        ? 0
393
        : Array.isArray(field.attr)
4!
394
          ? field.attr.length
395
          : 1
396
    ),
397
  0);
398

399
const getTotalBits = desc =>
1✔
400
  desc.reduce((prev, field) => prev + ((field.bits === undefined) ? 0 : field.bits), 0);
8!
401

402
const isIntGTorDefault = opt => row => {
2✔
403
  const [key, min, def] = row;
8✔
404
  const val = Math.round(opt[key]);
8✔
405
  opt[key] = (typeof val === 'number' && val >= min) ? val : def;
8✔
406
};
407

408
const optDefaults = opt => {
1✔
409
  opt = (typeof opt === 'object') ? opt : {};
2✔
410

411
  [ // key         min default
2✔
412
    // ['vspace', 20, 60],
413
    ['hspace', 40, 800],
414
    ['lanes', 1, 1],
415
    ['bits', 1, undefined],
416
    ['fontsize', 6, 14]
417
  ].map(isIntGTorDefault(opt));
418

419
  opt.fontfamily = opt.fontfamily || 'sans-serif';
2✔
420
  opt.fontweight = opt.fontweight || 'normal';
2✔
421
  opt.compact = opt.compact || false;
2✔
422
  opt.hflip = opt.hflip || false;
2✔
423
  opt.uneven = opt.uneven || false;
2✔
424
  opt.margin = opt.margin || {};
2✔
425
  opt.offset = opt.offset || 0;
2✔
426

427
  return opt;
2✔
428
};
429

430
const render = (desc, opt) => {
1✔
431
  opt = optDefaults(opt);
2✔
432

433
  const maxAttributes = getMaxAttributes(desc);
2✔
434

435
  opt.vspace = opt.vspace || ((maxAttributes + 4) * opt.fontsize);
2✔
436

437
  if (opt.bits === undefined) {
2!
438
    opt.bits = getTotalBits(desc);
2✔
439
  }
440

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

443
  if (margin.right === undefined) {
2!
444
    if (label && label.right !== undefined) {
2!
445
      margin.right = round(.1 * hspace);
×
446
    } else {
447
      margin.right = 4;
2✔
448
    }
449
  }
450

451
  if (margin.left === undefined) {
2!
452
    if (label && label.left !== undefined) {
2!
453
      margin.left = round(.1 * hspace);
×
454
    } else {
455
      margin.left = 4; // margin.right;
2✔
456
    }
457
  }
458
  if (margin.top === undefined) {
2!
459
    margin.top = 1.5 * fontsize;
2✔
460
    if (margin.bottom === undefined) {
2!
461
      margin.bottom = fontsize * (maxAttributes) + 4;
2✔
462
    }
463
  } else {
464
    if (margin.bottom === undefined) {
×
465
      margin.bottom = 4;
×
466
    }
467
  }
468

469
  const width = hspace;
2✔
470
  let height = vspace * lanes;
2✔
471
  if (compact) {
2!
472
    height -= (lanes - 1) * (margin.top + margin.bottom);
×
473
  }
474

475
  if (legend) {
2!
476
    height += 12;
×
477
  }
478

479
  const res = ['g',
2✔
480
    tt(0.5, legend ? 12.5 : 0.5, {
2!
481
      'text-anchor': 'middle',
482
      'font-size': opt.fontsize,
483
      'font-family': opt.fontfamily,
484
      'font-weight': opt.fontweight
485
    })
486
  ];
487

488
  let lsb = 0;
2✔
489
  const mod = Math.ceil(bits * 1.0 / lanes);
2✔
490
  opt.mod = mod | 0;
2✔
491

492
  desc.map(e => {
2✔
493
    e.lsb = lsb;
8✔
494
    e.lsbm = lsb % mod;
8✔
495
    lsb += e.bits;
8✔
496
    e.msb = lsb - 1;
8✔
497
    e.msbm = e.msb % mod;
8✔
498
  });
499

500
  for (let i = 0; i < lanes; i++) {
2✔
501
    opt.index = i;
2✔
502
    res.push(lane(desc, opt));
2✔
503
  }
504
  if (compact) {
2!
505
    res.push(compactLabels(desc, opt));
×
506
  }
507

508
  if (legend) {
2!
509
    res.push(getLegendItems(opt));
×
510
  }
511

512
  return getSVG(width, height).concat([res]);
2✔
513
};
514

515
// ----- ✂ ------------------------------------------------------------
516

517
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