pptx-glimpse 0.1.4 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +355 -25
- package/dist/index.js +355 -25
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -2078,14 +2078,16 @@ function measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa) {
|
|
|
2078
2078
|
const codePoint = char.codePointAt(0);
|
|
2079
2079
|
const isEa = isCjkCodePoint(codePoint);
|
|
2080
2080
|
const metrics = isEa && eaMetrics ? eaMetrics : latinMetrics;
|
|
2081
|
+
let charWidth;
|
|
2081
2082
|
if (metrics) {
|
|
2082
|
-
|
|
2083
|
+
charWidth = measureCharMetrics(char, codePoint, baseSizePx, metrics);
|
|
2083
2084
|
} else {
|
|
2084
|
-
|
|
2085
|
+
charWidth = measureCharHeuristic(codePoint, baseSizePx);
|
|
2085
2086
|
}
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2087
|
+
if (bold && !isEa) {
|
|
2088
|
+
charWidth *= BOLD_FACTOR;
|
|
2089
|
+
}
|
|
2090
|
+
totalWidth += charWidth;
|
|
2089
2091
|
}
|
|
2090
2092
|
return totalWidth;
|
|
2091
2093
|
}
|
|
@@ -3135,7 +3137,7 @@ function renderTextDecorations(x, y, segmentWidth, fontSizePx, props) {
|
|
|
3135
3137
|
}
|
|
3136
3138
|
return lines;
|
|
3137
3139
|
}
|
|
3138
|
-
function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, fontResolver) {
|
|
3140
|
+
function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, fontResolver, vert) {
|
|
3139
3141
|
const fontSize = (props.fontSize ?? defaultFontSize) * fontScale;
|
|
3140
3142
|
const fontSizePx = fontSize * PX_PER_PT2;
|
|
3141
3143
|
const parts = [];
|
|
@@ -3168,7 +3170,50 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
|
|
|
3168
3170
|
}
|
|
3169
3171
|
totalWidth += segWidth;
|
|
3170
3172
|
};
|
|
3171
|
-
|
|
3173
|
+
const processCjkUpright = (segText, fontFamily, fontFamilyEa) => {
|
|
3174
|
+
if (segText.length === 0) return;
|
|
3175
|
+
const font = fontResolver.resolveFont(fontFamily, fontFamilyEa);
|
|
3176
|
+
const fillAttrs = buildPathFillAttrs(props);
|
|
3177
|
+
for (const char of segText) {
|
|
3178
|
+
const charWidth = getTextMeasurer().measureTextWidth(
|
|
3179
|
+
char,
|
|
3180
|
+
fontSize,
|
|
3181
|
+
props.bold,
|
|
3182
|
+
fontFamily,
|
|
3183
|
+
fontFamilyEa
|
|
3184
|
+
);
|
|
3185
|
+
if (font) {
|
|
3186
|
+
const charX = x + totalWidth;
|
|
3187
|
+
const path = font.getPath(char, charX, effectiveY, fontSizePx);
|
|
3188
|
+
const pathData = path.toPathData(2);
|
|
3189
|
+
if (pathData && pathData.length > 0) {
|
|
3190
|
+
const cx = charX + charWidth / 2;
|
|
3191
|
+
const cy = effectiveY - fontSizePx * (font.ascender + font.descender) / 2 / font.unitsPerEm;
|
|
3192
|
+
parts.push(
|
|
3193
|
+
`<g transform="rotate(-90, ${cx.toFixed(2)}, ${cy.toFixed(2)})"><path d="${pathData}" ${fillAttrs}/></g>`
|
|
3194
|
+
);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
if (props.underline || props.strikethrough) {
|
|
3198
|
+
parts.push(
|
|
3199
|
+
...renderTextDecorations(x + totalWidth, effectiveY, charWidth, fontSizePx, props)
|
|
3200
|
+
);
|
|
3201
|
+
}
|
|
3202
|
+
totalWidth += charWidth;
|
|
3203
|
+
}
|
|
3204
|
+
};
|
|
3205
|
+
if (vert === "eaVert") {
|
|
3206
|
+
const scriptParts = splitByScript(processedText);
|
|
3207
|
+
for (const part of scriptParts) {
|
|
3208
|
+
const ff = part.isEa ? props.fontFamilyEa ?? props.fontFamily : props.fontFamily;
|
|
3209
|
+
const ffEa = part.isEa ? props.fontFamilyEa : props.fontFamilyEa;
|
|
3210
|
+
if (part.isEa) {
|
|
3211
|
+
processCjkUpright(part.text, ff, ffEa);
|
|
3212
|
+
} else {
|
|
3213
|
+
processSegment(part.text, ff, ffEa);
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
} else if (needsScriptSplit(props)) {
|
|
3172
3217
|
const scriptParts = splitByScript(processedText);
|
|
3173
3218
|
for (const part of scriptParts) {
|
|
3174
3219
|
const ff = part.isEa ? props.fontFamilyEa : props.fontFamily;
|
|
@@ -3334,7 +3379,8 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
|
|
|
3334
3379
|
currentY,
|
|
3335
3380
|
fontScale,
|
|
3336
3381
|
defaultFontSize,
|
|
3337
|
-
fontResolver
|
|
3382
|
+
fontResolver,
|
|
3383
|
+
bodyProperties.vert
|
|
3338
3384
|
);
|
|
3339
3385
|
if (result.svg) elements.push(result.svg);
|
|
3340
3386
|
currentX += result.width;
|
|
@@ -3381,7 +3427,8 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
|
|
|
3381
3427
|
currentY,
|
|
3382
3428
|
fontScale,
|
|
3383
3429
|
defaultFontSize,
|
|
3384
|
-
fontResolver
|
|
3430
|
+
fontResolver,
|
|
3431
|
+
bodyProperties.vert
|
|
3385
3432
|
);
|
|
3386
3433
|
if (result.svg) elements.push(result.svg);
|
|
3387
3434
|
currentX += result.width;
|
|
@@ -3824,6 +3871,15 @@ function renderChart(element) {
|
|
|
3824
3871
|
case "radar":
|
|
3825
3872
|
parts.push(renderRadarChart(chart, plotX, plotY, plotW, plotH));
|
|
3826
3873
|
break;
|
|
3874
|
+
case "stock":
|
|
3875
|
+
parts.push(renderStockChart(chart, plotX, plotY, plotW, plotH));
|
|
3876
|
+
break;
|
|
3877
|
+
case "surface":
|
|
3878
|
+
parts.push(renderSurfaceChart(chart, plotX, plotY, plotW, plotH));
|
|
3879
|
+
break;
|
|
3880
|
+
case "ofPie":
|
|
3881
|
+
parts.push(renderOfPieChart(chart, plotX, plotY, plotW, plotH));
|
|
3882
|
+
break;
|
|
3827
3883
|
}
|
|
3828
3884
|
}
|
|
3829
3885
|
if (chart.legend && chart.series.length > 0) {
|
|
@@ -4195,9 +4251,237 @@ function renderRadarChart(chart, x, y, w, h) {
|
|
|
4195
4251
|
}
|
|
4196
4252
|
return parts.join("");
|
|
4197
4253
|
}
|
|
4254
|
+
function renderStockChart(chart, x, y, w, h) {
|
|
4255
|
+
const parts = [];
|
|
4256
|
+
const { series, categories } = chart;
|
|
4257
|
+
if (series.length < 3) return "";
|
|
4258
|
+
const highSeries = series[0];
|
|
4259
|
+
const lowSeries = series[1];
|
|
4260
|
+
const closeSeries = series[2];
|
|
4261
|
+
const catCount = categories.length || highSeries.values.length;
|
|
4262
|
+
if (catCount === 0) return "";
|
|
4263
|
+
let maxVal = 0;
|
|
4264
|
+
let minVal = Infinity;
|
|
4265
|
+
for (const s of [highSeries, lowSeries, closeSeries]) {
|
|
4266
|
+
for (const v of s.values) {
|
|
4267
|
+
maxVal = Math.max(maxVal, v);
|
|
4268
|
+
minVal = Math.min(minVal, v);
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
if (maxVal === minVal) return "";
|
|
4272
|
+
parts.push(
|
|
4273
|
+
`<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
4274
|
+
);
|
|
4275
|
+
parts.push(
|
|
4276
|
+
`<line x1="${round3(x)}" y1="${round3(y)}" x2="${round3(x)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
4277
|
+
);
|
|
4278
|
+
for (let c = 0; c < catCount; c++) {
|
|
4279
|
+
const label = categories[c] ?? "";
|
|
4280
|
+
const labelX = x + (c + 0.5) * (w / catCount);
|
|
4281
|
+
parts.push(
|
|
4282
|
+
`<text x="${round3(labelX)}" y="${round3(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
|
|
4283
|
+
);
|
|
4284
|
+
}
|
|
4285
|
+
const range = maxVal - minVal;
|
|
4286
|
+
for (let c = 0; c < catCount; c++) {
|
|
4287
|
+
const cx = x + (c + 0.5) * (w / catCount);
|
|
4288
|
+
const highVal = highSeries.values[c] ?? 0;
|
|
4289
|
+
const lowVal = lowSeries.values[c] ?? 0;
|
|
4290
|
+
const closeVal = closeSeries.values[c] ?? 0;
|
|
4291
|
+
const highY = y + h - (highVal - minVal) / range * h;
|
|
4292
|
+
const lowY = y + h - (lowVal - minVal) / range * h;
|
|
4293
|
+
const closeY = y + h - (closeVal - minVal) / range * h;
|
|
4294
|
+
parts.push(
|
|
4295
|
+
`<line x1="${round3(cx)}" y1="${round3(highY)}" x2="${round3(cx)}" y2="${round3(lowY)}" stroke="#404040" stroke-width="2"/>`
|
|
4296
|
+
);
|
|
4297
|
+
const tickW = w / catCount * 0.2;
|
|
4298
|
+
parts.push(
|
|
4299
|
+
`<line x1="${round3(cx)}" y1="${round3(closeY)}" x2="${round3(cx + tickW)}" y2="${round3(closeY)}" stroke="#404040" stroke-width="2"/>`
|
|
4300
|
+
);
|
|
4301
|
+
}
|
|
4302
|
+
return parts.join("");
|
|
4303
|
+
}
|
|
4304
|
+
function renderSurfaceChart(chart, x, y, w, h) {
|
|
4305
|
+
const parts = [];
|
|
4306
|
+
const { series, categories } = chart;
|
|
4307
|
+
if (series.length === 0) return "";
|
|
4308
|
+
const rows = series.length;
|
|
4309
|
+
const cols = categories.length || Math.max(...series.map((s) => s.values.length));
|
|
4310
|
+
if (cols === 0) return "";
|
|
4311
|
+
let minVal = Infinity;
|
|
4312
|
+
let maxVal = -Infinity;
|
|
4313
|
+
for (const s of series) {
|
|
4314
|
+
for (const v of s.values) {
|
|
4315
|
+
minVal = Math.min(minVal, v);
|
|
4316
|
+
maxVal = Math.max(maxVal, v);
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
if (minVal === maxVal) maxVal = minVal + 1;
|
|
4320
|
+
const cellW = w / cols;
|
|
4321
|
+
const cellH = h / rows;
|
|
4322
|
+
for (let r = 0; r < rows; r++) {
|
|
4323
|
+
for (let c = 0; c < cols; c++) {
|
|
4324
|
+
const val = series[r].values[c] ?? 0;
|
|
4325
|
+
const t = (val - minVal) / (maxVal - minVal);
|
|
4326
|
+
const color = heatmapColor(t);
|
|
4327
|
+
const cx = x + c * cellW;
|
|
4328
|
+
const cy = y + r * cellH;
|
|
4329
|
+
parts.push(
|
|
4330
|
+
`<rect x="${round3(cx)}" y="${round3(cy)}" width="${round3(cellW)}" height="${round3(cellH)}" fill="${color}" stroke="#FFFFFF" stroke-width="0.5"/>`
|
|
4331
|
+
);
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
for (let c = 0; c < cols; c++) {
|
|
4335
|
+
const label = categories[c] ?? "";
|
|
4336
|
+
if (label) {
|
|
4337
|
+
const labelX = x + (c + 0.5) * cellW;
|
|
4338
|
+
parts.push(
|
|
4339
|
+
`<text x="${round3(labelX)}" y="${round3(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
|
|
4340
|
+
);
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
for (let r = 0; r < rows; r++) {
|
|
4344
|
+
const label = series[r].name ?? "";
|
|
4345
|
+
if (label) {
|
|
4346
|
+
const labelY = y + (r + 0.5) * cellH;
|
|
4347
|
+
parts.push(
|
|
4348
|
+
`<text x="${round3(x - 5)}" y="${round3(labelY + 4)}" text-anchor="end" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
|
|
4349
|
+
);
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
return parts.join("");
|
|
4353
|
+
}
|
|
4354
|
+
function heatmapColor(t) {
|
|
4355
|
+
const clamped = Math.max(0, Math.min(1, t));
|
|
4356
|
+
let r, g, b;
|
|
4357
|
+
if (clamped < 0.25) {
|
|
4358
|
+
const s = clamped / 0.25;
|
|
4359
|
+
r = 0;
|
|
4360
|
+
g = Math.round(s * 255);
|
|
4361
|
+
b = 255;
|
|
4362
|
+
} else if (clamped < 0.5) {
|
|
4363
|
+
const s = (clamped - 0.25) / 0.25;
|
|
4364
|
+
r = 0;
|
|
4365
|
+
g = 255;
|
|
4366
|
+
b = Math.round((1 - s) * 255);
|
|
4367
|
+
} else if (clamped < 0.75) {
|
|
4368
|
+
const s = (clamped - 0.5) / 0.25;
|
|
4369
|
+
r = Math.round(s * 255);
|
|
4370
|
+
g = 255;
|
|
4371
|
+
b = 0;
|
|
4372
|
+
} else {
|
|
4373
|
+
const s = (clamped - 0.75) / 0.25;
|
|
4374
|
+
r = 255;
|
|
4375
|
+
g = Math.round((1 - s) * 255);
|
|
4376
|
+
b = 0;
|
|
4377
|
+
}
|
|
4378
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
4379
|
+
}
|
|
4380
|
+
function renderOfPieChart(chart, x, y, w, h) {
|
|
4381
|
+
const parts = [];
|
|
4382
|
+
const series = chart.series[0];
|
|
4383
|
+
if (!series || series.values.length === 0) return "";
|
|
4384
|
+
const total = series.values.reduce((sum, v) => sum + v, 0);
|
|
4385
|
+
if (total === 0) return "";
|
|
4386
|
+
const splitPos = chart.splitPos ?? 2;
|
|
4387
|
+
const secondPieSize = chart.secondPieSize ?? 75;
|
|
4388
|
+
const isBarOfPie = chart.ofPieType === "bar";
|
|
4389
|
+
const splitIdx = Math.max(0, series.values.length - splitPos);
|
|
4390
|
+
const primaryValues = series.values.slice(0, splitIdx);
|
|
4391
|
+
const secondaryValues = series.values.slice(splitIdx);
|
|
4392
|
+
const secondaryTotal = secondaryValues.reduce((sum, v) => sum + v, 0);
|
|
4393
|
+
const pieW = w * 0.45;
|
|
4394
|
+
const pieCx = x + pieW / 2;
|
|
4395
|
+
const pieCy = y + h / 2;
|
|
4396
|
+
const pieR = Math.min(pieW, h) / 2 * 0.85;
|
|
4397
|
+
let currentAngle = -Math.PI / 2;
|
|
4398
|
+
for (let i = 0; i < primaryValues.length; i++) {
|
|
4399
|
+
const val = primaryValues[i];
|
|
4400
|
+
const sliceAngle = val / total * 2 * Math.PI;
|
|
4401
|
+
const color = getPieSliceColor(i, chart);
|
|
4402
|
+
const x1 = pieCx + pieR * Math.cos(currentAngle);
|
|
4403
|
+
const y1 = pieCy + pieR * Math.sin(currentAngle);
|
|
4404
|
+
const x2 = pieCx + pieR * Math.cos(currentAngle + sliceAngle);
|
|
4405
|
+
const y2 = pieCy + pieR * Math.sin(currentAngle + sliceAngle);
|
|
4406
|
+
const largeArc = sliceAngle > Math.PI ? 1 : 0;
|
|
4407
|
+
parts.push(
|
|
4408
|
+
`<path d="M${round3(pieCx)},${round3(pieCy)} L${round3(x1)},${round3(y1)} A${round3(pieR)},${round3(pieR)} 0 ${largeArc},1 ${round3(x2)},${round3(y2)} Z" ${fillAttr(color)}/>`
|
|
4409
|
+
);
|
|
4410
|
+
currentAngle += sliceAngle;
|
|
4411
|
+
}
|
|
4412
|
+
const otherAngleStart = currentAngle;
|
|
4413
|
+
const otherSliceAngle = secondaryTotal / total * 2 * Math.PI;
|
|
4414
|
+
const otherColor = { hex: "#D9D9D9", alpha: 1 };
|
|
4415
|
+
if (primaryValues.length === 0 && secondaryValues.length > 0) {
|
|
4416
|
+
parts.push(
|
|
4417
|
+
`<circle cx="${round3(pieCx)}" cy="${round3(pieCy)}" r="${round3(pieR)}" ${fillAttr(otherColor)}/>`
|
|
4418
|
+
);
|
|
4419
|
+
} else if (secondaryTotal > 0) {
|
|
4420
|
+
const x1 = pieCx + pieR * Math.cos(otherAngleStart);
|
|
4421
|
+
const y1 = pieCy + pieR * Math.sin(otherAngleStart);
|
|
4422
|
+
const x2 = pieCx + pieR * Math.cos(otherAngleStart + otherSliceAngle);
|
|
4423
|
+
const y2 = pieCy + pieR * Math.sin(otherAngleStart + otherSliceAngle);
|
|
4424
|
+
const largeArc = otherSliceAngle > Math.PI ? 1 : 0;
|
|
4425
|
+
parts.push(
|
|
4426
|
+
`<path d="M${round3(pieCx)},${round3(pieCy)} L${round3(x1)},${round3(y1)} A${round3(pieR)},${round3(pieR)} 0 ${largeArc},1 ${round3(x2)},${round3(y2)} Z" ${fillAttr(otherColor)}/>`
|
|
4427
|
+
);
|
|
4428
|
+
}
|
|
4429
|
+
const secW = w * 0.25;
|
|
4430
|
+
const secH = h * (secondPieSize / 100) * 0.85;
|
|
4431
|
+
const secX = x + w * 0.65;
|
|
4432
|
+
const secCy = y + h / 2;
|
|
4433
|
+
const lineStartX = pieCx + pieR * Math.cos(otherAngleStart);
|
|
4434
|
+
const lineStartY = pieCy + pieR * Math.sin(otherAngleStart);
|
|
4435
|
+
const lineEndStartX = pieCx + pieR * Math.cos(otherAngleStart + otherSliceAngle);
|
|
4436
|
+
const lineEndStartY = pieCy + pieR * Math.sin(otherAngleStart + otherSliceAngle);
|
|
4437
|
+
parts.push(
|
|
4438
|
+
`<line x1="${round3(lineStartX)}" y1="${round3(lineStartY)}" x2="${round3(secX)}" y2="${round3(secCy - secH / 2)}" stroke="#A6A6A6" stroke-width="1"/>`
|
|
4439
|
+
);
|
|
4440
|
+
parts.push(
|
|
4441
|
+
`<line x1="${round3(lineEndStartX)}" y1="${round3(lineEndStartY)}" x2="${round3(secX)}" y2="${round3(secCy + secH / 2)}" stroke="#A6A6A6" stroke-width="1"/>`
|
|
4442
|
+
);
|
|
4443
|
+
if (isBarOfPie) {
|
|
4444
|
+
let barY = secCy - secH / 2;
|
|
4445
|
+
for (let i = 0; i < secondaryValues.length; i++) {
|
|
4446
|
+
const val = secondaryValues[i];
|
|
4447
|
+
const barH = secondaryTotal > 0 ? val / secondaryTotal * secH : 0;
|
|
4448
|
+
const color = getPieSliceColor(splitIdx + i, chart);
|
|
4449
|
+
parts.push(
|
|
4450
|
+
`<rect x="${round3(secX)}" y="${round3(barY)}" width="${round3(secW)}" height="${round3(barH)}" ${fillAttr(color)}/>`
|
|
4451
|
+
);
|
|
4452
|
+
barY += barH;
|
|
4453
|
+
}
|
|
4454
|
+
} else {
|
|
4455
|
+
const secPieCx = secX + secW / 2;
|
|
4456
|
+
const secR = Math.min(secW, secH) / 2;
|
|
4457
|
+
let secAngle = -Math.PI / 2;
|
|
4458
|
+
if (secondaryValues.length === 1) {
|
|
4459
|
+
const color = getPieSliceColor(splitIdx, chart);
|
|
4460
|
+
parts.push(
|
|
4461
|
+
`<circle cx="${round3(secPieCx)}" cy="${round3(secCy)}" r="${round3(secR)}" ${fillAttr(color)}/>`
|
|
4462
|
+
);
|
|
4463
|
+
} else {
|
|
4464
|
+
for (let i = 0; i < secondaryValues.length; i++) {
|
|
4465
|
+
const val = secondaryValues[i];
|
|
4466
|
+
const sliceAngle = secondaryTotal > 0 ? val / secondaryTotal * 2 * Math.PI : 0;
|
|
4467
|
+
const color = getPieSliceColor(splitIdx + i, chart);
|
|
4468
|
+
const sx1 = secPieCx + secR * Math.cos(secAngle);
|
|
4469
|
+
const sy1 = secCy + secR * Math.sin(secAngle);
|
|
4470
|
+
const sx2 = secPieCx + secR * Math.cos(secAngle + sliceAngle);
|
|
4471
|
+
const sy2 = secCy + secR * Math.sin(secAngle + sliceAngle);
|
|
4472
|
+
const largeArc = sliceAngle > Math.PI ? 1 : 0;
|
|
4473
|
+
parts.push(
|
|
4474
|
+
`<path d="M${round3(secPieCx)},${round3(secCy)} L${round3(sx1)},${round3(sy1)} A${round3(secR)},${round3(secR)} 0 ${largeArc},1 ${round3(sx2)},${round3(sy2)} Z" ${fillAttr(color)}/>`
|
|
4475
|
+
);
|
|
4476
|
+
secAngle += sliceAngle;
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
return parts.join("");
|
|
4481
|
+
}
|
|
4198
4482
|
function renderLegend(chart, chartW, chartH, position) {
|
|
4199
4483
|
const parts = [];
|
|
4200
|
-
const entries2 = chart.chartType === "pie" || chart.chartType === "doughnut" ? chart.categories.map((cat, i) => ({
|
|
4484
|
+
const entries2 = chart.chartType === "pie" || chart.chartType === "doughnut" || chart.chartType === "ofPie" ? chart.categories.map((cat, i) => ({
|
|
4201
4485
|
label: cat,
|
|
4202
4486
|
color: getPieSliceColor(i, chart)
|
|
4203
4487
|
})) : chart.series.map((s, i) => ({
|
|
@@ -5594,7 +5878,11 @@ var CHART_TYPE_MAP = [
|
|
|
5594
5878
|
["bubbleChart", "bubble"],
|
|
5595
5879
|
["areaChart", "area"],
|
|
5596
5880
|
["area3DChart", "area"],
|
|
5597
|
-
["radarChart", "radar"]
|
|
5881
|
+
["radarChart", "radar"],
|
|
5882
|
+
["stockChart", "stock"],
|
|
5883
|
+
["surfaceChart", "surface"],
|
|
5884
|
+
["surface3DChart", "surface"],
|
|
5885
|
+
["ofPieChart", "ofPie"]
|
|
5598
5886
|
];
|
|
5599
5887
|
function parseChart(chartXml, colorResolver) {
|
|
5600
5888
|
const parsed = parseXml(chartXml);
|
|
@@ -5605,7 +5893,17 @@ function parseChart(chartXml, colorResolver) {
|
|
|
5605
5893
|
const plotArea = chart.plotArea;
|
|
5606
5894
|
if (!plotArea) return null;
|
|
5607
5895
|
const title = parseChartTitle(chart.title);
|
|
5608
|
-
const {
|
|
5896
|
+
const {
|
|
5897
|
+
chartType,
|
|
5898
|
+
series,
|
|
5899
|
+
categories,
|
|
5900
|
+
barDirection,
|
|
5901
|
+
holeSize,
|
|
5902
|
+
radarStyle,
|
|
5903
|
+
ofPieType,
|
|
5904
|
+
secondPieSize,
|
|
5905
|
+
splitPos
|
|
5906
|
+
} = parseChartTypeAndData(plotArea, colorResolver);
|
|
5609
5907
|
if (!chartType) return null;
|
|
5610
5908
|
const legend = parseLegend(chart.legend);
|
|
5611
5909
|
return {
|
|
@@ -5616,6 +5914,9 @@ function parseChart(chartXml, colorResolver) {
|
|
|
5616
5914
|
...barDirection !== void 0 && { barDirection },
|
|
5617
5915
|
...holeSize !== void 0 && { holeSize },
|
|
5618
5916
|
...radarStyle !== void 0 && { radarStyle },
|
|
5917
|
+
...ofPieType !== void 0 && { ofPieType },
|
|
5918
|
+
...secondPieSize !== void 0 && { secondPieSize },
|
|
5919
|
+
...splitPos !== void 0 && { splitPos },
|
|
5619
5920
|
legend
|
|
5620
5921
|
};
|
|
5621
5922
|
}
|
|
@@ -5634,15 +5935,23 @@ function parseChartTypeAndData(plotArea, colorResolver) {
|
|
|
5634
5935
|
const holeSize = chartType === "doughnut" ? Number(holeSizeNode?.["@_val"] ?? 50) : void 0;
|
|
5635
5936
|
const radarStyleNode = chartNode.radarStyle;
|
|
5636
5937
|
const radarStyle = chartType === "radar" ? radarStyleNode?.["@_val"] ?? "standard" : void 0;
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5938
|
+
const ofPieTypeNode = chartNode.ofPieType;
|
|
5939
|
+
const ofPieType = chartType === "ofPie" ? ofPieTypeNode?.["@_val"] ?? "pie" : void 0;
|
|
5940
|
+
const secondPieSizeNode = chartNode.secondPieSize;
|
|
5941
|
+
const secondPieSize = chartType === "ofPie" ? Number(secondPieSizeNode?.["@_val"] ?? 75) : void 0;
|
|
5942
|
+
const splitPosNode = chartNode.splitPos;
|
|
5943
|
+
const splitPos = chartType === "ofPie" ? Number(splitPosNode?.["@_val"] ?? 2) : void 0;
|
|
5944
|
+
return {
|
|
5945
|
+
chartType,
|
|
5946
|
+
series,
|
|
5947
|
+
categories,
|
|
5948
|
+
barDirection,
|
|
5949
|
+
holeSize,
|
|
5950
|
+
radarStyle,
|
|
5951
|
+
ofPieType,
|
|
5952
|
+
secondPieSize,
|
|
5953
|
+
splitPos
|
|
5954
|
+
};
|
|
5646
5955
|
}
|
|
5647
5956
|
return { chartType: null, series: [], categories: [] };
|
|
5648
5957
|
}
|
|
@@ -7385,7 +7694,23 @@ function findMatchingPlaceholderStyle(placeholderType, placeholderIdx, styles) {
|
|
|
7385
7694
|
if (byIdx?.lstStyle) return byIdx.lstStyle;
|
|
7386
7695
|
}
|
|
7387
7696
|
const byType = styles.find((s) => s.placeholderType === placeholderType);
|
|
7388
|
-
|
|
7697
|
+
if (byType?.lstStyle) return byType.lstStyle;
|
|
7698
|
+
const fallbackType = getPlaceholderFallbackType(placeholderType);
|
|
7699
|
+
if (fallbackType) {
|
|
7700
|
+
const byFallback = styles.find((s) => s.placeholderType === fallbackType);
|
|
7701
|
+
return byFallback?.lstStyle;
|
|
7702
|
+
}
|
|
7703
|
+
return void 0;
|
|
7704
|
+
}
|
|
7705
|
+
function getPlaceholderFallbackType(type) {
|
|
7706
|
+
switch (type) {
|
|
7707
|
+
case "ctrTitle":
|
|
7708
|
+
return "title";
|
|
7709
|
+
case "subTitle":
|
|
7710
|
+
return "body";
|
|
7711
|
+
default:
|
|
7712
|
+
return void 0;
|
|
7713
|
+
}
|
|
7389
7714
|
}
|
|
7390
7715
|
function getTxStyleForPlaceholder(placeholderType, txStyles) {
|
|
7391
7716
|
if (!txStyles) return void 0;
|
|
@@ -7616,11 +7941,16 @@ var OpentypeTextMeasurer = class {
|
|
|
7616
7941
|
const fontSizePx = fontSizePt * PX_PER_PT3;
|
|
7617
7942
|
const scale = fontSizePx / font.unitsPerEm;
|
|
7618
7943
|
let totalWidth = 0;
|
|
7944
|
+
const chars = [...text];
|
|
7619
7945
|
const glyphs = font.stringToGlyphs(text);
|
|
7620
|
-
for (
|
|
7621
|
-
|
|
7946
|
+
for (let i = 0; i < glyphs.length; i++) {
|
|
7947
|
+
let charWidth = (glyphs[i].advanceWidth ?? font.unitsPerEm * 0.6) * scale;
|
|
7948
|
+
const codePoint = chars[i]?.codePointAt(0);
|
|
7949
|
+
if (bold && (codePoint === void 0 || !isCjkCodePoint(codePoint))) {
|
|
7950
|
+
charWidth *= BOLD_FACTOR2;
|
|
7951
|
+
}
|
|
7952
|
+
totalWidth += charWidth;
|
|
7622
7953
|
}
|
|
7623
|
-
if (bold) totalWidth *= BOLD_FACTOR2;
|
|
7624
7954
|
return totalWidth;
|
|
7625
7955
|
}
|
|
7626
7956
|
getLineHeightRatio(fontFamily, fontFamilyEa) {
|
package/dist/index.js
CHANGED
|
@@ -2035,14 +2035,16 @@ function measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa) {
|
|
|
2035
2035
|
const codePoint = char.codePointAt(0);
|
|
2036
2036
|
const isEa = isCjkCodePoint(codePoint);
|
|
2037
2037
|
const metrics = isEa && eaMetrics ? eaMetrics : latinMetrics;
|
|
2038
|
+
let charWidth;
|
|
2038
2039
|
if (metrics) {
|
|
2039
|
-
|
|
2040
|
+
charWidth = measureCharMetrics(char, codePoint, baseSizePx, metrics);
|
|
2040
2041
|
} else {
|
|
2041
|
-
|
|
2042
|
+
charWidth = measureCharHeuristic(codePoint, baseSizePx);
|
|
2042
2043
|
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2044
|
+
if (bold && !isEa) {
|
|
2045
|
+
charWidth *= BOLD_FACTOR;
|
|
2046
|
+
}
|
|
2047
|
+
totalWidth += charWidth;
|
|
2046
2048
|
}
|
|
2047
2049
|
return totalWidth;
|
|
2048
2050
|
}
|
|
@@ -3092,7 +3094,7 @@ function renderTextDecorations(x, y, segmentWidth, fontSizePx, props) {
|
|
|
3092
3094
|
}
|
|
3093
3095
|
return lines;
|
|
3094
3096
|
}
|
|
3095
|
-
function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, fontResolver) {
|
|
3097
|
+
function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, fontResolver, vert) {
|
|
3096
3098
|
const fontSize = (props.fontSize ?? defaultFontSize) * fontScale;
|
|
3097
3099
|
const fontSizePx = fontSize * PX_PER_PT2;
|
|
3098
3100
|
const parts = [];
|
|
@@ -3125,7 +3127,50 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
|
|
|
3125
3127
|
}
|
|
3126
3128
|
totalWidth += segWidth;
|
|
3127
3129
|
};
|
|
3128
|
-
|
|
3130
|
+
const processCjkUpright = (segText, fontFamily, fontFamilyEa) => {
|
|
3131
|
+
if (segText.length === 0) return;
|
|
3132
|
+
const font = fontResolver.resolveFont(fontFamily, fontFamilyEa);
|
|
3133
|
+
const fillAttrs = buildPathFillAttrs(props);
|
|
3134
|
+
for (const char of segText) {
|
|
3135
|
+
const charWidth = getTextMeasurer().measureTextWidth(
|
|
3136
|
+
char,
|
|
3137
|
+
fontSize,
|
|
3138
|
+
props.bold,
|
|
3139
|
+
fontFamily,
|
|
3140
|
+
fontFamilyEa
|
|
3141
|
+
);
|
|
3142
|
+
if (font) {
|
|
3143
|
+
const charX = x + totalWidth;
|
|
3144
|
+
const path = font.getPath(char, charX, effectiveY, fontSizePx);
|
|
3145
|
+
const pathData = path.toPathData(2);
|
|
3146
|
+
if (pathData && pathData.length > 0) {
|
|
3147
|
+
const cx = charX + charWidth / 2;
|
|
3148
|
+
const cy = effectiveY - fontSizePx * (font.ascender + font.descender) / 2 / font.unitsPerEm;
|
|
3149
|
+
parts.push(
|
|
3150
|
+
`<g transform="rotate(-90, ${cx.toFixed(2)}, ${cy.toFixed(2)})"><path d="${pathData}" ${fillAttrs}/></g>`
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
if (props.underline || props.strikethrough) {
|
|
3155
|
+
parts.push(
|
|
3156
|
+
...renderTextDecorations(x + totalWidth, effectiveY, charWidth, fontSizePx, props)
|
|
3157
|
+
);
|
|
3158
|
+
}
|
|
3159
|
+
totalWidth += charWidth;
|
|
3160
|
+
}
|
|
3161
|
+
};
|
|
3162
|
+
if (vert === "eaVert") {
|
|
3163
|
+
const scriptParts = splitByScript(processedText);
|
|
3164
|
+
for (const part of scriptParts) {
|
|
3165
|
+
const ff = part.isEa ? props.fontFamilyEa ?? props.fontFamily : props.fontFamily;
|
|
3166
|
+
const ffEa = part.isEa ? props.fontFamilyEa : props.fontFamilyEa;
|
|
3167
|
+
if (part.isEa) {
|
|
3168
|
+
processCjkUpright(part.text, ff, ffEa);
|
|
3169
|
+
} else {
|
|
3170
|
+
processSegment(part.text, ff, ffEa);
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
} else if (needsScriptSplit(props)) {
|
|
3129
3174
|
const scriptParts = splitByScript(processedText);
|
|
3130
3175
|
for (const part of scriptParts) {
|
|
3131
3176
|
const ff = part.isEa ? props.fontFamilyEa : props.fontFamily;
|
|
@@ -3291,7 +3336,8 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
|
|
|
3291
3336
|
currentY,
|
|
3292
3337
|
fontScale,
|
|
3293
3338
|
defaultFontSize,
|
|
3294
|
-
fontResolver
|
|
3339
|
+
fontResolver,
|
|
3340
|
+
bodyProperties.vert
|
|
3295
3341
|
);
|
|
3296
3342
|
if (result.svg) elements.push(result.svg);
|
|
3297
3343
|
currentX += result.width;
|
|
@@ -3338,7 +3384,8 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
|
|
|
3338
3384
|
currentY,
|
|
3339
3385
|
fontScale,
|
|
3340
3386
|
defaultFontSize,
|
|
3341
|
-
fontResolver
|
|
3387
|
+
fontResolver,
|
|
3388
|
+
bodyProperties.vert
|
|
3342
3389
|
);
|
|
3343
3390
|
if (result.svg) elements.push(result.svg);
|
|
3344
3391
|
currentX += result.width;
|
|
@@ -3781,6 +3828,15 @@ function renderChart(element) {
|
|
|
3781
3828
|
case "radar":
|
|
3782
3829
|
parts.push(renderRadarChart(chart, plotX, plotY, plotW, plotH));
|
|
3783
3830
|
break;
|
|
3831
|
+
case "stock":
|
|
3832
|
+
parts.push(renderStockChart(chart, plotX, plotY, plotW, plotH));
|
|
3833
|
+
break;
|
|
3834
|
+
case "surface":
|
|
3835
|
+
parts.push(renderSurfaceChart(chart, plotX, plotY, plotW, plotH));
|
|
3836
|
+
break;
|
|
3837
|
+
case "ofPie":
|
|
3838
|
+
parts.push(renderOfPieChart(chart, plotX, plotY, plotW, plotH));
|
|
3839
|
+
break;
|
|
3784
3840
|
}
|
|
3785
3841
|
}
|
|
3786
3842
|
if (chart.legend && chart.series.length > 0) {
|
|
@@ -4152,9 +4208,237 @@ function renderRadarChart(chart, x, y, w, h) {
|
|
|
4152
4208
|
}
|
|
4153
4209
|
return parts.join("");
|
|
4154
4210
|
}
|
|
4211
|
+
function renderStockChart(chart, x, y, w, h) {
|
|
4212
|
+
const parts = [];
|
|
4213
|
+
const { series, categories } = chart;
|
|
4214
|
+
if (series.length < 3) return "";
|
|
4215
|
+
const highSeries = series[0];
|
|
4216
|
+
const lowSeries = series[1];
|
|
4217
|
+
const closeSeries = series[2];
|
|
4218
|
+
const catCount = categories.length || highSeries.values.length;
|
|
4219
|
+
if (catCount === 0) return "";
|
|
4220
|
+
let maxVal = 0;
|
|
4221
|
+
let minVal = Infinity;
|
|
4222
|
+
for (const s of [highSeries, lowSeries, closeSeries]) {
|
|
4223
|
+
for (const v of s.values) {
|
|
4224
|
+
maxVal = Math.max(maxVal, v);
|
|
4225
|
+
minVal = Math.min(minVal, v);
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
if (maxVal === minVal) return "";
|
|
4229
|
+
parts.push(
|
|
4230
|
+
`<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
4231
|
+
);
|
|
4232
|
+
parts.push(
|
|
4233
|
+
`<line x1="${round3(x)}" y1="${round3(y)}" x2="${round3(x)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
|
|
4234
|
+
);
|
|
4235
|
+
for (let c = 0; c < catCount; c++) {
|
|
4236
|
+
const label = categories[c] ?? "";
|
|
4237
|
+
const labelX = x + (c + 0.5) * (w / catCount);
|
|
4238
|
+
parts.push(
|
|
4239
|
+
`<text x="${round3(labelX)}" y="${round3(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
|
|
4240
|
+
);
|
|
4241
|
+
}
|
|
4242
|
+
const range = maxVal - minVal;
|
|
4243
|
+
for (let c = 0; c < catCount; c++) {
|
|
4244
|
+
const cx = x + (c + 0.5) * (w / catCount);
|
|
4245
|
+
const highVal = highSeries.values[c] ?? 0;
|
|
4246
|
+
const lowVal = lowSeries.values[c] ?? 0;
|
|
4247
|
+
const closeVal = closeSeries.values[c] ?? 0;
|
|
4248
|
+
const highY = y + h - (highVal - minVal) / range * h;
|
|
4249
|
+
const lowY = y + h - (lowVal - minVal) / range * h;
|
|
4250
|
+
const closeY = y + h - (closeVal - minVal) / range * h;
|
|
4251
|
+
parts.push(
|
|
4252
|
+
`<line x1="${round3(cx)}" y1="${round3(highY)}" x2="${round3(cx)}" y2="${round3(lowY)}" stroke="#404040" stroke-width="2"/>`
|
|
4253
|
+
);
|
|
4254
|
+
const tickW = w / catCount * 0.2;
|
|
4255
|
+
parts.push(
|
|
4256
|
+
`<line x1="${round3(cx)}" y1="${round3(closeY)}" x2="${round3(cx + tickW)}" y2="${round3(closeY)}" stroke="#404040" stroke-width="2"/>`
|
|
4257
|
+
);
|
|
4258
|
+
}
|
|
4259
|
+
return parts.join("");
|
|
4260
|
+
}
|
|
4261
|
+
function renderSurfaceChart(chart, x, y, w, h) {
|
|
4262
|
+
const parts = [];
|
|
4263
|
+
const { series, categories } = chart;
|
|
4264
|
+
if (series.length === 0) return "";
|
|
4265
|
+
const rows = series.length;
|
|
4266
|
+
const cols = categories.length || Math.max(...series.map((s) => s.values.length));
|
|
4267
|
+
if (cols === 0) return "";
|
|
4268
|
+
let minVal = Infinity;
|
|
4269
|
+
let maxVal = -Infinity;
|
|
4270
|
+
for (const s of series) {
|
|
4271
|
+
for (const v of s.values) {
|
|
4272
|
+
minVal = Math.min(minVal, v);
|
|
4273
|
+
maxVal = Math.max(maxVal, v);
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
if (minVal === maxVal) maxVal = minVal + 1;
|
|
4277
|
+
const cellW = w / cols;
|
|
4278
|
+
const cellH = h / rows;
|
|
4279
|
+
for (let r = 0; r < rows; r++) {
|
|
4280
|
+
for (let c = 0; c < cols; c++) {
|
|
4281
|
+
const val = series[r].values[c] ?? 0;
|
|
4282
|
+
const t = (val - minVal) / (maxVal - minVal);
|
|
4283
|
+
const color = heatmapColor(t);
|
|
4284
|
+
const cx = x + c * cellW;
|
|
4285
|
+
const cy = y + r * cellH;
|
|
4286
|
+
parts.push(
|
|
4287
|
+
`<rect x="${round3(cx)}" y="${round3(cy)}" width="${round3(cellW)}" height="${round3(cellH)}" fill="${color}" stroke="#FFFFFF" stroke-width="0.5"/>`
|
|
4288
|
+
);
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
for (let c = 0; c < cols; c++) {
|
|
4292
|
+
const label = categories[c] ?? "";
|
|
4293
|
+
if (label) {
|
|
4294
|
+
const labelX = x + (c + 0.5) * cellW;
|
|
4295
|
+
parts.push(
|
|
4296
|
+
`<text x="${round3(labelX)}" y="${round3(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
|
|
4297
|
+
);
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
for (let r = 0; r < rows; r++) {
|
|
4301
|
+
const label = series[r].name ?? "";
|
|
4302
|
+
if (label) {
|
|
4303
|
+
const labelY = y + (r + 0.5) * cellH;
|
|
4304
|
+
parts.push(
|
|
4305
|
+
`<text x="${round3(x - 5)}" y="${round3(labelY + 4)}" text-anchor="end" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
|
|
4306
|
+
);
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
return parts.join("");
|
|
4310
|
+
}
|
|
4311
|
+
function heatmapColor(t) {
|
|
4312
|
+
const clamped = Math.max(0, Math.min(1, t));
|
|
4313
|
+
let r, g, b;
|
|
4314
|
+
if (clamped < 0.25) {
|
|
4315
|
+
const s = clamped / 0.25;
|
|
4316
|
+
r = 0;
|
|
4317
|
+
g = Math.round(s * 255);
|
|
4318
|
+
b = 255;
|
|
4319
|
+
} else if (clamped < 0.5) {
|
|
4320
|
+
const s = (clamped - 0.25) / 0.25;
|
|
4321
|
+
r = 0;
|
|
4322
|
+
g = 255;
|
|
4323
|
+
b = Math.round((1 - s) * 255);
|
|
4324
|
+
} else if (clamped < 0.75) {
|
|
4325
|
+
const s = (clamped - 0.5) / 0.25;
|
|
4326
|
+
r = Math.round(s * 255);
|
|
4327
|
+
g = 255;
|
|
4328
|
+
b = 0;
|
|
4329
|
+
} else {
|
|
4330
|
+
const s = (clamped - 0.75) / 0.25;
|
|
4331
|
+
r = 255;
|
|
4332
|
+
g = Math.round((1 - s) * 255);
|
|
4333
|
+
b = 0;
|
|
4334
|
+
}
|
|
4335
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
4336
|
+
}
|
|
4337
|
+
function renderOfPieChart(chart, x, y, w, h) {
|
|
4338
|
+
const parts = [];
|
|
4339
|
+
const series = chart.series[0];
|
|
4340
|
+
if (!series || series.values.length === 0) return "";
|
|
4341
|
+
const total = series.values.reduce((sum, v) => sum + v, 0);
|
|
4342
|
+
if (total === 0) return "";
|
|
4343
|
+
const splitPos = chart.splitPos ?? 2;
|
|
4344
|
+
const secondPieSize = chart.secondPieSize ?? 75;
|
|
4345
|
+
const isBarOfPie = chart.ofPieType === "bar";
|
|
4346
|
+
const splitIdx = Math.max(0, series.values.length - splitPos);
|
|
4347
|
+
const primaryValues = series.values.slice(0, splitIdx);
|
|
4348
|
+
const secondaryValues = series.values.slice(splitIdx);
|
|
4349
|
+
const secondaryTotal = secondaryValues.reduce((sum, v) => sum + v, 0);
|
|
4350
|
+
const pieW = w * 0.45;
|
|
4351
|
+
const pieCx = x + pieW / 2;
|
|
4352
|
+
const pieCy = y + h / 2;
|
|
4353
|
+
const pieR = Math.min(pieW, h) / 2 * 0.85;
|
|
4354
|
+
let currentAngle = -Math.PI / 2;
|
|
4355
|
+
for (let i = 0; i < primaryValues.length; i++) {
|
|
4356
|
+
const val = primaryValues[i];
|
|
4357
|
+
const sliceAngle = val / total * 2 * Math.PI;
|
|
4358
|
+
const color = getPieSliceColor(i, chart);
|
|
4359
|
+
const x1 = pieCx + pieR * Math.cos(currentAngle);
|
|
4360
|
+
const y1 = pieCy + pieR * Math.sin(currentAngle);
|
|
4361
|
+
const x2 = pieCx + pieR * Math.cos(currentAngle + sliceAngle);
|
|
4362
|
+
const y2 = pieCy + pieR * Math.sin(currentAngle + sliceAngle);
|
|
4363
|
+
const largeArc = sliceAngle > Math.PI ? 1 : 0;
|
|
4364
|
+
parts.push(
|
|
4365
|
+
`<path d="M${round3(pieCx)},${round3(pieCy)} L${round3(x1)},${round3(y1)} A${round3(pieR)},${round3(pieR)} 0 ${largeArc},1 ${round3(x2)},${round3(y2)} Z" ${fillAttr(color)}/>`
|
|
4366
|
+
);
|
|
4367
|
+
currentAngle += sliceAngle;
|
|
4368
|
+
}
|
|
4369
|
+
const otherAngleStart = currentAngle;
|
|
4370
|
+
const otherSliceAngle = secondaryTotal / total * 2 * Math.PI;
|
|
4371
|
+
const otherColor = { hex: "#D9D9D9", alpha: 1 };
|
|
4372
|
+
if (primaryValues.length === 0 && secondaryValues.length > 0) {
|
|
4373
|
+
parts.push(
|
|
4374
|
+
`<circle cx="${round3(pieCx)}" cy="${round3(pieCy)}" r="${round3(pieR)}" ${fillAttr(otherColor)}/>`
|
|
4375
|
+
);
|
|
4376
|
+
} else if (secondaryTotal > 0) {
|
|
4377
|
+
const x1 = pieCx + pieR * Math.cos(otherAngleStart);
|
|
4378
|
+
const y1 = pieCy + pieR * Math.sin(otherAngleStart);
|
|
4379
|
+
const x2 = pieCx + pieR * Math.cos(otherAngleStart + otherSliceAngle);
|
|
4380
|
+
const y2 = pieCy + pieR * Math.sin(otherAngleStart + otherSliceAngle);
|
|
4381
|
+
const largeArc = otherSliceAngle > Math.PI ? 1 : 0;
|
|
4382
|
+
parts.push(
|
|
4383
|
+
`<path d="M${round3(pieCx)},${round3(pieCy)} L${round3(x1)},${round3(y1)} A${round3(pieR)},${round3(pieR)} 0 ${largeArc},1 ${round3(x2)},${round3(y2)} Z" ${fillAttr(otherColor)}/>`
|
|
4384
|
+
);
|
|
4385
|
+
}
|
|
4386
|
+
const secW = w * 0.25;
|
|
4387
|
+
const secH = h * (secondPieSize / 100) * 0.85;
|
|
4388
|
+
const secX = x + w * 0.65;
|
|
4389
|
+
const secCy = y + h / 2;
|
|
4390
|
+
const lineStartX = pieCx + pieR * Math.cos(otherAngleStart);
|
|
4391
|
+
const lineStartY = pieCy + pieR * Math.sin(otherAngleStart);
|
|
4392
|
+
const lineEndStartX = pieCx + pieR * Math.cos(otherAngleStart + otherSliceAngle);
|
|
4393
|
+
const lineEndStartY = pieCy + pieR * Math.sin(otherAngleStart + otherSliceAngle);
|
|
4394
|
+
parts.push(
|
|
4395
|
+
`<line x1="${round3(lineStartX)}" y1="${round3(lineStartY)}" x2="${round3(secX)}" y2="${round3(secCy - secH / 2)}" stroke="#A6A6A6" stroke-width="1"/>`
|
|
4396
|
+
);
|
|
4397
|
+
parts.push(
|
|
4398
|
+
`<line x1="${round3(lineEndStartX)}" y1="${round3(lineEndStartY)}" x2="${round3(secX)}" y2="${round3(secCy + secH / 2)}" stroke="#A6A6A6" stroke-width="1"/>`
|
|
4399
|
+
);
|
|
4400
|
+
if (isBarOfPie) {
|
|
4401
|
+
let barY = secCy - secH / 2;
|
|
4402
|
+
for (let i = 0; i < secondaryValues.length; i++) {
|
|
4403
|
+
const val = secondaryValues[i];
|
|
4404
|
+
const barH = secondaryTotal > 0 ? val / secondaryTotal * secH : 0;
|
|
4405
|
+
const color = getPieSliceColor(splitIdx + i, chart);
|
|
4406
|
+
parts.push(
|
|
4407
|
+
`<rect x="${round3(secX)}" y="${round3(barY)}" width="${round3(secW)}" height="${round3(barH)}" ${fillAttr(color)}/>`
|
|
4408
|
+
);
|
|
4409
|
+
barY += barH;
|
|
4410
|
+
}
|
|
4411
|
+
} else {
|
|
4412
|
+
const secPieCx = secX + secW / 2;
|
|
4413
|
+
const secR = Math.min(secW, secH) / 2;
|
|
4414
|
+
let secAngle = -Math.PI / 2;
|
|
4415
|
+
if (secondaryValues.length === 1) {
|
|
4416
|
+
const color = getPieSliceColor(splitIdx, chart);
|
|
4417
|
+
parts.push(
|
|
4418
|
+
`<circle cx="${round3(secPieCx)}" cy="${round3(secCy)}" r="${round3(secR)}" ${fillAttr(color)}/>`
|
|
4419
|
+
);
|
|
4420
|
+
} else {
|
|
4421
|
+
for (let i = 0; i < secondaryValues.length; i++) {
|
|
4422
|
+
const val = secondaryValues[i];
|
|
4423
|
+
const sliceAngle = secondaryTotal > 0 ? val / secondaryTotal * 2 * Math.PI : 0;
|
|
4424
|
+
const color = getPieSliceColor(splitIdx + i, chart);
|
|
4425
|
+
const sx1 = secPieCx + secR * Math.cos(secAngle);
|
|
4426
|
+
const sy1 = secCy + secR * Math.sin(secAngle);
|
|
4427
|
+
const sx2 = secPieCx + secR * Math.cos(secAngle + sliceAngle);
|
|
4428
|
+
const sy2 = secCy + secR * Math.sin(secAngle + sliceAngle);
|
|
4429
|
+
const largeArc = sliceAngle > Math.PI ? 1 : 0;
|
|
4430
|
+
parts.push(
|
|
4431
|
+
`<path d="M${round3(secPieCx)},${round3(secCy)} L${round3(sx1)},${round3(sy1)} A${round3(secR)},${round3(secR)} 0 ${largeArc},1 ${round3(sx2)},${round3(sy2)} Z" ${fillAttr(color)}/>`
|
|
4432
|
+
);
|
|
4433
|
+
secAngle += sliceAngle;
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
return parts.join("");
|
|
4438
|
+
}
|
|
4155
4439
|
function renderLegend(chart, chartW, chartH, position) {
|
|
4156
4440
|
const parts = [];
|
|
4157
|
-
const entries2 = chart.chartType === "pie" || chart.chartType === "doughnut" ? chart.categories.map((cat, i) => ({
|
|
4441
|
+
const entries2 = chart.chartType === "pie" || chart.chartType === "doughnut" || chart.chartType === "ofPie" ? chart.categories.map((cat, i) => ({
|
|
4158
4442
|
label: cat,
|
|
4159
4443
|
color: getPieSliceColor(i, chart)
|
|
4160
4444
|
})) : chart.series.map((s, i) => ({
|
|
@@ -5551,7 +5835,11 @@ var CHART_TYPE_MAP = [
|
|
|
5551
5835
|
["bubbleChart", "bubble"],
|
|
5552
5836
|
["areaChart", "area"],
|
|
5553
5837
|
["area3DChart", "area"],
|
|
5554
|
-
["radarChart", "radar"]
|
|
5838
|
+
["radarChart", "radar"],
|
|
5839
|
+
["stockChart", "stock"],
|
|
5840
|
+
["surfaceChart", "surface"],
|
|
5841
|
+
["surface3DChart", "surface"],
|
|
5842
|
+
["ofPieChart", "ofPie"]
|
|
5555
5843
|
];
|
|
5556
5844
|
function parseChart(chartXml, colorResolver) {
|
|
5557
5845
|
const parsed = parseXml(chartXml);
|
|
@@ -5562,7 +5850,17 @@ function parseChart(chartXml, colorResolver) {
|
|
|
5562
5850
|
const plotArea = chart.plotArea;
|
|
5563
5851
|
if (!plotArea) return null;
|
|
5564
5852
|
const title = parseChartTitle(chart.title);
|
|
5565
|
-
const {
|
|
5853
|
+
const {
|
|
5854
|
+
chartType,
|
|
5855
|
+
series,
|
|
5856
|
+
categories,
|
|
5857
|
+
barDirection,
|
|
5858
|
+
holeSize,
|
|
5859
|
+
radarStyle,
|
|
5860
|
+
ofPieType,
|
|
5861
|
+
secondPieSize,
|
|
5862
|
+
splitPos
|
|
5863
|
+
} = parseChartTypeAndData(plotArea, colorResolver);
|
|
5566
5864
|
if (!chartType) return null;
|
|
5567
5865
|
const legend = parseLegend(chart.legend);
|
|
5568
5866
|
return {
|
|
@@ -5573,6 +5871,9 @@ function parseChart(chartXml, colorResolver) {
|
|
|
5573
5871
|
...barDirection !== void 0 && { barDirection },
|
|
5574
5872
|
...holeSize !== void 0 && { holeSize },
|
|
5575
5873
|
...radarStyle !== void 0 && { radarStyle },
|
|
5874
|
+
...ofPieType !== void 0 && { ofPieType },
|
|
5875
|
+
...secondPieSize !== void 0 && { secondPieSize },
|
|
5876
|
+
...splitPos !== void 0 && { splitPos },
|
|
5576
5877
|
legend
|
|
5577
5878
|
};
|
|
5578
5879
|
}
|
|
@@ -5591,15 +5892,23 @@ function parseChartTypeAndData(plotArea, colorResolver) {
|
|
|
5591
5892
|
const holeSize = chartType === "doughnut" ? Number(holeSizeNode?.["@_val"] ?? 50) : void 0;
|
|
5592
5893
|
const radarStyleNode = chartNode.radarStyle;
|
|
5593
5894
|
const radarStyle = chartType === "radar" ? radarStyleNode?.["@_val"] ?? "standard" : void 0;
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5895
|
+
const ofPieTypeNode = chartNode.ofPieType;
|
|
5896
|
+
const ofPieType = chartType === "ofPie" ? ofPieTypeNode?.["@_val"] ?? "pie" : void 0;
|
|
5897
|
+
const secondPieSizeNode = chartNode.secondPieSize;
|
|
5898
|
+
const secondPieSize = chartType === "ofPie" ? Number(secondPieSizeNode?.["@_val"] ?? 75) : void 0;
|
|
5899
|
+
const splitPosNode = chartNode.splitPos;
|
|
5900
|
+
const splitPos = chartType === "ofPie" ? Number(splitPosNode?.["@_val"] ?? 2) : void 0;
|
|
5901
|
+
return {
|
|
5902
|
+
chartType,
|
|
5903
|
+
series,
|
|
5904
|
+
categories,
|
|
5905
|
+
barDirection,
|
|
5906
|
+
holeSize,
|
|
5907
|
+
radarStyle,
|
|
5908
|
+
ofPieType,
|
|
5909
|
+
secondPieSize,
|
|
5910
|
+
splitPos
|
|
5911
|
+
};
|
|
5603
5912
|
}
|
|
5604
5913
|
return { chartType: null, series: [], categories: [] };
|
|
5605
5914
|
}
|
|
@@ -7342,7 +7651,23 @@ function findMatchingPlaceholderStyle(placeholderType, placeholderIdx, styles) {
|
|
|
7342
7651
|
if (byIdx?.lstStyle) return byIdx.lstStyle;
|
|
7343
7652
|
}
|
|
7344
7653
|
const byType = styles.find((s) => s.placeholderType === placeholderType);
|
|
7345
|
-
|
|
7654
|
+
if (byType?.lstStyle) return byType.lstStyle;
|
|
7655
|
+
const fallbackType = getPlaceholderFallbackType(placeholderType);
|
|
7656
|
+
if (fallbackType) {
|
|
7657
|
+
const byFallback = styles.find((s) => s.placeholderType === fallbackType);
|
|
7658
|
+
return byFallback?.lstStyle;
|
|
7659
|
+
}
|
|
7660
|
+
return void 0;
|
|
7661
|
+
}
|
|
7662
|
+
function getPlaceholderFallbackType(type) {
|
|
7663
|
+
switch (type) {
|
|
7664
|
+
case "ctrTitle":
|
|
7665
|
+
return "title";
|
|
7666
|
+
case "subTitle":
|
|
7667
|
+
return "body";
|
|
7668
|
+
default:
|
|
7669
|
+
return void 0;
|
|
7670
|
+
}
|
|
7346
7671
|
}
|
|
7347
7672
|
function getTxStyleForPlaceholder(placeholderType, txStyles) {
|
|
7348
7673
|
if (!txStyles) return void 0;
|
|
@@ -7573,11 +7898,16 @@ var OpentypeTextMeasurer = class {
|
|
|
7573
7898
|
const fontSizePx = fontSizePt * PX_PER_PT3;
|
|
7574
7899
|
const scale = fontSizePx / font.unitsPerEm;
|
|
7575
7900
|
let totalWidth = 0;
|
|
7901
|
+
const chars = [...text];
|
|
7576
7902
|
const glyphs = font.stringToGlyphs(text);
|
|
7577
|
-
for (
|
|
7578
|
-
|
|
7903
|
+
for (let i = 0; i < glyphs.length; i++) {
|
|
7904
|
+
let charWidth = (glyphs[i].advanceWidth ?? font.unitsPerEm * 0.6) * scale;
|
|
7905
|
+
const codePoint = chars[i]?.codePointAt(0);
|
|
7906
|
+
if (bold && (codePoint === void 0 || !isCjkCodePoint(codePoint))) {
|
|
7907
|
+
charWidth *= BOLD_FACTOR2;
|
|
7908
|
+
}
|
|
7909
|
+
totalWidth += charWidth;
|
|
7579
7910
|
}
|
|
7580
|
-
if (bold) totalWidth *= BOLD_FACTOR2;
|
|
7581
7911
|
return totalWidth;
|
|
7582
7912
|
}
|
|
7583
7913
|
getLineHeightRatio(fontFamily, fontFamilyEa) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pptx-glimpse",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "A Node.js library to render PPTX as SVG / PNG.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"author": "hirokisakabe",
|
|
63
63
|
"license": "MIT",
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"fast-xml-parser": "^
|
|
65
|
+
"fast-xml-parser": "^5.3.6",
|
|
66
66
|
"fflate": "^0.8.2",
|
|
67
67
|
"opentype.js": "^1.3.4",
|
|
68
68
|
"sharp": "^0.34.5"
|