@json-to-office/jto 0.28.1 → 0.29.0
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/cli.js +151 -37
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/index-DgKS-gEZ.js.map +1 -1
- package/dist/render-server.d.ts +2 -1
- package/dist/render-server.js +149 -36
- package/dist/render-server.js.map +1 -1
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -1910,19 +1910,80 @@ import {
|
|
|
1910
1910
|
clampVisualDpi,
|
|
1911
1911
|
DEFAULT_VISUAL_DPI,
|
|
1912
1912
|
MIN_VISUAL_DPI,
|
|
1913
|
-
MAX_VISUAL_DPI
|
|
1913
|
+
MAX_VISUAL_DPI,
|
|
1914
|
+
MAX_RASTERIZE_BATCH_SLIDES
|
|
1914
1915
|
} from "@json-to-office/shared";
|
|
1915
|
-
import {
|
|
1916
|
+
import {
|
|
1917
|
+
createLibreOfficePptxRasterizer,
|
|
1918
|
+
createLibreOfficePptxBatchRasterizer
|
|
1919
|
+
} from "@json-to-office/jto-cli";
|
|
1916
1920
|
function getSharedRasterizer() {
|
|
1917
1921
|
if (!sharedRasterizer) {
|
|
1918
1922
|
sharedRasterizer = createLibreOfficePptxRasterizer();
|
|
1919
1923
|
}
|
|
1920
1924
|
return sharedRasterizer;
|
|
1921
1925
|
}
|
|
1926
|
+
function getSharedBatchRasterizer() {
|
|
1927
|
+
if (!sharedBatchRasterizer) {
|
|
1928
|
+
sharedBatchRasterizer = createLibreOfficePptxBatchRasterizer();
|
|
1929
|
+
}
|
|
1930
|
+
return sharedBatchRasterizer;
|
|
1931
|
+
}
|
|
1932
|
+
function estimateSlidePixels(presentation, dpi) {
|
|
1933
|
+
const props = presentation?.props;
|
|
1934
|
+
const dim = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
1935
|
+
const widthIn = dim(props?.slideWidth, DEFAULT_SLIDE_WIDTH_IN);
|
|
1936
|
+
const heightIn = dim(props?.slideHeight, DEFAULT_SLIDE_HEIGHT_IN);
|
|
1937
|
+
return widthIn * dpi * (heightIn * dpi);
|
|
1938
|
+
}
|
|
1939
|
+
function assertPixelBudget(slides) {
|
|
1940
|
+
let total = 0;
|
|
1941
|
+
for (const slide of slides) {
|
|
1942
|
+
const pixels = estimateSlidePixels(slide.presentation, slide.dpi);
|
|
1943
|
+
if (pixels > MAX_SLIDE_PIXELS) {
|
|
1944
|
+
throw new HTTPException3(400, {
|
|
1945
|
+
message: "Requested slide dimensions are too large"
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
total += pixels;
|
|
1949
|
+
}
|
|
1950
|
+
if (total > MAX_BATCH_PIXELS) {
|
|
1951
|
+
throw new HTTPException3(400, {
|
|
1952
|
+
message: "Requested batch raster size is too large"
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
function resolveSafeBaseDir(baseDir) {
|
|
1957
|
+
if (baseDir === void 0) return void 0;
|
|
1958
|
+
const resolved = path6.resolve(baseDir);
|
|
1959
|
+
const cwd = process.cwd();
|
|
1960
|
+
if (resolved !== cwd && !resolved.startsWith(cwd + path6.sep)) {
|
|
1961
|
+
throw new HTTPException3(400, {
|
|
1962
|
+
message: "baseDir must be inside the server working directory"
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
return resolved;
|
|
1966
|
+
}
|
|
1967
|
+
function toHttpException(error) {
|
|
1968
|
+
if (error instanceof HTTPException3) return error;
|
|
1969
|
+
if (error instanceof UnsafeOutboundSourceError) {
|
|
1970
|
+
return new HTTPException3(400, { message: error.message });
|
|
1971
|
+
}
|
|
1972
|
+
const msg = error instanceof Error ? error.message.toLowerCase() : String(error);
|
|
1973
|
+
if (msg.includes("not found") || msg.includes("rasterization needs")) {
|
|
1974
|
+
return new HTTPException3(503, { message: error.message });
|
|
1975
|
+
}
|
|
1976
|
+
if (msg.includes("invalid") || msg.includes("validation")) {
|
|
1977
|
+
return new HTTPException3(400, { message: error.message });
|
|
1978
|
+
}
|
|
1979
|
+
return new HTTPException3(500, {
|
|
1980
|
+
message: "Internal server error during rasterization"
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1922
1983
|
function registerRasterizeRoute(router, options = {}) {
|
|
1923
1984
|
const getRasterizer = options.getRasterizer ?? getSharedRasterizer;
|
|
1924
|
-
|
|
1925
|
-
|
|
1985
|
+
const getBatchRasterizer = options.getBatchRasterizer ?? getSharedBatchRasterizer;
|
|
1986
|
+
const shared = [
|
|
1926
1987
|
...options.preMiddleware ?? [],
|
|
1927
1988
|
bodyLimit({
|
|
1928
1989
|
maxSize: 32 * 1024 * 1024,
|
|
@@ -1930,22 +1991,26 @@ function registerRasterizeRoute(router, options = {}) {
|
|
|
1930
1991
|
throw new HTTPException3(413, { message: "Request body too large" });
|
|
1931
1992
|
}
|
|
1932
1993
|
}),
|
|
1933
|
-
jsonOnly
|
|
1994
|
+
jsonOnly
|
|
1995
|
+
];
|
|
1996
|
+
const guard = async (run) => {
|
|
1997
|
+
try {
|
|
1998
|
+
return await run();
|
|
1999
|
+
} catch (error) {
|
|
2000
|
+
options.onError?.(error);
|
|
2001
|
+
throw toHttpException(error);
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
router.post(
|
|
2005
|
+
"/rasterize",
|
|
2006
|
+
...shared,
|
|
1934
2007
|
tbValidator(RasterizeRequestSchema),
|
|
1935
2008
|
async (c) => {
|
|
1936
2009
|
const { presentation, dpi, baseDir } = getValidated(c, "json");
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
if (resolved !== cwd && !resolved.startsWith(cwd + path6.sep)) {
|
|
1942
|
-
throw new HTTPException3(400, {
|
|
1943
|
-
message: "baseDir must be inside the server working directory"
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
safeBaseDir = resolved;
|
|
1947
|
-
}
|
|
1948
|
-
try {
|
|
2010
|
+
const safeBaseDir = resolveSafeBaseDir(baseDir);
|
|
2011
|
+
const effectiveDpi = clampVisualDpi(dpi ?? DEFAULT_VISUAL_DPI);
|
|
2012
|
+
assertPixelBudget([{ presentation, dpi: effectiveDpi }]);
|
|
2013
|
+
const result = await guard(async () => {
|
|
1949
2014
|
if (options.sourcePolicy) {
|
|
1950
2015
|
assertSafeOutboundSources(
|
|
1951
2016
|
presentation,
|
|
@@ -1953,33 +2018,57 @@ function registerRasterizeRoute(router, options = {}) {
|
|
|
1953
2018
|
"presentation"
|
|
1954
2019
|
);
|
|
1955
2020
|
}
|
|
1956
|
-
|
|
2021
|
+
return getRasterizer()({
|
|
1957
2022
|
presentation,
|
|
1958
|
-
dpi:
|
|
2023
|
+
dpi: effectiveDpi,
|
|
1959
2024
|
baseDir: safeBaseDir
|
|
1960
2025
|
});
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2026
|
+
});
|
|
2027
|
+
return c.json(result);
|
|
2028
|
+
}
|
|
2029
|
+
);
|
|
2030
|
+
router.post(
|
|
2031
|
+
"/rasterize/batch",
|
|
2032
|
+
...shared,
|
|
2033
|
+
tbValidator(RasterizeBatchRequestSchema),
|
|
2034
|
+
async (c) => {
|
|
2035
|
+
const { slides, baseDir } = getValidated(c, "json");
|
|
2036
|
+
const safeBaseDir = resolveSafeBaseDir(baseDir);
|
|
2037
|
+
const effectiveSlides = slides.map((slide) => ({
|
|
2038
|
+
presentation: slide.presentation,
|
|
2039
|
+
dpi: clampVisualDpi(slide.dpi ?? DEFAULT_VISUAL_DPI)
|
|
2040
|
+
}));
|
|
2041
|
+
assertPixelBudget(effectiveSlides);
|
|
2042
|
+
const result = await guard(async () => {
|
|
2043
|
+
if (options.sourcePolicy) {
|
|
2044
|
+
slides.forEach(
|
|
2045
|
+
(slide, index) => assertSafeOutboundSources(
|
|
2046
|
+
slide.presentation,
|
|
2047
|
+
options.sourcePolicy,
|
|
2048
|
+
`slides[${index}].presentation`
|
|
2049
|
+
)
|
|
2050
|
+
);
|
|
1974
2051
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
2052
|
+
return getBatchRasterizer()({
|
|
2053
|
+
slides: effectiveSlides,
|
|
2054
|
+
baseDir: safeBaseDir
|
|
1977
2055
|
});
|
|
1978
|
-
}
|
|
2056
|
+
});
|
|
2057
|
+
return c.json({
|
|
2058
|
+
results: result.results.map((slide) => {
|
|
2059
|
+
if (slide.ok) return slide;
|
|
2060
|
+
options.onError?.(new Error(slide.error));
|
|
2061
|
+
return slide.stage === "build" ? { ok: false, error: slide.error, stage: slide.stage } : {
|
|
2062
|
+
ok: false,
|
|
2063
|
+
error: "Slide rasterization failed",
|
|
2064
|
+
stage: slide.stage
|
|
2065
|
+
};
|
|
2066
|
+
})
|
|
2067
|
+
});
|
|
1979
2068
|
}
|
|
1980
2069
|
);
|
|
1981
2070
|
}
|
|
1982
|
-
var RasterizeRequestSchema, sharedRasterizer, jsonOnly;
|
|
2071
|
+
var RasterizeRequestSchema, RasterizeBatchRequestSchema, sharedRasterizer, sharedBatchRasterizer, jsonOnly, DEFAULT_SLIDE_WIDTH_IN, DEFAULT_SLIDE_HEIGHT_IN, MAX_SLIDE_PIXELS, MAX_BATCH_PIXELS;
|
|
1983
2072
|
var init_rasterize_route = __esm({
|
|
1984
2073
|
"src/server/rasterize-route.ts"() {
|
|
1985
2074
|
"use strict";
|
|
@@ -1996,6 +2085,24 @@ var init_rasterize_route = __esm({
|
|
|
1996
2085
|
},
|
|
1997
2086
|
{ additionalProperties: false }
|
|
1998
2087
|
);
|
|
2088
|
+
RasterizeBatchRequestSchema = Type2.Object(
|
|
2089
|
+
{
|
|
2090
|
+
slides: Type2.Array(
|
|
2091
|
+
Type2.Object(
|
|
2092
|
+
{
|
|
2093
|
+
presentation: Type2.Object({}, { additionalProperties: true }),
|
|
2094
|
+
dpi: Type2.Optional(
|
|
2095
|
+
Type2.Number({ minimum: MIN_VISUAL_DPI, maximum: MAX_VISUAL_DPI })
|
|
2096
|
+
)
|
|
2097
|
+
},
|
|
2098
|
+
{ additionalProperties: false }
|
|
2099
|
+
),
|
|
2100
|
+
{ minItems: 1, maxItems: MAX_RASTERIZE_BATCH_SLIDES }
|
|
2101
|
+
),
|
|
2102
|
+
baseDir: Type2.Optional(Type2.String())
|
|
2103
|
+
},
|
|
2104
|
+
{ additionalProperties: false }
|
|
2105
|
+
);
|
|
1999
2106
|
jsonOnly = async (c, next) => {
|
|
2000
2107
|
const contentType = c.req.header("content-type");
|
|
2001
2108
|
if (!contentType || !contentType.includes("application/json")) {
|
|
@@ -2005,6 +2112,10 @@ var init_rasterize_route = __esm({
|
|
|
2005
2112
|
}
|
|
2006
2113
|
await next();
|
|
2007
2114
|
};
|
|
2115
|
+
DEFAULT_SLIDE_WIDTH_IN = 13.34;
|
|
2116
|
+
DEFAULT_SLIDE_HEIGHT_IN = 7.5;
|
|
2117
|
+
MAX_SLIDE_PIXELS = 64e6;
|
|
2118
|
+
MAX_BATCH_PIXELS = 256e6;
|
|
2008
2119
|
}
|
|
2009
2120
|
});
|
|
2010
2121
|
|
|
@@ -2638,6 +2749,9 @@ function createFormatRouter(adapter) {
|
|
|
2638
2749
|
rateLimiter({
|
|
2639
2750
|
limit: process.env.NODE_ENV === "production" ? 10 : 1e3,
|
|
2640
2751
|
window: 15 * 60 * 1e3,
|
|
2752
|
+
// One bucket for /rasterize AND /rasterize/batch — without a shared
|
|
2753
|
+
// namespace the default per-path key would double the budget.
|
|
2754
|
+
namespace: "rasterize",
|
|
2641
2755
|
trustProxy: config.rateLimit.trustProxy
|
|
2642
2756
|
})
|
|
2643
2757
|
],
|
|
@@ -4311,7 +4425,7 @@ function createDevCommand(adapter) {
|
|
|
4311
4425
|
}
|
|
4312
4426
|
|
|
4313
4427
|
// src/cli.ts
|
|
4314
|
-
var PACKAGE_VERSION = true ? "0.
|
|
4428
|
+
var PACKAGE_VERSION = true ? "0.29.0" : "dev-mode";
|
|
4315
4429
|
var program = new Command2();
|
|
4316
4430
|
program.name("jto").description("JSON to Office CLI - Generate .docx and .pptx from JSON").version(PACKAGE_VERSION);
|
|
4317
4431
|
registerCoreCommands(program, {
|