apply_hwp_text_style
Apply color, bold, italic, underline, or font size to the first occurrence of target text in a HWP document. Specify formatting options to update the character style.
Instructions
Apply formatting (color/bold/italic/underline/font_size) to the first run that contains target_text. Adds a new charPr to header.xml and retargets the matching hp:run. Color is a 6-digit hex (e.g. 'FF0000'). Args: file_path, target_text, color/bold/italic/underline/font_size (any subset), output_path (optional).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file_path | Yes | ||
| target_text | Yes | ||
| color | No | ||
| bold | No | ||
| italic | No | ||
| underline | No | ||
| font_size | No | ||
| output_path | No |
Implementation Reference
- src/core/hwpx-mutate.ts:816-910 (handler)Core implementation of applyHwpxTextStyle: reads an .hwpx file, adds a new <hh:charPr> to header.xml with the requested text style (color/bold/italic/underline/fontSize), then retargets the first <hp:run> containing the target_text to use that new charPr.
export async function applyHwpxTextStyle( inputPath: string, outputPath: string, targetText: string, style: TextStyle ): Promise<{ retargeted: number; charPrId: string }> { const bytes = await readFile(inputPath); const zip = await JSZip.loadAsync(bytes); const headerName = Object.keys(zip.files).find((n) => /^Contents\/header\.xml$/i.test(n)); if (!headerName) throw new Error("Contents/header.xml missing — cannot edit charPr"); let header = await zip.files[headerName].async("string"); // Find the existing <hh:charPrList> ... </hh:charPrList> if present, else the // last <hh:charPr id="..."> element block. const listMatch = /<hh:charPrList>([\s\S]*?)<\/hh:charPrList>/.exec(header); const charPrRegex = /<hh:charPr [^>]*>[\s\S]*?<\/hh:charPr>/g; const list = listMatch ? listMatch[1] : header; const all = [...list.matchAll(charPrRegex)]; if (all.length === 0) throw new Error("No <hh:charPr> found in header.xml"); const baseCharPr = all[0][0]; const baseId = baseCharPr.match(/id="(\d+)"/)?.[1] ?? "0"; const maxId = Math.max( ...all.map((m) => Number(m[0].match(/id="(\d+)"/)?.[1] ?? 0)) ); const newId = String(maxId + 1); // Mutate baseCharPr to a new charPr with the requested style let mutated = baseCharPr.replace(/id="\d+"/, `id="${newId}"`); if (style.color) { if (/<hc:color [^>]*>/.test(mutated)) { mutated = mutated.replace(/<hc:color [^>]*\/>/, `<hc:color val="#${style.color}"/>`); } else { mutated = mutated.replace( /<\/hh:charPr>/, `<hc:color val="#${style.color}"/></hh:charPr>` ); } } // bold / italic / underline are usually attributes, not children if (style.bold !== undefined) { if (/bold="[^"]*"/.test(mutated)) { mutated = mutated.replace(/bold="[^"]*"/, `bold="${style.bold ? "1" : "0"}"`); } else { mutated = mutated.replace(/<hh:charPr /, `<hh:charPr bold="${style.bold ? "1" : "0"}" `); } } if (style.italic !== undefined) { if (/italic="[^"]*"/.test(mutated)) { mutated = mutated.replace(/italic="[^"]*"/, `italic="${style.italic ? "1" : "0"}"`); } else { mutated = mutated.replace(/<hh:charPr /, `<hh:charPr italic="${style.italic ? "1" : "0"}" `); } } if (style.fontSize) { if (/height="\d+"/.test(mutated)) { mutated = mutated.replace(/height="\d+"/, `height="${style.fontSize}"`); } } const newHeader = header.replace(baseCharPr, baseCharPr + mutated); zip.file(headerName, newHeader); // Now retarget the first run carrying targetText const sectionName = Object.keys(zip.files).find((n) => /^Contents\/section\d+\.xml$/i.test(n))!; const xml = await zip.files[sectionName].async("string"); const runRegex = new RegExp( `(<hp:run )(charPrIDRef="\\d+")(>[^<]*<hp:t>[^<]*${escapeRegex( targetText )}[^<]*</hp:t>)`, "g" ); let retargeted = 0; const newXml = xml.replace(runRegex, (_m, openPart, _ref, rest) => { if (retargeted > 0) return _m; retargeted = 1; return `${openPart}charPrIDRef="${newId}"${rest}`; }); if (retargeted === 0) { return { retargeted: 0, charPrId: newId }; } zip.file(sectionName, newXml); if (zip.files["mimetype"]) { const mt = await zip.files["mimetype"].async("string"); zip.file("mimetype", mt, { compression: "STORE" }); } const out = await zip.generateAsync({ type: "uint8array", compression: "DEFLATE", compressionOptions: { level: 6 }, }); await writeFile(outputPath, out); return { retargeted, charPrId: newId }; } - src/tools/edit.ts:292-311 (handler)Tool handler for apply_hwp_text_style: validates input via preflight(), builds a TextStyle object from args, calls applyHwpxTextStyle from hwpx-mutate, and returns a user-facing success/error message.
export async function applyHwpTextStyle(args: ApplyStyleArgs): Promise<string> { const err = preflight(args.file_path); if (err) return err; const out = args.output_path && args.output_path.length > 0 ? args.output_path : defaultOutput(args.file_path, "styled"); const style: TextStyle = {}; if (args.color) style.color = args.color.replace(/^#/, ""); if (args.bold !== undefined) style.bold = args.bold; if (args.italic !== undefined) style.italic = args.italic; if (args.underline !== undefined) style.underline = args.underline; if (args.font_size !== undefined) style.fontSize = args.font_size; try { const r = await applyHwpxTextStyle(args.file_path, out, args.target_text, style); if (r.retargeted === 0) return `대상 텍스트를 찾지 못했습니다 (target not found): ${args.target_text}`; return `텍스트 서식 적용 (style applied): '${args.target_text}' → charPrId=${r.charPrId}\n저장 (saved): ${out}`; } catch (e) { return `서식 적용 오류 (style error): ${(e as Error).message}`; } } - src/tools/edit.ts:281-290 (schema)ApplyStyleArgs interface defining input schema for the tool: file_path, target_text, optional color/bold/italic/underline/font_size, and output_path.
export interface ApplyStyleArgs { file_path: string; target_text: string; color?: string; bold?: boolean; italic?: boolean; underline?: boolean; font_size?: number; output_path?: string; } - src/core/hwpx-mutate.ts:562-568 (schema)TextStyle interface used internally: color (6-digit hex), bold, italic, underline, fontSize.
export interface TextStyle { color?: string; // 6-digit hex e.g. "FF0000" bold?: boolean; italic?: boolean; underline?: boolean; fontSize?: number; // HWP units; 1300 ≈ 13pt } - src/server.ts:433-451 (registration)Tool registration with name 'apply_hwp_text_style', description, and JSON inputSchema (file_path, target_text, optional color/bold/italic/underline/font_size, output_path).
{ name: "apply_hwp_text_style", description: "Apply formatting (color/bold/italic/underline/font_size) to the first run that contains target_text. Adds a new charPr to header.xml and retargets the matching <hp:run>. Color is a 6-digit hex (e.g. 'FF0000'). Args: file_path, target_text, color/bold/italic/underline/font_size (any subset), output_path (optional).", inputSchema: { type: "object", properties: { file_path: { type: "string" }, target_text: { type: "string" }, color: { type: "string" }, bold: { type: "boolean" }, italic: { type: "boolean" }, underline: { type: "boolean" }, font_size: { type: "number" }, output_path: { type: "string" }, }, required: ["file_path", "target_text"], }, }, - src/server.ts:27-36 (registration)Import of applyHwpTextStyle from './tools/edit.js'.
applyHwpTextStyle, applyHwpParagraphStyle, mergeHwpCellsHorizontal, mergeHwpCellsVertical, deleteHwpImage, setHwpFieldValue, setHwpParagraphText, setHwpCellText, } from "./tools/edit.js"; import { renderHwpHtml, renderHwpEquationSvg } from "./tools/render-extra.js"; - src/server.ts:534-534 (registration)Registration of apply_hwp_text_style in the HANDLERS record mapping tool names to handler functions.
apply_hwp_text_style: applyHwpTextStyle,