Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bff150b1a7 | |||
| 8ffb1e5f6a | |||
| 42ff5b6226 | |||
| 539c503d0d | |||
| 4c473a2ffd | |||
| 4f64a94a15 | |||
| 5a82243965 | |||
| ebb14ee302 | |||
| 80f5e46d88 | |||
| b826b5b741 | |||
| 41228aca53 | |||
| cd3b7c1e3b | |||
| ecd525d782 |
@@ -256,6 +256,10 @@ jobs:
|
||||
# Analytics
|
||||
UMAMI_WEBSITE_ID: ${{ secrets.UMAMI_WEBSITE_ID || vars.UMAMI_WEBSITE_ID }}
|
||||
UMAMI_API_ENDPOINT: ${{ secrets.UMAMI_API_ENDPOINT || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
|
||||
|
||||
# Notifications
|
||||
GOTIFY_URL: ${{ secrets.GOTIFY_URL }}
|
||||
GOTIFY_TOKEN: ${{ secrets.GOTIFY_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -309,6 +313,10 @@ jobs:
|
||||
echo "UMAMI_WEBSITE_ID=$UMAMI_WEBSITE_ID"
|
||||
echo "UMAMI_API_ENDPOINT=$UMAMI_API_ENDPOINT"
|
||||
echo ""
|
||||
echo "# Notifications"
|
||||
echo "GOTIFY_URL=$GOTIFY_URL"
|
||||
echo "GOTIFY_TOKEN=$GOTIFY_TOKEN"
|
||||
echo ""
|
||||
echo "TARGET=$TARGET"
|
||||
echo "SENTRY_ENVIRONMENT=$TARGET"
|
||||
echo "PROJECT_NAME=$PROJECT_NAME"
|
||||
|
||||
@@ -3,7 +3,7 @@ import Image from 'next/image';
|
||||
import { getAllPosts } from '@/lib/blog';
|
||||
import { Section, Container, Heading, Card, Badge, Button } from '@/components/ui';
|
||||
import Reveal from '@/components/Reveal';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import { BlogPaginationKeyboardObserver } from '@/components/blog/BlogPaginationKeyboardObserver';
|
||||
@@ -12,6 +12,7 @@ interface BlogIndexProps {
|
||||
params: Promise<{
|
||||
locale: string;
|
||||
}>;
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: BlogIndexProps) {
|
||||
@@ -41,8 +42,10 @@ export async function generateMetadata({ params }: BlogIndexProps) {
|
||||
};
|
||||
}
|
||||
|
||||
export default async function BlogIndex({ params }: BlogIndexProps) {
|
||||
export default async function BlogIndex({ params, searchParams }: BlogIndexProps) {
|
||||
const { locale } = await params;
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const categoryParam = resolvedSearchParams.category as string | undefined;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations('Blog');
|
||||
const posts = await getAllPosts(locale);
|
||||
@@ -52,8 +55,14 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
||||
(a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime(),
|
||||
);
|
||||
|
||||
const featuredPost = sortedPosts[0];
|
||||
const remainingPosts = sortedPosts.slice(1);
|
||||
const filteredPosts = categoryParam
|
||||
? sortedPosts.filter(
|
||||
(post) => post.frontmatter.category?.toLowerCase() === categoryParam.toLowerCase(),
|
||||
)
|
||||
: sortedPosts;
|
||||
|
||||
const featuredPost = filteredPosts[0];
|
||||
const remainingPosts = filteredPosts.slice(1);
|
||||
|
||||
return (
|
||||
<div className="bg-neutral-light min-h-screen">
|
||||
@@ -123,31 +132,38 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
||||
{t('allArticles')}
|
||||
</Heading>
|
||||
<div className="flex flex-wrap gap-2 md:gap-4">
|
||||
{/* Category filters could go here */}
|
||||
<Badge
|
||||
variant="primary"
|
||||
className="cursor-pointer hover:bg-primary hover:text-white transition-colors touch-target px-3 md:px-4"
|
||||
>
|
||||
{t('categories.all')}
|
||||
</Badge>
|
||||
<Badge
|
||||
variant="neutral"
|
||||
className="cursor-pointer hover:bg-primary hover:text-white transition-colors touch-target px-3 md:px-4"
|
||||
>
|
||||
{t('categories.industry')}
|
||||
</Badge>
|
||||
<Badge
|
||||
variant="neutral"
|
||||
className="cursor-pointer hover:bg-primary hover:text-white transition-colors touch-target px-3 md:px-4"
|
||||
>
|
||||
{t('categories.technical')}
|
||||
</Badge>
|
||||
<Badge
|
||||
variant="neutral"
|
||||
className="cursor-pointer hover:bg-primary hover:text-white transition-colors touch-target px-3 md:px-4"
|
||||
>
|
||||
{t('categories.sustainability')}
|
||||
</Badge>
|
||||
<Link href={`/${locale}/blog`}>
|
||||
<Badge
|
||||
variant={!categoryParam ? 'primary' : 'neutral'}
|
||||
className="cursor-pointer hover:bg-primary hover:text-white transition-colors touch-target px-3 md:px-4"
|
||||
>
|
||||
{t('categories.all')}
|
||||
</Badge>
|
||||
</Link>
|
||||
<Link href={`/${locale}/blog?category=industry`}>
|
||||
<Badge
|
||||
variant={categoryParam === 'industry' ? 'primary' : 'neutral'}
|
||||
className="cursor-pointer hover:bg-primary hover:text-white transition-colors touch-target px-3 md:px-4"
|
||||
>
|
||||
{t('categories.industry')}
|
||||
</Badge>
|
||||
</Link>
|
||||
<Link href={`/${locale}/blog?category=technical`}>
|
||||
<Badge
|
||||
variant={categoryParam === 'technical' ? 'primary' : 'neutral'}
|
||||
className="cursor-pointer hover:bg-primary hover:text-white transition-colors touch-target px-3 md:px-4"
|
||||
>
|
||||
{t('categories.technical')}
|
||||
</Badge>
|
||||
</Link>
|
||||
<Link href={`/${locale}/blog?category=sustainability`}>
|
||||
<Badge
|
||||
variant={categoryParam === 'sustainability' ? 'primary' : 'neutral'}
|
||||
className="cursor-pointer hover:bg-primary hover:text-white transition-colors touch-target px-3 md:px-4"
|
||||
>
|
||||
{t('categories.sustainability')}
|
||||
</Badge>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
@@ -260,7 +276,7 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
||||
{t('prev')}
|
||||
</Button>
|
||||
<Button
|
||||
href={`/${locale}/blog?page=1`}
|
||||
href={`/${locale}/blog?page=1${categoryParam ? `&category=${categoryParam}` : ''}`}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="md:h-11 md:px-6 md:text-base"
|
||||
@@ -269,7 +285,7 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
||||
1
|
||||
</Button>
|
||||
<Button
|
||||
href={`/${locale}/blog?page=2`}
|
||||
href={`/${locale}/blog?page=2${categoryParam ? `&category=${categoryParam}` : ''}`}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="md:h-11 md:px-6 md:text-base"
|
||||
@@ -277,7 +293,7 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
||||
2
|
||||
</Button>
|
||||
<Button
|
||||
href={`/${locale}/blog?page=2`}
|
||||
href={`/${locale}/blog?page=2${categoryParam ? `&category=${categoryParam}` : ''}`}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="md:h-11 md:px-6 md:text-base"
|
||||
|
||||
@@ -307,7 +307,7 @@ const styles = StyleSheet.create({
|
||||
nameText: {
|
||||
fontSize: 13,
|
||||
fontWeight: 'bold',
|
||||
color: COLORS.neutralDark,
|
||||
color: COLORS.primary,
|
||||
marginBottom: 1.5,
|
||||
letterSpacing: -0.15,
|
||||
},
|
||||
@@ -334,7 +334,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
contactText: {
|
||||
fontSize: 7.5,
|
||||
color: COLORS.neutralDark,
|
||||
color: COLORS.primary,
|
||||
fontWeight: 'normal',
|
||||
paddingTop: 0.5,
|
||||
letterSpacing: 0.1,
|
||||
@@ -372,7 +372,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
addressFooterText: {
|
||||
fontSize: 6.5,
|
||||
color: COLORS.grayText,
|
||||
color: COLORS.primary,
|
||||
letterSpacing: 0.4,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
@@ -383,7 +383,7 @@ const PhoneIcon = () => (
|
||||
<Svg viewBox="0 0 24 24" width={9} height={9}>
|
||||
<Path
|
||||
d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z"
|
||||
fill="#000000"
|
||||
fill={COLORS.primary}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
@@ -392,7 +392,7 @@ const EmailIcon = () => (
|
||||
<Svg viewBox="0 0 24 24" width={9} height={9}>
|
||||
<Path
|
||||
d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"
|
||||
fill="#000000"
|
||||
fill={COLORS.primary}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
@@ -401,7 +401,7 @@ const GlobeIcon = () => (
|
||||
<Svg viewBox="0 0 24 24" width={9} height={9}>
|
||||
<Path
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"
|
||||
fill="#000000"
|
||||
fill={COLORS.primary}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
@@ -410,7 +410,7 @@ const LocationIcon = () => (
|
||||
<Svg viewBox="0 0 24 24" width={9} height={9}>
|
||||
<Path
|
||||
d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"
|
||||
fill="#000000"
|
||||
fill={COLORS.primary}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
@@ -419,7 +419,7 @@ const ScanIcon = () => (
|
||||
<Svg viewBox="0 0 24 24" width={9} height={9}>
|
||||
<Path
|
||||
d="M3 3h6v2H5v4H3V3zm18 0h-6v2h4v4h2V3zM3 21h6v-2H5v-4H3v6zm18 0h-6v-2h4v-4h2v6zM7 7h10v10H7V7z"
|
||||
fill="#000000"
|
||||
fill={COLORS.primary}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
@@ -455,6 +455,7 @@ const VectorLogo = ({
|
||||
style,
|
||||
overrideColor,
|
||||
overrideWhiteColor,
|
||||
overrideBlackColor,
|
||||
pathOverrides,
|
||||
solidPaths,
|
||||
solidBackgroundColor = '#000a66',
|
||||
@@ -463,6 +464,7 @@ const VectorLogo = ({
|
||||
style: any;
|
||||
overrideColor?: string;
|
||||
overrideWhiteColor?: string;
|
||||
overrideBlackColor?: string;
|
||||
pathOverrides?: Record<number, string>;
|
||||
solidPaths?: number[];
|
||||
solidBackgroundColor?: string;
|
||||
@@ -492,6 +494,11 @@ const VectorLogo = ({
|
||||
(p.fill === 'white' || p.fill === '#ffffff' || p.fill === '#fff')
|
||||
) {
|
||||
fill = overrideWhiteColor;
|
||||
} else if (
|
||||
overrideBlackColor &&
|
||||
(p.fill === 'black' || p.fill === '#000000' || p.fill === '#0a0a0a' || p.fill === '#000')
|
||||
) {
|
||||
fill = overrideBlackColor;
|
||||
}
|
||||
return <Path key={i} d={d} fill={fill} />;
|
||||
})}
|
||||
@@ -824,6 +831,8 @@ export const PDFBusinessCard: React.FC<PDFBusinessCardProps> = ({
|
||||
? path.join(process.cwd(), 'public', 'media', 'cable_drums_truck.png')
|
||||
: path.join(process.cwd(), 'public', 'cable_drums_bg.png');
|
||||
|
||||
const mmToPt = (mm: number) => (mm * 72) / 25.4;
|
||||
|
||||
const renderPageWrapper = (children: React.ReactNode, bleedStyle: any) => {
|
||||
if (withCropMarks) {
|
||||
return (
|
||||
@@ -838,7 +847,7 @@ export const PDFBusinessCard: React.FC<PDFBusinessCardProps> = ({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Page size={[258, 173]} style={bleedStyle}>
|
||||
<Page size={[mmToPt(91), mmToPt(61)]} style={bleedStyle}>
|
||||
{children}
|
||||
</Page>
|
||||
);
|
||||
@@ -905,16 +914,6 @@ export const PDFBusinessCard: React.FC<PDFBusinessCardProps> = ({
|
||||
solidPaths={KLZ_PATHS}
|
||||
solidBackgroundColor={isWhiteVariant ? '#ffffff' : COLORS.primary}
|
||||
/>
|
||||
|
||||
{/* Letterpress Mask Layer: We use #ff00fe as a marker color. Only the "KLZ" letters! */}
|
||||
{isViaprinto && (
|
||||
<VectorLogo
|
||||
paths={getFrontLogoPaths().filter((_, i) => KLZ_PATHS.includes(i))}
|
||||
style={[styles.frontLogo, { position: 'absolute' }]}
|
||||
overrideColor="#ff00fe"
|
||||
solidPaths={KLZ_PATHS.map((_, i) => i)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</>,
|
||||
[styles.pageBleed, isWhiteVariant && { backgroundColor: '#ffffff' }],
|
||||
@@ -926,7 +925,7 @@ export const PDFBusinessCard: React.FC<PDFBusinessCardProps> = ({
|
||||
<VectorLogo
|
||||
paths={getBackLogoPaths()}
|
||||
style={styles.backLogo}
|
||||
overrideWhiteColor={COLORS.black}
|
||||
overrideBlackColor={COLORS.primary}
|
||||
pathOverrides={{
|
||||
...(KLZ_PATHS.reduce(
|
||||
(acc: Record<number, string>, idx) => ({ ...acc, [idx]: 'none' }),
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
"prepare": "husky",
|
||||
"preinstall": "npx only-allow pnpm"
|
||||
},
|
||||
"version": "2.3.25",
|
||||
"version": "2.3.28",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -71,7 +71,7 @@ async function generateBusinessCards() {
|
||||
width: 400,
|
||||
margin: 0,
|
||||
errorCorrectionLevel: 'L', // Low error correction = less dots
|
||||
color: { dark: '#000000', light: '#ffffff' },
|
||||
color: { dark: '#000a66', light: '#ffffff' },
|
||||
});
|
||||
|
||||
const safeName = person.name.replace(/\s+/g, '_');
|
||||
@@ -121,7 +121,36 @@ async function generateBusinessCards() {
|
||||
try {
|
||||
execSync(cmykCommand, { stdio: 'inherit' });
|
||||
fs.unlinkSync(tempViaprintoPath);
|
||||
console.log(`✓ CMYK Print File generated: ${finalFileNameCMYK}`);
|
||||
console.log(`[INFO] Injecting PANTONE 289 C Spot Color...`);
|
||||
const injectPantoneCommand = `npx tsx scripts/inject-pantone.ts "${finalPathCMYK}"`;
|
||||
execSync(injectPantoneCommand, { stdio: 'inherit' });
|
||||
|
||||
console.log(`✓ 5-Color Print File generated: ${finalFileNameCMYK}`);
|
||||
} catch (e) {
|
||||
console.error(`❌ CMYK conversion failed! Is Ghostscript installed?`);
|
||||
}
|
||||
|
||||
// 2.5 SAXOPRINT PDF (Pure CMYK, no mask, no Pantone)
|
||||
console.log(`- Rendering Saxoprint PDF (Pure CMYK)...`);
|
||||
const tempSaxoprintPath = path.join(CONFIG.outputDir, `temp_saxoprint_${safeName}.pdf`);
|
||||
buffer = await renderToBuffer(
|
||||
React.createElement(PDFBusinessCard, {
|
||||
person,
|
||||
qrCodeDataUrl,
|
||||
variant: 'blue',
|
||||
}),
|
||||
);
|
||||
fs.writeFileSync(tempSaxoprintPath, buffer);
|
||||
|
||||
const finalFileNameSaxoCMYK = `KLZ_Visitenkarte_${safeName}_Saxoprint_CMYK_91x61mm_3mmBleed.pdf`;
|
||||
const finalPathSaxoCMYK = path.join(CONFIG.outputDir, finalFileNameSaxoCMYK);
|
||||
|
||||
console.log(`- Converting to CMYK for Saxoprint...`);
|
||||
const cmykSaxoCommand = `bash "${CONFIG.ghostscriptScript}" "${tempSaxoprintPath}" "${finalPathSaxoCMYK}"`;
|
||||
try {
|
||||
execSync(cmykSaxoCommand, { stdio: 'inherit' });
|
||||
fs.unlinkSync(tempSaxoprintPath);
|
||||
console.log(`✓ 4-Color CMYK Print File generated: ${finalFileNameSaxoCMYK}`);
|
||||
} catch (e) {
|
||||
console.error(`❌ CMYK conversion failed! Is Ghostscript installed?`);
|
||||
}
|
||||
|
||||
105
scripts/inject-pantone.ts
Normal file
105
scripts/inject-pantone.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { PDFDocument, PDFName, decodePDFRawStream, PDFRawStream } from 'pdf-lib';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
async function run() {
|
||||
const inputPath = process.argv[2];
|
||||
const outputPath = process.argv[3] || inputPath;
|
||||
|
||||
if (!inputPath || !fs.existsSync(inputPath)) {
|
||||
console.error(`[ERROR] Input file not found: ${inputPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`[INFO] Injecting PANTONE 289 C into: ${path.basename(inputPath)}`);
|
||||
const pdfBytes = fs.readFileSync(inputPath);
|
||||
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||
|
||||
// CMYK tint transform for PANTONE 289 C.
|
||||
// Approximation: 100C, 64M, 0Y, 60K (1.0 0.64 0.0 0.60)
|
||||
// The function maps an input x (0 to 1) to CMYK: x*1.0, x*0.64, x*0.0, x*0.60
|
||||
const tintTransform = pdfDoc.context.obj({
|
||||
FunctionType: 2,
|
||||
Domain: [0, 1],
|
||||
C0: [0, 0, 0, 0],
|
||||
C1: [1.0, 0.64, 0.0, 0.6],
|
||||
N: 1,
|
||||
});
|
||||
|
||||
// Create the /Separation color space array
|
||||
const pantoneCS = pdfDoc.context.obj([
|
||||
PDFName.of('Separation'),
|
||||
PDFName.of('PANTONE#20289#20C'),
|
||||
PDFName.of('DeviceCMYK'),
|
||||
tintTransform,
|
||||
]);
|
||||
const csRef = pdfDoc.context.register(pantoneCS);
|
||||
|
||||
const pages = pdfDoc.getPages();
|
||||
for (const page of pages) {
|
||||
let resources = page.node.Resources();
|
||||
if (!resources) {
|
||||
resources = pdfDoc.context.obj({});
|
||||
page.node.set(PDFName.of('Resources'), resources);
|
||||
}
|
||||
|
||||
// Ensure ColorSpace dict exists
|
||||
let colorSpaces = resources.get(PDFName.of('ColorSpace')) as any;
|
||||
if (!colorSpaces) {
|
||||
colorSpaces = pdfDoc.context.obj({});
|
||||
resources.set(PDFName.of('ColorSpace'), colorSpaces);
|
||||
}
|
||||
// Register the PANTONE color space as /CSPantone in the page's resources
|
||||
colorSpaces.set(PDFName.of('CSPantone'), csRef);
|
||||
|
||||
// Read all content streams
|
||||
const contents = page.node.Contents();
|
||||
let streams: any[] = [];
|
||||
if (contents) {
|
||||
const resolvedContents = pdfDoc.context.lookup(contents);
|
||||
if (resolvedContents instanceof PDFRawStream) {
|
||||
streams = [resolvedContents];
|
||||
} else if (resolvedContents.constructor.name === 'PDFArray') {
|
||||
const arr = resolvedContents as any;
|
||||
for (let j = 0; j < arr.size(); j++) {
|
||||
streams.push(pdfDoc.context.lookup(arr.get(j)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let newStr = '';
|
||||
for (let i = 0; i < streams.length; i++) {
|
||||
const stream = streams[i];
|
||||
if (stream instanceof PDFRawStream) {
|
||||
const decoded = decodePDFRawStream(stream).decode();
|
||||
const str = Buffer.from(decoded).toString('utf-8');
|
||||
newStr += str + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// The CMYK values for the KLZ Blue created by Ghostscript from #000a66
|
||||
// is '1 0.98 0.224 0.298'. We use a slightly dynamic regex to catch small precision diffs.
|
||||
const dynamicRegexK = /1 0\.98[0-9]* 0\.224[0-9]* 0\.298[0-9]* k/g;
|
||||
const dynamicRegexStrokeK = /1 0\.98[0-9]* 0\.224[0-9]* 0\.298[0-9]* K/g;
|
||||
|
||||
let modified = false;
|
||||
if (dynamicRegexK.test(newStr) || dynamicRegexStrokeK.test(newStr)) {
|
||||
newStr = newStr.replace(dynamicRegexK, '/CSPantone cs 1 scn');
|
||||
newStr = newStr.replace(dynamicRegexStrokeK, '/CSPantone CS 1 SCN');
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
const newStream = pdfDoc.context.flateStream(newStr);
|
||||
const newRef = pdfDoc.context.register(newStream);
|
||||
page.node.set(PDFName.of('Contents'), newRef);
|
||||
console.log(`[INFO] Spot color injected into page ${pages.indexOf(page) + 1}.`);
|
||||
}
|
||||
}
|
||||
|
||||
const finalPdfBytes = await pdfDoc.save();
|
||||
fs.writeFileSync(outputPath, finalPdfBytes);
|
||||
console.log(`[SUCCESS] Saved 5-color PDF to ${outputPath}`);
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
Reference in New Issue
Block a user